Class: Card::Query

Inherits:
Object
  • Object
show all
Includes:
Attributes, Clause, Conjunctions, Helpers, Interpretation, RelationalAttributes, Sorting
Defined in:
lib/card/query.rb,
lib/card/query/join.rb,
lib/card/query/value.rb,
lib/card/query/helpers.rb,
lib/card/query/sorting.rb,
lib/card/query/reference.rb,
lib/card/query/attributes.rb,
lib/card/query/conjunctions.rb,
lib/card/query/sql_statement.rb,
lib/card/query/interpretation.rb,
lib/card/query/relational_attributes.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. (http://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, Conjunctions, Helpers, Interpretation, RelationalAttributes, Sorting 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
                       updater_of updated_by),
  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 name_match complete junction_complete
                      extension_type),
  ignore:          %w(prepend append view params vars size)
}.each_with_object({}) do |pair, h|
  pair[1].each { |v| h[v.to_sym] = pair[0] }
end
CONJUNCTIONS =
{ any: :or, in: :or, or: :or, all: :and, and: :and }.freeze
MODIFIERS =
%w(conj return sort sort_as group dir limit offset)
.each_with_object({}) { |v, h| h[v.to_sym] = nil }
OPERATORS =
%w(!= = =~ < > in ~).each_with_object({}) { |v, h| h[v] = v }.merge(
  {
    eq: "=", gt: ">", lt: "<", match: "~", ne: "!=", "not in" => nil
  }.stringify_keys
)
DEFAULT_ORDER_DIRS =
{ update: "desc", relevance: "desc" }.freeze

Constants included from Sorting

Sorting::SORT_JOIN_TO_ITEM_MAP

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers

#id_from_val, #join_cards, #join_references, #restrict, #restrict_reference, #table_alias, #table_id, #tick_table_seq!

Methods included from Conjunctions

#all, #any, #conjoin, #conjunction, #not

Methods included from Sorting

#sort, #sort_by_count, #sort_by_count_referred_to

Methods included from Interpretation

#add_condition, #all_joins, #clause_to_hash, #current_conjunction, #interpret, #interpret_attributes, #interpret_by_key, #normalize_clause, #normalize_string_value, #normalize_value, #relate, #relate_compound, #relate_multi_value

Methods included from RelationalAttributes

#created_by, #creator_of, #edited_by, #editor_of, #junction, #last_edited_by, #last_editor_of, #left, #left_plus, #member, #member_of, #part, #plus, #right, #right_plus, #type, #updated_by, #updater_of

Methods included from Attributes

#complete, #extension_type, #found_by, #found_by_cards, #junction_complete, #match, #name_match

Methods included from Clause

#match_prep, #quote, #safe_sql

Constructor Details

#initialize(statement, comment = nil) ⇒ Query

Returns a new instance of Query.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/card/query.rb', line 99

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       = initialize_vars

  @comment = comment || default_comment

  interpret @statement
  self
end

Instance Attribute Details

#commentObject (readonly)

Returns the value of attribute comment.



85
86
87
# File 'lib/card/query.rb', line 85

def comment
  @comment
end

#conditionsObject (readonly)

Returns the value of attribute conditions.



85
86
87
# File 'lib/card/query.rb', line 85

def conditions
  @conditions
end

#conditions_on_joinObject

Returns the value of attribute conditions_on_join.



87
88
89
# File 'lib/card/query.rb', line 87

def conditions_on_join
  @conditions_on_join
end

#joinsObject

Returns the value of attribute joins.



87
88
89
# File 'lib/card/query.rb', line 87

def joins
  @joins
end

#modsObject (readonly)

Returns the value of attribute mods.



85
86
87
# File 'lib/card/query.rb', line 85

def mods
  @mods
end

#statementObject (readonly)

Returns the value of attribute statement.



85
86
87
# File 'lib/card/query.rb', line 85

def statement
  @statement
end

#subqueriesObject (readonly)

Returns the value of attribute subqueries.



85
86
87
# File 'lib/card/query.rb', line 85

def subqueries
  @subqueries
end

#superqueryObject (readonly)

Returns the value of attribute superquery.



85
86
87
# File 'lib/card/query.rb', line 85

def superquery
  @superquery
end

#table_seqObject

Returns the value of attribute table_seq.



87
88
89
# File 'lib/card/query.rb', line 87

def table_seq
  @table_seq
end

#unjoinedObject (readonly)

Returns the value of attribute unjoined.



85
86
87
# File 'lib/card/query.rb', line 85

def unjoined
  @unjoined
end

#varsObject (readonly)

Returns the value of attribute vars.



85
86
87
# File 'lib/card/query.rb', line 85

def vars
  @vars
end

Class Method Details

.run(statement, comment = nil) ⇒ Object

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)



95
96
97
# File 'lib/card/query.rb', line 95

def self.run statement, comment=nil
  new(statement, comment).run
end

Instance Method Details

#contextObject



209
210
211
212
213
214
215
# File 'lib/card/query.rb', line 209

def context
  if !@context.nil?
    @context
  else
    @context = @superquery ? @superquery.context : ""
  end
end

#contextual_name_processor(pattern) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/card/query.rb', line 174

def contextual_name_processor pattern
  case pattern.downcase
  when "_left", "_l"
    ->(name) { name.to_name.left_name.to_s }
  when "_right", "_r"
    ->(name) { name.to_name.right_name.to_s }
  else
    chain = "name.to_name"
    pattern.each_char do |ch|
      case ch
      when "l", "L"
        chain += ".left_name"
      when "r", "R"
        chain += ".right_name"
      end
    end
    eval "lambda { |name| #{chain}.to_s }"
  end
end

#default_commentObject



124
125
126
127
# File 'lib/card/query.rb', line 124

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.

Returns:

  • Integer for :count, otherwise Array of Strings or Integers



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/card/query.rb', line 143

def get_results retrn
  rows = run_sql
  if retrn =~ /_\w+/
    name_processor = contextual_name_processor retrn
    rows.map do |row|
      name_processor.call row["name"]
    end
  elsif 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

#initialize_varsObject



117
118
119
120
121
122
# File 'lib/card/query.rb', line 117

def initialize_vars
  if (v = @statement.delete :vars) then v.symbolize_keys
  elsif @superquery                then @superquery.vars
  else                                  {}
  end
end

#limitObject



217
218
219
# File 'lib/card/query.rb', line 217

def limit
  @mods[:limit].to_i
end

#rootObject

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....



199
200
201
# File 'lib/card/query.rb', line 199

def root
  @root ||= @superquery ? @superquery.root : self
end

#runObject

run the current query

Returns:

  • array of card objects by default



131
132
133
134
135
136
137
138
139
140
# File 'lib/card/query.rb', line 131

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_sqlObject



164
165
166
167
168
# File 'lib/card/query.rb', line 164

def run_sql
  # puts "\nstatement = #{@statement}"
  # puts "sql = #{sql}"
  ActiveRecord::Base.connection.select_all(sql)
end

#sqlObject



170
171
172
# File 'lib/card/query.rb', line 170

def sql
  @sql ||= SqlStatement.new(self).build.to_s
end

#subquery(opts = {}) ⇒ Object



203
204
205
206
207
# File 'lib/card/query.rb', line 203

def subquery opts={}
  subquery = Query.new opts.merge(superquery: self)
  @subqueries << subquery
  subquery
end