Class: Card::Query
- Includes:
- Attributes, Clause
- Defined in:
- lib/card/query.rb,
lib/card/query/join.rb,
lib/card/query/value.rb,
lib/card/query/reference.rb,
lib/card/query/attributes.rb,
lib/card/query/sql_statement.rb
Overview
Card::Query is for finding implicit lists (or counts of lists) of cards.
Search and Set cards use Card::Query to query the database, and it’s also frequently used directly in code.
Query “statements” (objects, really) are made in WQL (Wagn Query Language). Because WQL is used by Wagneers, the primary language documentation is on wagn.org. (wagn.org/WQL_Syntax). Note that the examples there are in JSON, like Search card content, but statements in Card::Query are in ruby form.
In Wagn’s current form, Card::Query generates and executes SQL statements. However, the SQL generation is largely (not yet fully) separated from the WQL statement interpretation.
The most common way to use Card::Query is as follows:
list_of_cards = Card::Query.run(statement)
This is equivalent to:
query = Card::Query.new(statement)
list_of_cards = query.run
Upon initiation, the query is interpreted, and the following key objects are populated:
-
@join - an Array of Card::Query::Join objects
-
@conditions - an Array of conditions
-
@mod - a Hash of other query-altering keys
-
@subqueries - a list of other queries nested within this one
Each condition is either a SQL-ready string (boo) or an Array in this form:
[ field_string_or_sym, Card::Value::Query object ]
Defined Under Namespace
Modules: Attributes, Clause Classes: Join, Reference, SqlStatement, Value
Constant Summary collapse
- ATTRIBUTES =
{ basic: %w( id name key type_id content left_id right_id creator_id updater_id codename read_rule_id ), 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 }
- CONJUNCTIONS =
{ any: :or, in: :or, or: :or, all: :and, and: :and }.freeze
- MODIFIERS =
%w( conj return sort sort_as group dir limit offset ) .inject({}) { |h, v| h[v.to_sym] = nil; h }
- OPERATORS =
%w( != = =~ < > in ~ ).inject({}) { |h, v| h[v] = v; h }.merge({ eq: '=', gt: '>', lt: '<', match: '~', ne: '!=', 'not in' => nil }.stringify_keys)
- DEFAULT_ORDER_DIRS =
{ update: 'desc', relevance: 'desc' }.freeze
Constants included from Attributes
Attributes::SORT_JOIN_TO_ITEM_MAP
Instance Attribute Summary collapse
-
#comment ⇒ Object
readonly
Returns the value of attribute comment.
-
#conditions ⇒ Object
readonly
Returns the value of attribute conditions.
-
#conditions_on_join ⇒ Object
Returns the value of attribute conditions_on_join.
-
#joins ⇒ Object
Returns the value of attribute joins.
-
#mods ⇒ Object
readonly
Returns the value of attribute mods.
-
#statement ⇒ Object
readonly
Returns the value of attribute statement.
-
#subqueries ⇒ Object
readonly
Returns the value of attribute subqueries.
-
#superquery ⇒ Object
readonly
Returns the value of attribute superquery.
-
#table_seq ⇒ Object
Returns the value of attribute table_seq.
-
#unjoined ⇒ Object
Returns the value of attribute unjoined.
Class Method Summary collapse
-
.run(statement, comment = nil) ⇒ Object
Query Execution By default a query returns card objects.
Instance Method Summary collapse
- #add_condition(*args) ⇒ Object
- #all_joins ⇒ Object
- #clause_to_hash(clause) ⇒ Object
- #context ⇒ Object
- #current_conjunction ⇒ Object
- #default_comment ⇒ Object
-
#get_results(retrn) ⇒ Object
Integer for :count, otherwise Array of Strings or Integers.
-
#initialize(statement, comment = nil) ⇒ Query
constructor
A new instance of Query.
-
#interpret(clause) ⇒ Object
normalize and extract meaning from a clause.
- #interpret_attributes(key, val) ⇒ Object
- #interpret_by_key(clause) ⇒ Object
- #normalize_clause(clause) ⇒ Object
- #normalize_string_value(val) ⇒ Object
- #normalize_value(val) ⇒ Object
- #relate(key, val, opts = {}) ⇒ Object
- #relate_compound(key, val) ⇒ Object
-
#root ⇒ Object
Query Hierarchy @root, @subqueries, and @superquery are used to track a hierarchy of query objects.
-
#run ⇒ Object
run the current query.
- #run_sql ⇒ Object
- #sql ⇒ Object
- #subquery(opts = {}) ⇒ Object
Methods included from Attributes
#all, #any, #complete, #conjoin, #conjunction, #created_by, #creator_of, #edited_by, #editor_of, #extension_type, #found_by, #found_by_cards, #id_from_val, #join_cards, #join_references, #junction, #last_edited_by, #last_editor_of, #left, #left_plus, #match, #member, #member_of, #not, #part, #plus, #restrict, #restrict_reference, #right, #right_plus, #sort, #sort_by_count, #table_alias, #table_id, #tick_table_seq!, #type
Methods included from Clause
#match_prep, #quote, #safe_sql
Constructor Details
#initialize(statement, comment = nil) ⇒ Query
Returns a new instance of Query.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/card/query.rb', line 77 def initialize statement, comment=nil @subqueries = [] @conditions = [] @joins = [] @mods = {} @statement = statement.clone @context = @statement.delete(:context) || nil @unjoined = @statement.delete(:unjoined) || nil @superquery = @statement.delete(:superquery) || nil @vars = @statement.delete(:vars) || {} @vars.symbolize_keys! @comment = comment || default_comment interpret @statement self end |
Instance Attribute Details
#comment ⇒ Object (readonly)
Returns the value of attribute comment.
73 74 75 |
# File 'lib/card/query.rb', line 73 def comment @comment end |
#conditions ⇒ Object (readonly)
Returns the value of attribute conditions.
73 74 75 |
# File 'lib/card/query.rb', line 73 def conditions @conditions end |
#conditions_on_join ⇒ Object
Returns the value of attribute conditions_on_join.
75 76 77 |
# File 'lib/card/query.rb', line 75 def conditions_on_join @conditions_on_join end |
#joins ⇒ Object
Returns the value of attribute joins.
75 76 77 |
# File 'lib/card/query.rb', line 75 def joins @joins end |
#mods ⇒ Object (readonly)
Returns the value of attribute mods.
73 74 75 |
# File 'lib/card/query.rb', line 73 def mods @mods end |
#statement ⇒ Object (readonly)
Returns the value of attribute statement.
73 74 75 |
# File 'lib/card/query.rb', line 73 def statement @statement end |
#subqueries ⇒ Object (readonly)
Returns the value of attribute subqueries.
73 74 75 |
# File 'lib/card/query.rb', line 73 def subqueries @subqueries end |
#superquery ⇒ Object (readonly)
Returns the value of attribute superquery.
73 74 75 |
# File 'lib/card/query.rb', line 73 def superquery @superquery end |
#table_seq ⇒ Object
Returns the value of attribute table_seq.
75 76 77 |
# File 'lib/card/query.rb', line 75 def table_seq @table_seq end |
#unjoined ⇒ Object
Returns the value of attribute unjoined.
75 76 77 |
# File 'lib/card/query.rb', line 75 def unjoined @unjoined end |
Class Method Details
.run(statement, comment = nil) ⇒ Object
Query Execution By default a query returns card objects. This is accomplished by returning a card identifier from SQL and then hooking into our caching system (see Card::Fetch)
106 107 108 |
# File 'lib/card/query.rb', line 106 def self.run statement, comment=nil new(statement, comment).run end |
Instance Method Details
#add_condition(*args) ⇒ Object
236 237 238 239 240 241 242 243 |
# File 'lib/card/query.rb', line 236 def add_condition *args @conditions << if args.size > 1 [args.shift, Value.new(args.shift, self)] else args[0] end end |
#all_joins ⇒ Object
286 287 288 289 |
# File 'lib/card/query.rb', line 286 def all_joins @all_joins ||= (joins + subqueries.select(&:unjoined).map(&:all_joins)).flatten end |
#clause_to_hash(clause) ⇒ Object
182 183 184 185 186 187 188 189 |
# File 'lib/card/query.rb', line 182 def clause_to_hash clause case clause when Hash then clause when String then { key: clause.to_name.key } when Integer then { id: clause } else raise BadQuery, "Invalid query args #{clause.inspect}" end end |
#context ⇒ Object
211 212 213 214 215 216 217 |
# File 'lib/card/query.rb', line 211 def context if !@context.nil? @context else @context = @superquery ? @superquery.context : '' end end |
#current_conjunction ⇒ Object
282 283 284 |
# File 'lib/card/query.rb', line 282 def current_conjunction @mods[:conj].blank? ? :and : @mods[:conj] end |
#default_comment ⇒ Object
96 97 98 99 |
# File 'lib/card/query.rb', line 96 def default_comment return if @superquery || !Card.config.sql_comments statement.to_s end |
#get_results(retrn) ⇒ Object
Returns Integer for :count, otherwise Array of Strings or Integers.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/card/query.rb', line 124 def get_results retrn rows = run_sql if retrn == 'name' && (statement[:prepend] || statement[:append]) rows.map do |row| [statement[:prepend], row['name'], statement[:append]].compact * '+' end else case retrn when 'count' then rows.first['count'].to_i when 'raw' then rows when /id$/ then rows.map { |row| row[retrn].to_i } else rows.map { |row| row[retrn] } end end end |
#interpret(clause) ⇒ Object
normalize and extract meaning from a clause
169 170 171 |
# File 'lib/card/query.rb', line 169 def interpret clause interpret_by_key normalize_clause(clause) end |
#interpret_attributes(key, val) ⇒ Object
245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/card/query.rb', line 245 def interpret_attributes key, val case ATTRIBUTES[key] when :basic then add_condition key, val when :conjunction then send key, val when :relational then relate key, val when :special then relate key, val when :ref_relational then relate key, val, method: :join_references when :plus_relational then relate_compound key, val when :ignore then # noop else raise BadQuery, "Invalid attribute #{key}" end end |
#interpret_by_key(clause) ⇒ Object
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/card/query.rb', line 219 def interpret_by_key clause clause.each do |key, val| case when OPERATORS.key?(key.to_s) && !ATTRIBUTES[key] # eg "match" is both operator and attribute; # interpret as attribute when "match" is key interpret content: [key, val] when MODIFIERS.key?(key) && !clause[key].is_a?(Hash) # eg when "sort" is hash, it can have subqueries # and must be interpreted like an attribute @mods[key] = val.is_a?(Array) ? val : val.to_s else interpret_attributes key, val end end end |
#normalize_clause(clause) ⇒ Object
173 174 175 176 177 178 179 180 |
# File 'lib/card/query.rb', line 173 def normalize_clause clause clause = clause_to_hash clause clause.symbolize_keys! clause.each do |key, val| clause[key] = normalize_value val end clause end |
#normalize_string_value(val) ⇒ Object
200 201 202 203 204 205 206 207 208 209 |
# File 'lib/card/query.rb', line 200 def normalize_string_value val case val.to_s when /^\$(\w+)$/ # replace from @vars @vars[Regexp.last_match[1].to_sym].to_s.strip when /\b_/ # absolutize based on @context val.to_name.to_absolute(context) else val end end |
#normalize_value(val) ⇒ Object
191 192 193 194 195 196 197 198 |
# File 'lib/card/query.rb', line 191 def normalize_value val case val when Integer, Float, Symbol, Hash then val when String, SmartName then normalize_string_value val when Array then val.map { |v| normalize_value v } else raise BadQuery, "unknown WQL value type: #{val.class}" end end |
#relate(key, val, opts = {}) ⇒ Object
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/card/query.rb', line 265 def relate key, val, opts={} multiple = opts[:multiple].nil? ? val.is_a?(Array) : opts[:multiple] method = opts[:method] || :send if multiple 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.map { |v| { key => v } } end else send method, key, val end end |
#relate_compound(key, val) ⇒ Object
258 259 260 261 262 263 |
# File 'lib/card/query.rb', line 258 def relate_compound key, val has_multiple_values = val.is_a?(Array) && (val.first.is_a?(Array) || conjunction(val.first).present?) relate key, val, multiple: has_multiple_values end |
#root ⇒ Object
Query Hierarchy @root, @subqueries, and @superquery are used to track a hierarchy of query objects. This nesting allows to find, for example, cards that link to cards that link to cards.…
155 156 157 |
# File 'lib/card/query.rb', line 155 def root @root ||= @superquery ? @superquery.root : self end |
#run ⇒ Object
run the current query
112 113 114 115 116 117 118 119 120 121 |
# File 'lib/card/query.rb', line 112 def run retrn = statement[:return].present? ? statement[:return].to_s : 'card' if retrn == 'card' get_results('name').map do |name| Card.fetch name, new: {} end else get_results retrn end end |
#run_sql ⇒ Object
140 141 142 143 144 |
# File 'lib/card/query.rb', line 140 def run_sql # puts "\nstatement = #{@statement}" # puts "sql = #{sql}" ActiveRecord::Base.connection.select_all(sql) end |
#sql ⇒ Object
146 147 148 |
# File 'lib/card/query.rb', line 146 def sql @sql ||= SqlStatement.new(self).build.to_s end |