Changeset 1121
- Timestamp:
- 2008-07-25 10:01:11 (6 months ago)
- Files:
-
- trunk/app/models/node.rb (modified) (1 diff)
- trunk/app/views/versions/edit.rhtml (modified) (1 diff)
- trunk/app/views/versions/preview.rjs (modified) (1 diff)
- trunk/config/environment.rb (modified) (1 diff)
- trunk/lib/comment_query.rb (added)
- trunk/lib/node_query.rb (modified) (9 diffs)
- trunk/lib/parser/lib/rules/zena.rb (modified) (7 diffs)
- trunk/lib/query_builder/lib/query_builder.rb (modified) (25 diffs)
- trunk/lib/query_builder/test/query_builder/errors.yml (modified) (1 diff)
- trunk/lib/query_builder/test/query_builder/filters.yml (modified) (1 diff)
- trunk/lib/query_builder/test/query_builder/mixed.yml (added)
- trunk/lib/query_builder/test/query_builder_test.rb (modified) (4 diffs)
- trunk/lib/yaml_test.rb (modified) (1 diff)
- trunk/public/javascripts/zena.js (modified) (1 diff)
- trunk/test/helpers/application_helper_test.rb (modified) (1 diff)
- trunk/test/helpers/node_query/relations.yml (modified) (1 diff)
- trunk/test/helpers/node_query_test.rb (modified) (2 diffs)
- trunk/test/helpers/zena_parser/ajax.yml (modified) (1 diff)
- trunk/test/helpers/zena_parser/basic.yml (modified) (3 diffs)
- trunk/test/helpers/zena_parser/relations.yml (modified) (1 diff)
- trunk/test/sites/zena/dyn_attributes.yml (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/app/models/node.rb
r1103 r1121 1004 1004 return unless str && str != "" 1005 1005 self[:name] = str.url_name 1006 end 1007 1008 # Return current discussion id 1009 def discussion_id 1010 discussion ? discussion[:id] : nil 1006 1011 end 1007 1012 trunk/app/views/versions/edit.rhtml
r1053 r1121 38 38 Zena.resizeElement('node_v_text'); 39 39 } 40 41 $('node_form').getElements().each(function(input, index) { 42 new Form.Element.Observer(input, 3, function(element, value) { 43 <%= "opener.Zena.editor_preview('#{preview_version_url(:node_id=>@node[:zip], :id=>(@node.v_number || 0), :_method => :put, :escape => false)}',element,value);" %> 44 }); 45 }); 46 40 47 <%= javascript_end %> 41 <%# observe all fields ? new Form.Observer('node_form', 3, function(element,value){ opener.Zena.editor_preview(element,value)}); %>42 <% ['v_text', 'v_summary', 'v_title'].each do |field| -%>43 <%= observe_field("node_#{field}", :frequency=>3, :function=>"opener.Zena.editor_preview('#{preview_version_url(:node_id=>@node[:zip], :id=>(@node.v_number || 0), :_method => :put, :key => field, :escape => false)}',element,value)") %>44 <% end -%>trunk/app/views/versions/preview.rjs
r883 r1121 1 key = "#{@key}#{@node[:zip]}" 2 1 3 page << "try {" 2 @value = zazen(@value, :pretty_code=>true, :preview => true) if @key != 'v_title' 3 page.replace_html "#{@key}#{@node[:zip]}", @value 4 page << "if ($('#{key}').classNames().include('zazen')) {" 5 page.replace_html key, zazen(@value, :pretty_code=>true, :preview => true) 6 page << "} else {" 7 page.replace_html key, @value 8 page << "}" 4 9 page << "} catch(err) {}" trunk/config/environment.rb
r1108 r1121 87 87 require File.join(lib_path, 'base_additions') 88 88 require File.join(lib_path, 'node_query') 89 require File.join(lib_path, 'comment_query') 89 90 ZazenParser = Parser.parser_with_rules(Zazen::Rules, Zazen::Tags) 90 91 ZafuParser = Parser.parser_with_rules(Zafu::Rules, Zena::Rules, Zafu::Tags, Zena::Tags) trunk/lib/node_query.rb
r1111 r1121 4 4 class NodeQuery < QueryBuilder 5 5 attr_reader :context, :uses_node_name 6 set_main_table :nodes 6 set_main_table 'nodes' 7 set_main_class 'Node' 8 7 9 load_custom_queries File.join(File.dirname(__FILE__), 'custom_queries') 8 10 … … 30 32 def default_order_clause 31 33 "position ASC, name ASC" 34 end 35 36 def default_context_filter 37 'self' 32 38 end 33 39 … … 94 100 unless fields 95 101 if klass = Node.get_class(rel) 96 parse_context( 'self') unless context102 parse_context(default_context_filter) unless context 97 103 @where << "#{table}.kpath LIKE '#{klass.kpath}%'" 98 104 return true … … 105 111 @where << "#{field_or_param(fields[0])} = #{field_or_param(fields[1], table(main_table,-1))}" 106 112 true 113 end 114 115 def parse_change_class(rel, is_last) 116 case rel 117 when 'comments' 118 if is_last 119 # no need to load discussions, versions and all the mess 120 add_table('comments') 121 @where << "#{table('comments')}.discussion_id = #{map_parameter('discussion_id')}" 122 return CommentQuery # class change 123 else 124 # parse_context(default_context_filter, true) if is_last 125 # after_parse 126 @errors << "comments with join not implemented" 127 return nil 128 end 129 else 130 return nil 131 end 107 132 end 108 133 … … 111 136 if rel == main_table || rel == 'children' 112 137 # dummy clauses 113 parse_context( 'self') unless context138 parse_context(default_context_filter) unless context 114 139 return :void 115 140 end … … 214 239 @uses_node_name = true 215 240 "\#{#{@node_name}.get_#{fld}}" 216 when 'id', 'parent_id' 241 when 'id', 'parent_id', 'discussion_id' 217 242 @uses_node_name = true 218 243 "\#{#{@node_name}.#{fld}}" … … 251 276 end 252 277 end 253 254 def class_from_table(table_name) 255 case table_name 256 when 'nodes' 257 Node 258 when 'versions' 259 Version 260 when 'comments' 261 Comment 262 when 'data_entries' 263 DataEntry 264 else 265 # ? error 266 Object 267 end 268 end 269 278 270 279 def parse_custom_query_argument(key, value) 271 280 return nil unless value … … 341 350 end 342 351 query = NodeQuery.new(pseudo_sql, opts.merge(:custom_query_group => visitor.site.host)) 343 [query.to_sql, query.errors, query.uses_node_name, query. result_class]352 [query.to_sql, query.errors, query.uses_node_name, query.main_class] 344 353 end 345 354 end … … 349 358 350 359 # Find a node and propagate visitor 351 def do_find(count, query, ignore_source = false )360 def do_find(count, query, ignore_source = false, klass = Node) 352 361 return nil if query.empty? 353 362 return nil if (new_record? && !ignore_source) # do not run query (might contain nil id) 354 res = Node.find_by_sql(query)363 res = klass.find_by_sql(query) 355 364 if count == :all 356 365 if res == [] trunk/lib/parser/lib/rules/zena.rb
r1120 r1121 359 359 360 360 attribute = @params[:attr] || @params[:tattr] || @params[:date] 361 362 if (@params[:edit_preview] || @params[:ep]) == 'true' 363 @html_tag_params[:id] = "#{attribute}#{erb_node_id}" 364 @html_tag ||= 'span' 365 end 366 361 367 if @params[:edit] == 'true' && !['url','path'].include?(attribute) 362 368 "<% if #{node}.can_write? -%><span class='show_edit' id='#{erb_dom_id("_#{attribute}")}'>#{actions}<%= link_to_remote(#{attribute_method}, :url => edit_node_path(#{node_id}) + \"?attribute=#{attribute}&dom_id=#{dom_id("_#{attribute}")}#{@params[:publish] == 'true' ? '&publish=true' : ''}\", :method => :get) %></span><% else -%>#{actions}<%= #{attribute_method} %><% end -%>" … … 385 391 else 386 392 # error 393 end 394 395 @html_tag ||= 'div' 396 397 add_html_class('zazen') 398 399 if (@params[:edit_preview] || @params[:ep]) == 'true' 400 @html_tag_params[:id] = "#{attribute}#{erb_node_id}" 387 401 end 388 402 … … 679 693 680 694 @html_tag ||= 'div' 695 681 696 if @html_tag_params[:id] 682 697 # add a sub-div … … 688 703 end 689 704 690 if c = @html_tag_params[:class] || @params[:class] 691 @html_tag_params[:class] = "#{c} zazen" 692 else 693 @html_tag_params[:class] = 'zazen' 694 end 705 add_html_class('zazen') 706 695 707 unless @params[:empty] == 'true' 696 708 out "#{pre}<% if #{node}.kind_of?(TextDocument); l = #{node}.content_lang -%>" … … 724 736 end 725 737 726 if c = @html_tag_params[:class] || @params[:class] 727 @html_tag_params[:class] = "#{c} zazen" 728 else 729 @html_tag_params[:class] = 'zazen' 730 end 738 add_html_class('zazen') 739 731 740 unless @params[:or] 732 741 text = @params[:text] ? @params[:text].inspect : node_attribute('v_summary') … … 1975 1984 return nil 1976 1985 end 1977 1978 res = "#{node_name}.do_find(#{count.inspect}, \"#{sql_query}\", #{!uses_node_name} )"1986 node_class_param = klass == Node ? '' : ", #{klass}" 1987 res = "#{node_name}.do_find(#{count.inspect}, \"#{sql_query}\", #{!uses_node_name}#{node_class_param})" 1979 1988 if params[:else] 1980 1989 else_query, else_klass = build_finder_for(count, params[:else], {}) … … 2649 2658 end 2650 2659 2660 # Add a class name to the html_tag 2661 def add_html_class(class_name) 2662 if klass = @html_tag_params[:class] 2663 @html_tag_params[:class] = "#{class_name} #{klass}" 2664 else 2665 @html_tag_params[:class] = class_name 2666 end 2667 end 2668 2651 2669 def render_html_tag(text,*append) 2652 2670 append ||= [] trunk/lib/query_builder/lib/query_builder.rb
r1111 r1121 10 10 =end 11 11 class QueryBuilder 12 attr_reader :tables, :where, :errors, :join_tables, :distinct 13 @@main_table = 'objects' 12 attr_reader :tables, :where, :errors, :join_tables, :distinct, :final_parser 13 @@main_table = {} 14 @@main_class = {} 14 15 @@custom_queries = {} 15 16 16 17 class << self 17 18 def set_main_table(table_name) 18 @@main_table = table_name.to_s 19 @@main_table[self] = table_name.to_s 20 end 21 22 def set_main_class(main_class) 23 @@main_class[self] = main_class.to_s 19 24 end 20 25 … … 41 46 raise ArgumentError.new("invalid class for CustomQueries (#{klass})") 42 47 end 48 49 def new(*args) 50 obj = super 51 obj.final_parser 52 end 43 53 end 44 54 45 55 def initialize(query, opts = {}) 46 if query.kind_of?(Array) 47 @query = query[0] 48 if query.size > 1 49 alt_queries = query[1..-1].map {|q| self.class.new(q, opts.merge(:skip_after_parse => true))} 50 end 56 if opts[:pre_query] 57 init_with_pre_query(opts[:pre_query], opts[:elements]) 51 58 else 52 @query = query 53 end 54 55 56 @tables = [] 57 @join_tables = {} 58 @table_counter = {} 59 @where = [] 60 # list of tables that need to be added for filter clauses (should be added only once per part) 61 @needed_tables = {} 62 # list of tables that need to be added through a join (should be added only once per part) 63 @needed_join_tables = {} 64 65 @errors = [] 66 67 @main_table ||= 'objects' 68 69 @select = [] 70 71 @ignore_warnings = opts[:ignore_warnings] 72 73 @ref_date = opts[:ref_date] ? "'#{opts[:ref_date]}'" : 'now()' 74 75 if @query == nil || @query == '' 76 elements = [main_table] 77 else 78 elements = @query.split(' from ') 79 last_element = elements.last 80 last_element, offset = last_element.split(' offset ') 81 last_element, limit = last_element.split(' limit ') 82 last_element, order = last_element.split(' order by ') 83 elements[-1], group = last_element.split(' group by ') 84 end 85 86 if @@custom_queries[self.class] && 87 @@custom_queries[self.class][opts[:custom_query_group]] && 88 custom_query = @@custom_queries[self.class][opts[:custom_query_group]][extract_custom_query(elements)] 89 custom_query.each do |k,v| 90 instance_variable_set("@#{k}", prepare_custom_query_arguments(k.to_sym, v)) 91 end 92 # set table counters 93 @tables.each do |t| 94 base, as, tbl = t.split(' ') 95 @table_counter[base] ||= 0 96 @table_counter[base] += 1 if tbl 97 end 98 # parse filters 99 clause, filters = elements[-1].split(/\s+where\s+/) 100 101 parse_filters(filters) if filters 102 @order = parse_order_clause(order) if order 103 else 104 # In order to know the table names of the dependencies, we need to parse it backwards. 105 # We first find the closest elements, then the final ones. For example, "pages from project" we need 106 # project information before getting 'pages'. 107 elements.reverse! 108 109 elements.each_index do |i| 110 parse_part(elements[i], i == 0) # yes, is_last is first (parsing reverse) 111 end 112 @distinct ||= elements.size > 1 113 @select << "#{table}.*" 114 115 merge_alternate_queries(alt_queries) if alt_queries 116 117 @limit = parse_limit_clause(opts[:limit] || limit) 118 @offset ||= parse_offset_clause(offset) 119 120 121 @group = parse_group_clause(group) if group 122 @order = parse_order_clause(order || default_order_clause) 123 end 124 125 after_parse unless opts[:skip_after_parse] 126 @where.compact! 59 init_with_query(query, opts) 60 end 61 62 parse_elements(@elements) 127 63 end 128 64 … … 152 88 end 153 89 154 def result_class155 class_from_table(current_table)90 def main_class 91 Module.const_get(@@main_class[self.class]) 156 92 end 157 93 … … 163 99 164 100 def main_table 165 @@main_table 101 @@main_table[self.class] 166 102 end 167 103 168 104 def parse_part(part, is_last) 169 add_table(main_table)170 105 171 106 rest, context = part.split(' in ') 172 107 clause, filters = rest.split(/\s+where\s+/) 173 108 174 parse_filters(filters) if filters 175 parse_context(context, is_last) if context # .. in project 176 parse_relation(clause, context) 109 if @just_changed_class 110 # just changed class: parse filters && context 111 parse_filters(filters) if filters 112 @just_changed_class = false 113 return nil 114 elsif new_class = parse_change_class(clause, is_last) 115 if context 116 last_filter = @where.pop # pop/push is to keep queries in correct order (helps reading sql) 117 parse_context(context, true) 118 @where << last_filter 119 end 120 return new_class 121 else 122 add_table(main_table) 123 parse_filters(filters) if filters 124 parse_context(context, is_last) if context # .. in project 125 parse_relation(clause, context) 126 return nil 127 end 177 128 end 178 129 … … 194 145 elsif rest[0..0] == '(' 195 146 unless allowed.include?(:par_open) 196 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"147 @errors << clause_error(clause, rest, res) 197 148 return 198 149 end … … 202 153 elsif rest[0..0] == ')' 203 154 unless allowed.include?(:par_close) 204 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"155 @errors << clause_error(clause, rest, res) 205 156 return 206 157 end … … 209 160 par_count -= 1 210 161 if par_count < 0 211 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"162 @errors << clause_error(clause, rest, res) 212 163 return 213 164 end … … 215 166 elsif rest =~ /\A((>=|<=|<>|<|=|>)|((not\s+like|like|lt|le|eq|ne|ge|gt)\s+))/ 216 167 unless allowed.include?(:op) 217 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"168 @errors << clause_error(clause, rest, res) 218 169 return 219 170 end … … 225 176 elsif rest =~ /\A("|')([^\1]*?)\1/ 226 177 unless allowed.include?(:value) 227 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"178 @errors << clause_error(clause, rest, res) 228 179 return 229 180 end … … 233 184 elsif rest =~ /\A(\d+|[\w:]+)\s+(second|minute|hour|day|week|month|year)s?/ 234 185 unless allowed.include?(:value) 235 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"186 @errors << clause_error(clause, rest, res) 236 187 return 237 188 end … … 246 197 elsif rest =~ /\A(\d+)/ 247 198 unless allowed.include?(:value) 248 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"199 @errors << clause_error(clause, rest, res) 249 200 return 250 201 end … … 254 205 elsif rest =~ /\A(is\s+not\s+null|is\s+null)/ 255 206 unless allowed.include?(:bool_op) 256 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"207 @errors << clause_error(clause, rest, res) 257 208 return 258 209 end … … 262 213 elsif rest[0..7] == 'REF_DATE' 263 214 unless allowed.include?(:value) 264 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"215 @errors << clause_error(clause, rest, res) 265 216 return 266 217 end … … 270 221 elsif rest =~ /\A(\+|\-)/ 271 222 unless allowed.include?(:op) 272 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"223 @errors << clause_error(clause, rest, res) 273 224 return 274 225 end … … 278 229 elsif rest =~ /\A(and|or)/ 279 230 unless allowed.include?(:bool_op) 280 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"231 @errors << clause_error(clause, rest, res) 281 232 return 282 233 end … … 287 238 elsif rest =~ /\A[\w:]+/ 288 239 unless allowed.include?(:value) 289 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"240 @errors << clause_error(clause, rest, res) 290 241 return 291 242 end … … 299 250 allowed = after_value 300 251 else 301 @errors << "invalid clause #{clause.inspect} near #{res.inspect}"252 @errors << clause_error(clause, rest, res) 302 253 return 303 254 end … … 314 265 315 266 def parse_order_clause(order) 316 return nilunless order267 return @order unless order 317 268 res = [] 318 269 … … 338 289 339 290 def parse_group_clause(field) 340 return nilunless field291 return @group unless field 341 292 if fld = map_field(field, table, :group) 342 293 " GROUP BY #{fld}" … … 348 299 349 300 def parse_limit_clause(limit) 350 return nilunless limit301 return @limit unless limit 351 302 if limit.kind_of?(Fixnum) 352 303 " LIMIT #{limit}" … … 363 314 364 315 def parse_offset_clause(offset) 365 return nilunless offset316 return @offset unless offset 366 317 if !@limit 367 318 # TODO: raise error ? … … 456 407 457 408 alt_queries.each do |query| 409 next unless query.main_class == self.main_class # no mixed class target ! 458 410 @errors += query.errors unless @ignore_warnings 459 411 next unless query.valid? … … 502 454 end 503 455 456 # Map a field to be used inside a query 457 def field_or_param(fld, table_name = table, context = nil) 458 if fld =~ /^\d+$/ 459 return fld 460 elsif @select.join =~ / AS #{fld}/ 461 @select.each do |s| 462 if s =~ /\A(.*) AS #{fld}\Z/ 463 return context == :filter ? "(#{$1})" : fld 464 end 465 end 466 elsif table_name 467 map_field(fld, table_name, context) 468 else 469 map_parameter(fld) 470 end 471 end 472 504 473 # ******** Overwrite these ********** 505 474 def class_from_table(table_name) … … 507 476 end 508 477 478 def default_context_filter 479 raise NameError.new("default_context_filter not defined for class #{self.class}") 480 end 481 482 # Default sort order 483 def default_order_clause 484 nil 485 end 486 487 def after_parse 488 # do nothing 489 end 490 491 def parse_change_class(rel, is_last) 492 nil 493 end 494 495 def parse_relation(clause, context) 496 return nil 497 end 498 499 def context_filter_fields(clause, is_last = false) 500 nil 501 end 502 503 def parse_context(clause, is_last = false) 504 505 if fields = context_filter_fields(clause, is_last) 506 @where << "#{field_or_param(fields[0])} = #{field_or_param(fields[1], table(main_table,-1))}" if fields != :void 507 else 508 @errors << "invalid context '#{clause}'" 509 end 510 end 511 512 # Map a litteral value to be used inside a query 513 def map_literal(value) 514 value.inspect 515 end 516 517 518 # Overwrite this and take car to check for valid fields. 519 def map_field(fld, table_name, context = nil) 520 if fld == 'id' 521 "#{table_name}.#{fld}" 522 else 523 # TODO: error, raise / ignore ? 524 end 525 end 526 527 def map_parameter(fld) 528 fld.to_s.upcase 529 end 530 531 # ******** And maybe overwrite these ********** 509 532 def parse_custom_query_argument(key, value) 510 533 return nil unless value … … 524 547 end 525 548 526 def default_context_filter(clause) 527 nil 528 end 529 530 # Default sort order 531 def default_order_clause 532 nil 533 end 534 535 def after_parse 536 # do nothing 537 end 538 539 def parse_relation(clause, context) 540 return nil 541 end 542 543 def context_filter_fields(clause, is_last = false) 544 nil 545 end 546 547 def parse_context(clause, is_last = false) 548 549 if fields = context_filter_fields(clause, is_last) 550 @where << "#{field_or_param(fields[0])} = #{field_or_param(fields[1], table(main_table,-1))}" if fields != :void 551 else 552 @errors << "invalid context '#{clause}'" 553 end 554 end 555 556 # Map a litteral value to be used inside a query 557 def map_literal(value) 558 value.inspect 559 end 560 561 # Map a field to be used inside a query 562 def field_or_param(fld, table_name = table, context = nil) 563 if fld =~ /^\d+$/ 564 return fld 565 elsif @select.join =~ / AS #{fld}/ 566 @select.each do |s| 567 if s =~ /\A(.*) AS #{fld}\Z/ 568 return context == :filter ? "(#{$1})" : fld 569 end 570 end 571 elsif table_name 572 map_field(fld, table_name, context) 573 else 574 map_parameter(fld) 575 end 576 end 577 578 # Overwrite this and take car to check for valid fields. 579 def map_field(fld, table_name, context = nil) 580 if fld == 'id' 581 "#{table_name}.#{fld}" 582 else 583 # TODO: error, raise / ignore ? 584 end 585 end 586 587 def map_parameter(fld) 588 fld.to_s.upcase 549 private 550 551 def parse_elements(elements) 552 # "final_parser" is the parser who will respond to 'to_sql'. It might be a sub-parser for another class. 553 @final_parser = self 554 555 if @@custom_queries[self.class] && 556 @@custom_queries[self.class][@opts[:custom_query_group]] && 557 custom_query = @@custom_queries[self.class][@opts[:custom_query_group]][extract_custom_query(elements)] 558 custom_query.each do |k,v| 559 instance_variable_set("@#{k}", prepare_custom_query_arguments(k.to_sym, v)) 560 end 561 # set table counters 562 @tables.each do |t| 563 base, as, tbl = t.split(' ') 564 @table_counter[base] ||= 0 565 @table_counter[base] += 1 if tbl 566 end 567 # parse filters 568 clause, filters = elements[-1].split(/\s+where\s+/) 569 570 parse_filters(filters) if filters 571 @order = parse_order_clause(@offset_limit_order_group[:order]) 572 else 573 i, new_class = 0, nil 574 elements.each_index do |i| 575 break if new_class = parse_part(elements[i], i == 0) # yes, is_last is first (parsing reverse) 576 end 577 578 if new_class 579 # move to another parser class 580 @final_parser = new_class.new(nil, :pre_query => self, :elements => elements[i..-1]) 581 else 582 @distinct ||= elements.size > 1 583 @select << "#{table}.*" 584 585 merge_alternate_queries(@alt_queries) if @alt_queries 586 587 @limit = parse_limit_clause(@offset_limit_order_group[:limit]) 588 @offset = parse_offset_clause(@offset_limit_order_group[:offset]) 589 590 591 @group = parse_group_clause(@offset_limit_order_group[:group]) 592 @order = parse_order_clause(@offset_limit_order_group[:order] || default_order_clause) 593 end 594 end 595 596 if @final_parser == self 597 after_parse unless @opts[:skip_after_parse] 598 @where.compact! 599 end 600 end 601 602 def init_with_query(query, opts) 603 @opts = opts 604 605 if query.kind_of?(Array) 606 @query = query[0] 607 if query.size > 1 608 @alt_queries = query[1..-1].map {|q| self.class.new(q, opts.merge(:skip_after_parse => true))} 609 end 610 else 611 @query = query 612 end 613 614 615 @offset_limit_order_group = {} 616 if @query == nil || @query == '' 617 elements = [main_table] 618 else 619 elements = @query.split(' from ') 620 last_element = elements.last 621 last_element, @offset_limit_order_group[:offset] = last_element.split(' offset ') 622 last_element, @offset_limit_order_group[:limit] = last_element.split(' limit ') 623 last_element, @offset_limit_order_group[:order] = last_element.split(' order by ') 624 elements[-1], @offset_limit_order_group[:group] = last_element.split(' group by ') 625 end 626 627 @offset_limit_order_group[:limit] = opts[:limit] || @offset_limit_order_group[:limit] 628 # In order to know the table names of the dependencies, we need to parse it backwards. 629 # We first find the closest elements, then the final ones. For example, "pages from project" we need 630 # project information before getting 'pages'. 631 @elements = elements.reverse 632 633 @tables = [] 634 @join_tables = {} 635 @table_counter = {} 636 @where = [] 637 # list of tables that need to be added for filter clauses (should be added only once per part) 638 @needed_tables = {} 639 # list of tables that need to be added through a join (should be added only once per part) 640 @needed_join_tables = {} 641 642 @errors = [] 643 644 @main_table ||= 'objects' 645 646 @select = [] 647 648 @ignore_warnings = opts[:ignore_warnings] 649 650 @ref_date = opts[:ref_date] ? "'#{opts[:ref_date]}'" : 'now()' 651 end 652 653 def init_with_pre_query(pre_query, elements) 654 pre_query.instance_variables.each do |iv| 655 next if iv == '@query' || iv == '@final_parser' 656 instance_variable_set(iv, pre_query.instance_variable_get(iv)) 657 end 658 @just_changed_class = true 659 @elements = elements 660 end 661 662 def clause_error(clause, rest, res) 663 "invalid clause #{clause.inspect} near \"#{res[-2..-1]}#{rest[0..1]}\"" 589 664 end 590 665 end trunk/lib/query_builder/test/query_builder/errors.yml
r1042 r1121 43 43 bad_equation: 44 44 src: "objects where event_at > 2006-04-01'" 45 res: "invalid clause \"event_at > 2006-04-01'\" near \" '\""45 res: "invalid clause \"event_at > 2006-04-01'\" near \"01'\"" 46 46 47 47 bad_plus_plus: 48 48 src: "objects where 1 + 3 + + 5 > event_at" 49 res: "invalid clause \"1 + 3 + + 5 > event_at\" near \"+ 5 > event_at\""49 res: "invalid clause \"1 + 3 + + 5 > event_at\" near \"+ + \"" 50 50 trunk/lib/query_builder/test/query_builder/filters.yml
r1042 r1121 28 28 29 29 equation_in_filter: 30 src: "objects where event_at > DATE + custom_a months"30 src: "objects where event_at > REF_DATE + custom_a months" 31 31 res: "SELECT objects.* FROM objects WHERE objects.parent_id = ID AND objects.event_at > now() + INTERVAL objects.custom_a MONTH" 32 32 trunk/lib/query_builder/test/query_builder_test.rb
r1040 r1121 5 5 6 6 class TestQuery < QueryBuilder 7 set_main_table 'objects' 8 set_main_class 'Object' 9 7 10 load_custom_queries File.join(File.dirname(__FILE__), 'custom_queries') 8 11 … … 20 23 21 24 private 25 22 26 # Root filters (relations that can be solved without a join). Think 'in clause' (in self, in parent). 23 27 def context_filter_fields(clause, is_last = false) … … 68 72 end 69 73 74 def parse_change_class(rel, is_last) 75 case rel 76 when 'users' 77 parse_context(default_context_filter, true) if is_last 78 add_table('users') 79 @where << "#{table('users')}.node_id = #{field_or_param('id')}" 80 return TestUserQuery # class change 81 else 82 return nil 83 end 84 end 85 70 86 # Filters that need a join 71 87 def join_relation(rel, context) … … 98 114 end 99 115 116 class TestUser 117 end 118 119 class TestUserQuery < QueryBuilder 120 set_main_table 'users' 121 set_main_class 'TestUser' 122 123 # Default sort order 124 def default_order_clause 125 "name ASC, first_name ASC" 126 end 127 128 def default_context_filter 129 'self' 130 end 131 132 def parse_change_class(rel, is_last) 133 case rel 134 when 'objects' 135 parse_context(default_context_filter, true) if is_last 136 add_table('objects') 137 @where << "#{table('objects')}.id = #{field_or_param('node_id')}" 138 return TestUserQuery # class change 139 else 140 return nil 141 end 142 end 143 144 def parse_relation(clause, context) 145 return nil 146 end 147 148 def context_filter_fields(clause, is_last = false) 149 nil 150 end 151 152 def parse_context(clause, is_last = false) 153 154 if fields = context_filter_fields(clause, is_last) 155 @where << "#{field_or_param(fields[0])} = #{field_or_param(fields[1], table(main_table,-1))}" if fields != :void 156 else 157 @errors << "invalid context '#{clause}'" 158 end 159 end 160 161 # Map a litteral value to be used inside a query 162 def map_literal(value) 163 value.inspect 164 end 165 166 167 # Overwrite this and take car to check for valid fields. 168 def map_field(fld, table_name, context = nil) 169 if ['id', 'name', 'first_name', 'node_id'].include?(fld) 170 "#{table_name}.#{fld}" 171 else 172 # TODO: error, raise / ignore ? 173 end 174 end 175 176 def map_parameter(fld) 177 fld.to_s.upcase 178 end 179 end 180 100 181 class QueryTest < Test::Unit::TestCase 101 yaml_test :basic, :joins, :filters, :errors182 yaml_test 102 183 103 184 def parse(value, opts) 104 185 query = TestQuery.new(value, opts) 105 if res = query.to_sql 106 return res 186 187 (query.main_class != Object ? "#{query.main_class.to_s}: " : '') + if res = query.to_sql 188 res 107 189 else 108 returnquery.errors.join(", ")190 query.errors.join(", ") 109 191 end 110 192 end trunk/lib/yaml_test.rb
r1078 r1121 40 40 end 41 41 end 42 43 42 44 43 class_eval %Q{ trunk/public/javascripts/zena.js
r907 r1121 5 5 // preview content from another window. 6 6 Zena.editor_preview = function(url, element, value) { 7 new Ajax.Request(url, {asynchronous:true, evalScripts:true, parameters:{content: value }}); // $F() 7 var key = element.name; 8 var full_url = url + '&key=' + key.slice(5, key.length - 1); 9 new Ajax.Request(full_url, {asynchronous:true, evalScripts:true, parameters:{content: value }}); // $F() 8 10 } 9 11 trunk/test/helpers/application_helper_test.rb
r1084 r1121 178 178 def test_date_box 179 179 @node = secure!(Node) { nodes(:status) } 180 assert_match %r{ divclass="date_box".*img src="\/calendar\/iconCalendar.gif".*input id='datef.*' name='node\[updated_at\]' type='text' value='2006-04-11 00:00'}m, date_box('node', 'updated_at')180 assert_match %r{span class="date_box".*img src="\/calendar\/iconCalendar.gif".*input id='datef.*' name='node\[updated_at\]' type='text' value='2006-04-11 00:00'}m, date_box('node', 'updated_at') 181 181 end 182 182 trunk/test/helpers/node_query/relations.yml
r1111 r1121 30 30 sql: "SELECT nodes.* FROM nodes WHERE nodes.kpath LIKE 'N
