Class: Card::Query::CardSpec
Constant Summary collapse
- ATTRIBUTES =
{ :basic => %w{ name type_id content id key updater_id left_id right_id creator_id updater_id codename }, :relational => %w{ type part left right editor_of edited_by last_editor_of last_edited_by creator_of created_by member_of member }, :plus_relational => %w{ plus left_plus right_plus }, :ref_relational => %w{ refer_to referred_to_by link_to linked_to_by include included_by }, :conjunction => %w{ and or all any }, :special => %w{ found_by not sort match complete extension_type }, :ignore => %w{ prepend append view params vars size } }.inject({}) {|h,pair| pair[1].each {|v| h[v.to_sym]=pair[0] }; h }
- DEFAULT_ORDER_DIRS =
{ :update => "desc", :relevance => "desc" }
- CONJUNCTIONS =
{ :any=>:or, :in=>:or, :or=>:or, :all=>:and, :and=>:and }
Instance Attribute Summary collapse
-
#join_count ⇒ Object
Returns the value of attribute join_count.
-
#joins ⇒ Object
Returns the value of attribute joins.
-
#query ⇒ Object
readonly
Returns the value of attribute query.
-
#rawspec ⇒ Object
readonly
Returns the value of attribute rawspec.
-
#selfname ⇒ Object
readonly
Returns the value of attribute selfname.
-
#sql ⇒ Object
readonly
Returns the value of attribute sql.
Attributes inherited from Spec
Class Method Summary collapse
Instance Method Summary collapse
- #absolute_name(name) ⇒ Object
- #action_spec(field, linkfield, val) ⇒ Object
- #add_join(name, table, cardfield, otherfield, opts = {}) ⇒ Object
-
#and(val) ⇒ Object
(also: #all)
~~~~~~~ CONJUNCTION.
- #basic_conditions ⇒ Object
- #clean(query) ⇒ Object
- #clean_val(val) ⇒ Object
- #complete(val) ⇒ Object
- #conjunction(val) ⇒ Object
- #created_by(val) ⇒ Object
- #creator_of(val) ⇒ Object
- #current_conjunction ⇒ Object
- #edited_by(val) ⇒ Object
- #editor_of(val) ⇒ Object
- #extension_type(val) ⇒ Object
- #field(name) ⇒ Object
- #field_root(key) ⇒ Object
- #fields_to_sql ⇒ Object
- #found_by(val) ⇒ Object
- #hashify(s) ⇒ Object
- #id_from_spec(spec) ⇒ Object
-
#initialize(query) ⇒ CardSpec
constructor
A new instance of CardSpec.
- #junction(side, val) ⇒ Object
- #last_edited_by(val) ⇒ Object
- #last_editor_of(val) ⇒ Object
- #left(val) ⇒ Object
-
#left_plus(val) ⇒ Object
~~~~~~ PLUS RELATIONAL.
- #match(val) ⇒ Object
- #member(val) ⇒ Object
- #member_of(val) ⇒ Object
- #merge(s) ⇒ Object
- #not(val) ⇒ Object
- #or(val) ⇒ Object (also: #any)
- #part(val) ⇒ Object
- #permission_conditions ⇒ Object
- #plus(val) ⇒ Object
- #ready_to_sqlize(spec) ⇒ Object
- #refspec(key, val) ⇒ Object
- #relate(subcond, key, val, method) ⇒ Object
- #restrict(id_field, val, opts = {}) ⇒ Object
- #restrict_by_join(id_field, val, opts = {}) ⇒ Object
- #right(val) ⇒ Object
- #right_plus(val) ⇒ Object
- #root ⇒ Object
- #sort(val) ⇒ Object
- #sort_field(key, as, dir) ⇒ Object
- #sort_to_sql ⇒ Object
- #subcondition(val, args = {}) ⇒ Object
- #table_alias ⇒ Object
- #to_sql(*args) ⇒ Object
- #translate_to_attributes(spec) ⇒ Object
-
#type(val) ⇒ Object
~~~~~~ RELATIONAL.
Methods inherited from Spec
#cast_type, #match_prep, #quote, #safe_sql
Constructor Details
#initialize(query) ⇒ CardSpec
Returns a new instance of CardSpec.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/card/query/card_spec.rb', line 29 def initialize query @mods = MODIFIERS.clone @spec, @joins = {}, {} @selfname, @parent = '', nil @sql = SqlStatement.new @query = query.clone @query.merge! @query.delete(:params) if @query[:params] @vars = @query.delete(:vars) || {} @vars.symbolize_keys! @query = clean(@query) @rawspec = @query.deep_clone self end |
Instance Attribute Details
#join_count ⇒ Object
Returns the value of attribute join_count.
20 21 22 |
# File 'lib/card/query/card_spec.rb', line 20 def join_count @join_count end |
#joins ⇒ Object
Returns the value of attribute joins.
20 21 22 |
# File 'lib/card/query/card_spec.rb', line 20 def joins @joins end |
#query ⇒ Object (readonly)
Returns the value of attribute query.
19 20 21 |
# File 'lib/card/query/card_spec.rb', line 19 def query @query end |
#rawspec ⇒ Object (readonly)
Returns the value of attribute rawspec.
19 20 21 |
# File 'lib/card/query/card_spec.rb', line 19 def rawspec @rawspec end |
#selfname ⇒ Object (readonly)
Returns the value of attribute selfname.
19 20 21 |
# File 'lib/card/query/card_spec.rb', line 19 def selfname @selfname end |
#sql ⇒ Object (readonly)
Returns the value of attribute sql.
19 20 21 |
# File 'lib/card/query/card_spec.rb', line 19 def sql @sql end |
Class Method Details
.build(query) ⇒ Object
23 24 25 26 |
# File 'lib/card/query/card_spec.rb', line 23 def build query cardspec = self.new query cardspec.merge cardspec.rawspec end |
Instance Method Details
#absolute_name(name) ⇒ Object
80 81 82 |
# File 'lib/card/query/card_spec.rb', line 80 def absolute_name name name =~ /\b_/ ? name.to_name.to_absolute(root.selfname) : name end |
#action_spec(field, linkfield, val) ⇒ Object
403 404 405 406 407 408 |
# File 'lib/card/query/card_spec.rb', line 403 def action_spec(field, linkfield, val) card_select = CardSpec.build(:_parent=>self, :return=>'id').merge(val).to_sql sql = "(SELECT DISTINCT #{field} AS join_card_id FROM card_acts INNER JOIN card_actions ON card_acts.id = card_act_id " sql += " JOIN (#{card_select}) AS ss ON #{linkfield}=ss.id AND (draft is not true))" add_join :ac, sql, :id, :join_card_id end |
#add_join(name, table, cardfield, otherfield, opts = {}) ⇒ Object
371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/card/query/card_spec.rb', line 371 def add_join(name, table, cardfield, otherfield, opts={}) root.join_count = root.join_count.to_i + 1 join_alias = "#{name}_#{root.join_count}" on = "#{table_alias}.#{cardfield} = #{join_alias}.#{otherfield}" #is_subselect = !table.is_a?( Symbol ) if @mods[:conj] == 'or' #and is_subselect opts[:side] ||= 'LEFT' merge field(:cond) => SqlCond.new(on) end @joins[join_alias] = ["\n ", opts[:side], 'JOIN', table, 'AS', join_alias, 'ON', on, "\n"].compact.join ' ' join_alias end |
#and(val) ⇒ Object Also known as: all
~~~~~~~ CONJUNCTION
255 256 257 |
# File 'lib/card/query/card_spec.rb', line 255 def and val subcondition val end |
#basic_conditions ⇒ Object
466 467 468 |
# File 'lib/card/query/card_spec.rb', line 466 def basic_conditions @spec.map { |key, val| val.to_sql field_root(key) }.compact.join " #{ current_conjunction } " end |
#clean(query) ⇒ Object
51 52 53 54 55 56 57 58 59 |
# File 'lib/card/query/card_spec.rb', line 51 def clean query query = query.symbolize_keys if s = query.delete(:context) then @selfname = s end if p = query.delete(:_parent) then @parent = p end query.each do |key,val| query[key] = clean_val val end query end |
#clean_val(val) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/card/query/card_spec.rb', line 61 def clean_val val case val when String if val =~ /^\$(\w+)$/ val = @vars[$1.to_sym].to_s.strip end absolute_name val when Card::Name ; clean_val val.s when Hash ; clean val when Array ; val.map { |v| clean_val v } when Integer, Float, Symbol ; val else ; raise BadQuery, "unknown WQL value type: #{val.class}" end end |
#complete(val) ⇒ Object
343 344 345 346 |
# File 'lib/card/query/card_spec.rb', line 343 def complete(val) no_plus_card = (val=~/\+/ ? '' : "and right_id is null") #FIXME -- this should really be more nuanced -- it breaks down after one plus merge field(:cond) => SqlCond.new(" lower(name) LIKE lower(#{quote(val.to_s+'%')}) #{no_plus_card}") end |
#conjunction(val) ⇒ Object
168 169 170 171 172 |
# File 'lib/card/query/card_spec.rb', line 168 def conjunction val if [String, Symbol].member? val.class CONJUNCTIONS[val.to_sym] end end |
#created_by(val) ⇒ Object
220 221 222 |
# File 'lib/card/query/card_spec.rb', line 220 def created_by val restrict :creator_id, val end |
#creator_of(val) ⇒ Object
216 217 218 |
# File 'lib/card/query/card_spec.rb', line 216 def creator_of val restrict_by_join :id, val, :return=>'creator_id' end |
#current_conjunction ⇒ Object
470 471 472 |
# File 'lib/card/query/card_spec.rb', line 470 def current_conjunction @mods[:conj].blank? ? :and : @mods[:conj] end |
#edited_by(val) ⇒ Object
204 205 206 |
# File 'lib/card/query/card_spec.rb', line 204 def edited_by val action_spec "card_actions.card_id", :actor_id, val end |
#editor_of(val) ⇒ Object
200 201 202 |
# File 'lib/card/query/card_spec.rb', line 200 def editor_of val action_spec :actor_id, "card_actions.card_id", val end |
#extension_type(val) ⇒ Object
348 349 350 351 352 |
# File 'lib/card/query/card_spec.rb', line 348 def extension_type val # DEPRECATED LONG AGO!!! Rails.logger.info "using DEPRECATED extension_type in WQL" merge field(:right_plus) => AccountID end |
#field(name) ⇒ Object
385 386 387 388 389 390 |
# File 'lib/card/query/card_spec.rb', line 385 def field name @fields ||= {} @fields[name] ||= 0 @fields[name] += 1 "#{ name }_#{ @fields[name] }" end |
#field_root(key) ⇒ Object
392 393 394 |
# File 'lib/card/query/card_spec.rb', line 392 def field_root key key.to_s.gsub /\_\d+/, '' end |
#fields_to_sql ⇒ Object
482 483 484 485 486 487 488 489 490 491 492 |
# File 'lib/card/query/card_spec.rb', line 482 def fields_to_sql field = @mods[:return] case (field.blank? ? :card : field.to_sym) when :raw; "#{table_alias}.*" when :card; "#{table_alias}.name" when :count; "coalesce(count(*),0) as count" when :content; "#{table_alias}.db_content" else ATTRIBUTES[field.to_sym]==:basic ? "#{table_alias}.#{field}" : safe_sql(field) end end |
#found_by(val) ⇒ Object
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/card/query/card_spec.rb', line 268 def found_by val cards = if Hash===val Query.new(val).run else Array.wrap(val).map do |v| Card.fetch absolute_name(val), :new=>{} end end cards.each do |c| unless c && [SearchTypeID,SetID].include?(c.type_id) raise BadQuery, %{"found_by" value needs to be valid Search, but #{c.name} is a #{c.type_name}} end restrict_by_join :id, CardSpec.new(c.get_spec).rawspec end end |
#hashify(s) ⇒ Object
98 99 100 101 102 103 104 105 |
# File 'lib/card/query/card_spec.rb', line 98 def hashify s case s when String; { :key => s.to_name.key } when Integer; { :id => s } when Hash; s else; raise BadQuery, "Invalid cardspec args #{s.inspect}" end end |
#id_from_spec(spec) ⇒ Object
410 411 412 413 414 415 |
# File 'lib/card/query/card_spec.rb', line 410 def id_from_spec spec case spec when Integer ; spec when String ; Card.fetch_id(spec) end end |
#junction(side, val) ⇒ Object
247 248 249 250 |
# File 'lib/card/query/card_spec.rb', line 247 def junction side, val part_spec, junction_spec = val.is_a?(Array) ? val : [ val, {} ] restrict_by_join :id, junction_spec, side=>part_spec, :return=>"#{ side==:left ? :right : :left}_id" end |
#last_edited_by(val) ⇒ Object
212 213 214 |
# File 'lib/card/query/card_spec.rb', line 212 def last_edited_by val restrict :updater_id, val end |
#last_editor_of(val) ⇒ Object
208 209 210 |
# File 'lib/card/query/card_spec.rb', line 208 def last_editor_of val restrict_by_join :id, val, :return=>'updater_id' end |
#left(val) ⇒ Object
192 193 194 |
# File 'lib/card/query/card_spec.rb', line 192 def left val restrict :left_id, val end |
#left_plus(val) ⇒ Object
~~~~~~ PLUS RELATIONAL
235 236 237 |
# File 'lib/card/query/card_spec.rb', line 235 def left_plus val junction :left, val end |
#match(val) ⇒ Object
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/card/query/card_spec.rb', line 323 def match(val) cxn, val = match_prep val val.gsub! /[^#{Card::Name::OK4KEY_RE}]+/, ' ' return nil if val.strip.empty? cond = begin val_list = val.split(/\s+/).map do |v| name_or_content = ["replace(#{self.table_alias}.name,'+',' ')","#{self.table_alias}.db_content"].map do |field| %{#{field} #{ cxn.match quote("[[:<:]]#{v}[[:>:]]") }} end "(#{name_or_content.join ' OR '})" end "(#{val_list.join ' AND '})" end merge field(:cond)=>SqlCond.new(cond) end |
#member(val) ⇒ Object
228 229 230 |
# File 'lib/card/query/card_spec.rb', line 228 def member val merge field(:referred_to_by) => {:left=>val, :right=>RolesID } end |
#member_of(val) ⇒ Object
224 225 226 |
# File 'lib/card/query/card_spec.rb', line 224 def member_of val merge field(:right_plus) => [RolesID, {:refer_to=>val}] end |
#merge(s) ⇒ Object
90 91 92 93 94 95 96 |
# File 'lib/card/query/card_spec.rb', line 90 def merge s s = hashify s translate_to_attributes s ready_to_sqlize s @spec.merge! s self end |
#not(val) ⇒ Object
286 287 288 289 290 |
# File 'lib/card/query/card_spec.rb', line 286 def not val subselect = CardSpec.build(:return=>:id, :_parent=>self).merge(val).to_sql join_alias = add_join :not, subselect, :id, :id, :side=>'LEFT' merge field(:cond) => SqlCond.new("#{join_alias}.id is null") end |
#or(val) ⇒ Object Also known as: any
260 261 262 |
# File 'lib/card/query/card_spec.rb', line 260 def or val subcondition val, :conj=>:or end |
#part(val) ⇒ Object
186 187 188 189 |
# File 'lib/card/query/card_spec.rb', line 186 def part val right = Integer===val ? val : val.clone subcondition :left=>val, :right=>right, :conj=>:or end |
#permission_conditions ⇒ Object
474 475 476 477 478 479 480 |
# File 'lib/card/query/card_spec.rb', line 474 def unless Auth.always_ok? #or ( Card::Query.root_perms_only && !root? ) read_rules = Auth.as_card.read_rules read_rule_list = read_rules.nil? ? 1 : read_rules.join(',') "(#{table_alias}.read_rule_id IN (#{ read_rule_list }))" end end |
#plus(val) ⇒ Object
243 244 245 |
# File 'lib/card/query/card_spec.rb', line 243 def plus val any( { :left_plus=>val, :right_plus=>val.deep_clone } ) end |
#ready_to_sqlize(spec) ⇒ Object
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/card/query/card_spec.rb', line 125 def ready_to_sqlize spec spec.each do |key,val| keyroot = field_root(key).to_sym if keyroot==:cond # internal SQL cond (already ready) elsif ATTRIBUTES[keyroot] == :basic # sqlize knows how to handle these keys; just process value spec[key] = ValueSpec.new(val, self) else # keys need additional processing val = spec.delete key is_array = Array===val case ATTRIBUTES[keyroot] when :ignore #noop when :relational, :special, :conjunction ; relate is_array, keyroot, val, :send when :ref_relational ; relate is_array, keyroot, val, :refspec when :plus_relational # Arrays can have multiple interpretations for these, so we have to look closer... subcond = is_array && ( Array===val.first || conjunction(val.first) ) relate subcond, keyroot, val, :send else ; raise BadQuery, "Invalid attribute #{key}" end end end end |
#refspec(key, val) ⇒ Object
163 164 165 |
# File 'lib/card/query/card_spec.rb', line 163 def refspec key, val add_join :ref, RefSpec.new( key, val, self ).to_sql, :id, :ref_id end |
#relate(subcond, key, val, method) ⇒ Object
150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/card/query/card_spec.rb', line 150 def relate subcond, key, val, method if subcond conj = conjunction( val.first ) ? conjunction( val.shift ) : :and if conj == current_conjunction # same conjunction as container, no need for subcondition val.each { |v| send method, key, v } else send conj, val.inject({}) { |h,v| h[field key] = v; h } # subcondition end else send method, key, val end end |
#restrict(id_field, val, opts = {}) ⇒ Object
417 418 419 420 421 422 423 |
# File 'lib/card/query/card_spec.rb', line 417 def restrict id_field, val, opts={} if id = id_from_spec(val) merge field(id_field) => id else restrict_by_join id_field, val, opts end end |
#restrict_by_join(id_field, val, opts = {}) ⇒ Object
425 426 427 428 429 |
# File 'lib/card/query/card_spec.rb', line 425 def restrict_by_join id_field, val, opts={} opts.reverse_merge!(:return=>:id, :_parent=>self) subselect = CardSpec.build(opts).merge(val).to_sql add_join "card_#{id_field}", subselect, id_field, opts[:return] end |
#right(val) ⇒ Object
196 197 198 |
# File 'lib/card/query/card_spec.rb', line 196 def right val restrict :right_id, val end |
#right_plus(val) ⇒ Object
239 240 241 |
# File 'lib/card/query/card_spec.rb', line 239 def right_plus val junction :right, val end |
#root ⇒ Object
76 77 78 |
# File 'lib/card/query/card_spec.rb', line 76 def root @parent ? @parent.root : self end |
#sort(val) ⇒ Object
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/card/query/card_spec.rb', line 292 def sort val return nil if @parent val[:return] = val[:return] ? safe_sql(val[:return]) : 'db_content' item = val.delete(:item) || 'left' if val[:return] == 'count' cs_args = { :return=>'count', :group=>'sort_join_field', :_parent=>self } @mods[:sort] = "coalesce(#{@mods[:sort]},0)" case item when 'referred_to' join_field = 'id' cs = CardSpec.build cs_args.merge( field(:cond)=>SqlCond.new("referer_id in #{CardSpec.build( val.merge(:return=>'id')).to_sql}") ) cs.add_join :wr, :card_references, :id, :referee_id else raise BadQuery, "count with item: #{item} not yet implemented" end else join_field = case item when 'left' ; 'left_id' when 'right' ; 'right_id' else ; raise BadQuery, "sort item: #{item} not yet implemented" end cs = CardSpec.build(val) end cs.sql.fields << "#{cs.table_alias}.#{join_field} as sort_join_field" join_table = add_join :sort, cs.to_sql, :id, :sort_join_field, :side=>'LEFT' @mods[:sort] = "#{join_table}.#{val[:return]}" end |
#sort_field(key, as, dir) ⇒ Object
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 |
# File 'lib/card/query/card_spec.rb', line 508 def sort_field key, as, dir order_field = case key when "id"; "#{table_alias}.id" when "update"; "#{table_alias}.updated_at" when "create"; "#{table_alias}.created_at" when /^(name|alpha)$/; "LOWER( #{table_alias}.key )" when 'content'; "#{table_alias}.db_content" when "relevance"; "#{table_alias}.updated_at" #deprecated else safe_sql(key) end order_field = "CAST(#{order_field} AS #{cast_type(as)})" if as "#{order_field} #{dir}" end |
#sort_to_sql ⇒ Object
494 495 496 497 498 499 500 501 502 503 504 505 506 |
# File 'lib/card/query/card_spec.rb', line 494 def sort_to_sql #fail "order_key = #{@mods[:sort]}, class = #{order_key.class}" return nil if @parent or @mods[:return]=='count' #FIXME - extend to all root-only clauses order_key ||= @mods[:sort].blank? ? "update" : @mods[:sort] order_directives = [order_key].flatten.map do |key| dir = @mods[:dir].blank? ? (DEFAULT_ORDER_DIRS[key.to_sym]||'asc') : safe_sql(@mods[:dir]) #wonky sort_field key, @mods[:sort_as], dir end.join ', ' "ORDER BY #{order_directives}" end |
#subcondition(val, args = {}) ⇒ Object
396 397 398 399 400 401 |
# File 'lib/card/query/card_spec.rb', line 396 def subcondition(val, args={}) args = { :return=>:condition, :_parent=>self }.merge(args) cardspec = CardSpec.build( args ) merge field(:cond) => cardspec.merge(val) self.joins.merge! cardspec.joins end |
#table_alias ⇒ Object
360 361 362 363 364 365 366 367 368 369 |
# File 'lib/card/query/card_spec.rb', line 360 def table_alias case when @mods[:return]=='condition' @parent ? @parent.table_alias : "t" when @parent @parent.table_alias + "x" else "t" end end |
#to_sql(*args) ⇒ Object
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 |
# File 'lib/card/query/card_spec.rb', line 436 def to_sql *args sql.conditions << basic_conditions if @mods[:return]=='condition' conds = sql.conditions.last return conds.blank? ? nil : "(#{conds})" end if pconds = sql.conditions << pconds end sql.fields.unshift fields_to_sql sql.order = sort_to_sql # has side effects! sql.tables = "cards #{table_alias}" sql.joins += @joins.values sql.conditions << "#{table_alias}.trash is false" sql.group = "GROUP BY #{safe_sql(@mods[:group])}" if !@mods[:group].blank? unless @parent or @mods[:return]=='count' if @mods[:limit].to_i > 0 sql.limit = "LIMIT #{ @mods[:limit ].to_i }" sql.offset = "OFFSET #{ @mods[:offset].to_i }" if !@mods[:offset].blank? end end sql.to_s end |
#translate_to_attributes(spec) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/card/query/card_spec.rb', line 107 def translate_to_attributes spec content = nil spec.each do |key,val| if key == :_parent @parent = spec.delete(key) elsif OPERATORS.has_key?(key.to_s) && !ATTRIBUTES[key] spec.delete(key) content = [key,val] elsif MODIFIERS.has_key?(key) next if spec[key].is_a? Hash val = spec.delete key @mods[key] = Array === val ? val : val.to_s end end spec[:content] = content if content end |
#type(val) ⇒ Object
~~~~~~ RELATIONAL
182 183 184 |
# File 'lib/card/query/card_spec.rb', line 182 def type val restrict :type_id, val end |