Class: DataMapper::Query

Inherits:
Object
  • Object
show all
Extended by:
Equalizer
Includes:
Assertions
Defined in:
lib/dm-core/query.rb,
lib/dm-core/query/path.rb,
lib/dm-core/query/sort.rb,
lib/dm-core/query/operator.rb,
lib/dm-core/query/direction.rb,
lib/dm-core/query/conditions/operation.rb,
lib/dm-core/query/conditions/comparison.rb

Overview

Query class represents a query which will be run against the data-store. Generally Query objects can be found inside Collection objects.

Defined Under Namespace

Modules: Conditions Classes: Direction, Operator, Path, Sort

Constant Summary collapse

OPTIONS =
%i(fields links conditions offset limit order unique add_reversed reload).to_set.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Equalizer

equalize

Methods included from Assertions

#assert_kind_of

Instance Attribute Details

#conditionsArray (readonly)

Returns the conditions of the query

In the following example:

Conditions are “greater than” operator for “wins” field and exact match operator for “conference”.

Examples:


Team.all(:wins.gt => 30, :conference => 'East')

Returns:

  • (Array)

    the conditions that will be used to scope the results



179
180
181
# File 'lib/dm-core/query.rb', line 179

def conditions
  @conditions
end

#fieldsPropertySet (readonly)

Returns the fields

Set in cases like the following:

Examples:


Document.all(:fields => [:title, :vernacular_title, :abstract])

Returns:

  • (PropertySet)

    the properties in the Model that will be retrieved



154
155
156
# File 'lib/dm-core/query.rb', line 154

def fields
  @fields
end

#limitInteger? (readonly)

Returns the limit query uses

Set in cases like the following:

Examples:


Document.all(:limit => 10)

Returns:

  • (Integer, nil)

    the maximum number of results



207
208
209
# File 'lib/dm-core/query.rb', line 207

def limit
  @limit
end

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the links (associations) query fetches

Returns:



162
163
164
# File 'lib/dm-core/query.rb', line 162

def links
  @links
end

#modelModel (readonly)

Returns model (class) that is used to instantiate objects from query result returned by adapter

Returns:

  • (Model)

    the Model to retrieve results from



140
141
142
# File 'lib/dm-core/query.rb', line 140

def model
  @model
end

#offsetInteger (readonly)

Returns the offset query uses

Set in cases like the following:

Examples:


Document.all(:offset => page.offset)

Returns:

  • (Integer)

    the offset of the results



193
194
195
# File 'lib/dm-core/query.rb', line 193

def offset
  @offset
end

#optionsHash (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the original options

Returns:

  • (Hash)

    the original options



232
233
234
# File 'lib/dm-core/query.rb', line 232

def options
  @options
end

#orderArray (readonly)

Returns the order

Set in cases like the following:

query order is a set of two ordering rules, descending on “created_at” field and descending again on “length” field

Examples:


Document.all(:order => [:created_at.desc, :length.desc])

Returns:

  • (Array)

    the order of results



224
225
226
# File 'lib/dm-core/query.rb', line 224

def order
  @order
end

#repositoryRepository (readonly)

Returns the repository query should be executed in

Set in cases like the following:

Examples:


Document.all(:repository => :medline)

Returns:

  • (Repository)

    the Repository to retrieve results from



130
131
132
# File 'lib/dm-core/query.rb', line 130

def repository
  @repository
end

Class Method Details

.target_conditions(source, source_key, target_key) ⇒ AbstractComparison, AbstractOperation

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Extract conditions to match a Resource or Collection

Parameters:

  • source (Array, Collection, Resource)

    the source to extract the values from

  • source_key (PropertySet)

    the key to extract the value from the resource

  • target_key (PropertySet)

    the key to match the resource with

Returns:

  • (AbstractComparison, AbstractOperation)

    the conditions to match the resources with



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/dm-core/query.rb', line 48

def self.target_conditions(source, source_key, target_key)
  target_key_size = target_key.size
  source_values   = []

  if source.nil?
    source_values << ([nil] * target_key_size)
  else
    Array(source).each do |resource|
      next unless source_key.loaded?(resource)

      source_value = source_key.get!(resource)
      next unless target_key.valid?(source_value)

      source_values << source_value
    end
  end

  source_values.uniq!

  if target_key_size == 1
    target_key = target_key.first
    source_values.flatten!

    if source_values.size == 1
      Conditions::EqualToComparison.new(target_key, source_values.first)
    else
      Conditions::InclusionComparison.new(target_key, source_values)
    end
  else
    or_operation = Conditions::OrOperation.new

    source_values.each do |source_value|
      and_operation = Conditions::AndOperation.new

      target_key.zip(source_value) do |property, value|
        and_operation << Conditions::EqualToComparison.new(property, value)
      end

      or_operation << and_operation
    end

    or_operation
  end
end

.target_query(repository, model, source) ⇒ Query

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the query to match the resources with.

Parameters:

  • repository (Repository)

    the default repository to scope the query within

  • model (Model)

    the default model for the query

  • source (#query, Enumerable)

    the source to generate the query with

Returns:

  • (Query)

    the query to match the resources with



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/dm-core/query.rb', line 104

def self.target_query(repository, model, source)
  if source.respond_to?(:query)
    source.query
  elsif source.is_a?(Enumerable)
    key        = model.key(repository.name)
    conditions = Query.target_conditions(source, key, key)
    repository.new_query(model, conditions: conditions)
  else
    raise ArgumentError, "+source+ must respond to #query or be an Enumerable, but was #{source.class}"
  end
end

Instance Method Details

#add_reversed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Indicates if each result should be returned in reverse order

Set in cases like the following:

Note that :add_reversed option may be used in conditions directly, but this is rarely the case

Examples:


Document.all(:limit => 5).reverse

Returns:

  • (Boolean)

    true if the results should be reversed, false if not



249
250
251
# File 'lib/dm-core/query.rb', line 249

def add_reversed?
  @add_reversed
end

#clearself

Clear conditions

Returns:

  • (self)


477
478
479
480
# File 'lib/dm-core/query.rb', line 477

def clear
  @conditions = Conditions::Operation.new(:null)
  self
end

#condition_propertiesSet<Property>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get the properties used in the conditions

Returns:

  • (Set<Property>)

    Set of properties used in the conditions



630
631
632
633
634
635
636
637
638
639
640
641
# File 'lib/dm-core/query.rb', line 630

def condition_properties
  properties = Set.new

  each_comparison do |comparison|
    next unless comparison.respond_to?(:subject)

    subject = comparison.subject
    properties << subject if subject.is_a?(Property)
  end

  properties
end

#difference(other) ⇒ Query Also known as: -

Return the difference with another query

Parameters:

  • other (Query)

    the other query

Returns:

  • (Query)

    the difference of the query and other



466
467
468
# File 'lib/dm-core/query.rb', line 466

def difference(other)
  set_operation(:difference, other)
end

#filter_records(records) ⇒ Enumerable

Takes an Enumerable of records, and destructively filters it. First finds all matching conditions, then sorts it, then does offset & limit

Parameters:

  • records (Enumerable)

    The set of records to be filtered

Returns:

  • (Enumerable)

    Whats left of the given array after the filtering



493
494
495
496
497
498
499
# File 'lib/dm-core/query.rb', line 493

def filter_records(records)
  records = records.uniq           if unique?
  records = match_records(records) if conditions
  records = sort_records(records)  if order
  records = limit_records(records) if limit || offset > 0
  records
end

#inspectString

Returns detailed human readable string representation of the query

Returns:

  • (String)

    detailed string representation of the query



607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/dm-core/query.rb', line 607

def inspect
  attrs = [
    [:repository, repository.name],
    [:model,      model],
    [:fields,     fields],
    [:links,      links],
    [:conditions, conditions],
    [:order,      order],
    [:limit,      limit],
    [:offset,     offset],
    [:reload,     reload?],
    [:unique,     unique?]
  ]

  "#<#{self.class.name} #{attrs.map { |key, value| "@#{key}=#{value.inspect}" }.join(' ')}>"
end

#intersection(other) ⇒ Query Also known as: &

Return the intersection with another query

Parameters:

  • other (Query)

    the other query

Returns:

  • (Query)

    the intersection of the query and other



449
450
451
452
453
# File 'lib/dm-core/query.rb', line 449

def intersection(other)
  return dup if self == other

  set_operation(:intersection, other)
end

#limit_records(records) ⇒ Enumerable

Limits a set of records by the offset and/or limit

Parameters:

  • records (Enumerable)

    A list of records to sort

Returns:

  • (Enumerable)

    The offset & limited records



543
544
545
546
547
548
549
550
551
552
553
554
555
# File 'lib/dm-core/query.rb', line 543

def limit_records(records)
  offset = self.offset
  limit  = self.limit
  size   = records.size

  if offset > size - 1
    []
  elsif (limit && limit != size) || offset > 0
    records[offset, limit || size] || []
  else
    records.dup
  end
end

#match_records(records) ⇒ Enumerable

Filter a set of records by the conditions

Parameters:

  • records (Enumerable)

    The set of records to be filtered

Returns:

  • (Enumerable)

    Whats left of the given array after the matching



510
511
512
513
# File 'lib/dm-core/query.rb', line 510

def match_records(records)
  conditions = self.conditions
  records.select { |record| conditions.matches?(record) }
end

#merge(other) ⇒ Query

Similar to Query#update, but acts on a duplicate.

Parameters:

  • other (Query, Hash)

    other query to merge with

Returns:

  • (Query)

    updated duplicate of original query



388
389
390
# File 'lib/dm-core/query.rb', line 388

def merge(other)
  dup.update(other)
end

#raw?Boolean

Indicates if the Query has raw conditions

Returns:

  • (Boolean)

    true if the query has raw conditions, false if not



283
284
285
# File 'lib/dm-core/query.rb', line 283

def raw?
  @raw
end

#relative(options) ⇒ Object

Builds and returns new query that merges original with one given, and slices the result with respect to :limit and :offset options

This method is used by Collection to concatenate options from multiple chained calls in cases like the following:

Examples:


author.books.all(:year => 2009).all(:published => false)


405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/dm-core/query.rb', line 405

def relative(options)
  options = options.to_hash

  offset = nil
  limit  = self.limit

  if options.key?(:offset) && (options.key?(:limit) || limit)
    options = options.dup
    offset  = options.delete(:offset)
    limit   = options.delete(:limit) || (limit - offset)
  end

  query = merge(options)
  query = query.slice!(offset, limit) if offset
  query
end

#reload?Boolean

Indicates if the Query results should replace the results in the Identity Map

TODO: needs example

Returns:

  • (Boolean)

    true if the results should be reloaded, false if not



261
262
263
# File 'lib/dm-core/query.rb', line 261

def reload?
  @reload
end

#reverseQuery

Returns a new Query with a reversed order

Will execute a single query with correct order

Examples:


Document.all(:limit => 5).reverse

Returns:

  • (Query)

    new Query with reversed order



309
310
311
# File 'lib/dm-core/query.rb', line 309

def reverse
  dup.reverse!
end

#reverse!Query

Reverses the sort order of the Query

Will execute a single query with original order and then reverse collection in the Ruby space

Examples:


Document.all(:limit => 5).reverse

Returns:



326
327
328
329
330
331
332
333
334
# File 'lib/dm-core/query.rb', line 326

def reverse!
  # reverse the sort order
  @order.map! { |direction| direction.dup.reverse! }

  # copy the order to the options
  @options = @options.merge(order: @order).freeze

  self
end

#slice(*args) ⇒ Object Also known as: []

Slices collection by adding limit and offset to the query, so a single query is executed

will execute query with the following limit and offset (when repository uses DataObjects adapter, and thus queries use SQL):

LIMIT 5 OFFSET 3

Examples:


Journal.all(:limit => 10).slice(3, 5)


571
572
573
# File 'lib/dm-core/query.rb', line 571

def slice(*args)
  dup.slice!(*args)
end

#slice!(*args) ⇒ Object

Slices collection by adding limit and offset to the query, so a single query is executed

will execute query with the following limit (when repository uses DataObjects adapter, and thus queries use SQL):

LIMIT 10

and then takes a slice of collection in the Ruby space

Examples:


Journal.all(:limit => 10).slice!(3, 5)


593
594
595
596
597
598
599
# File 'lib/dm-core/query.rb', line 593

def slice!(*args)
  offset, limit = extract_slice_arguments(*args)

  offset, limit = get_relative_position(offset, limit) if self.limit || self.offset > 0

  update(offset: offset, limit: limit)
end

#sort_records(records) ⇒ Enumerable

Sorts a list of Records by the order

Parameters:

  • records (Enumerable)

    A list of Resources to sort

Returns:

  • (Enumerable)

    The sorted records



524
525
526
527
528
529
530
531
532
# File 'lib/dm-core/query.rb', line 524

def sort_records(records)
  sort_order = order.map { |direction| [direction.target, direction.operator == :asc] }

  records.sort_by do |record|
    sort_order.map do |(property, ascending)|
      Sort.new(record_value(record, property), ascending)
    end
  end
end

#sorted_fieldsArray<Property>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return a list of fields in predictable order

Returns:

  • (Array<Property>)

    list of fields sorted in deterministic order



649
650
651
# File 'lib/dm-core/query.rb', line 649

def sorted_fields
  fields.sort_by(&:hash)
end

#to_hashHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Hash representation of a Query

Returns:

  • (Hash)

    Hash representation of a Query



670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
# File 'lib/dm-core/query.rb', line 670

def to_hash
  {
    repository: repository.name,
    model: model.name,
    fields: fields,
    links: links,
    conditions: conditions,
    offset: offset,
    limit: limit,
    order: order,
    unique: unique?,
    add_reversed: add_reversed?,
    reload: reload?
  }
end

#to_relative_hashHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Extract options from a Query

Parameters:

  • query (Query)

    the query to extract options from

Returns:

  • (Hash)

    the options to use to initialize the new query



695
696
697
# File 'lib/dm-core/query.rb', line 695

def to_relative_hash
  DataMapper::Ext::Hash.only(to_hash, :fields, :order, :unique, :add_reversed, :reload)
end

#to_subqueryAndOperation

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Transform Query into subquery conditions

Returns:

  • (AndOperation)

    a subquery for the Query



659
660
661
662
# File 'lib/dm-core/query.rb', line 659

def to_subquery
  collection = model.all(merge(fields: model_key))
  Conditions::Operation.new(:and, Conditions::Comparison.new(:in, self_relationship, collection))
end

#union(other) ⇒ Query Also known as: |, +

Return the union with another query

Parameters:

  • other (Query)

    the other query

Returns:

  • (Query)

    the union of the query and other



431
432
433
434
435
# File 'lib/dm-core/query.rb', line 431

def union(other)
  return dup if self == other

  set_operation(:union, other)
end

#unique?Boolean

Indicates if the Query results should be unique

TODO: needs example

Returns:

  • (Boolean)

    true if the results should be unique, false if not



273
274
275
# File 'lib/dm-core/query.rb', line 273

def unique?
  @unique
end

#update(other) ⇒ Query

Updates the Query with another Query or conditions

Pretty unrealistic example:

Examples:


Journal.all(:limit => 2).query.limit                     # => 2
Journal.all(:limit => 2).query.update(:limit => 3).limit # => 3

Parameters:

  • other (Query, Hash)

    other Query or conditions

Returns:



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/dm-core/query.rb', line 352

def update(other)
  other_options = if is_a?(other.class)
                    return self if eql?(other)

                    assert_valid_other(other)
                    other.options
                  else
                    other = other.to_hash
                    return self if other.empty?

                    other
                  end

  @options = @options.merge(other_options).freeze
  assert_valid_options(@options)

  normalize = DataMapper::Ext::Hash.only(other_options, *OPTIONS - [:conditions]).map do |attribute, value|
    instance_variable_set("@#{attribute}", DataMapper::Ext.try_dup(value))
    attribute
  end

  merge_conditions([DataMapper::Ext::Hash.except(other_options, *OPTIONS), other_options[:conditions]])
  normalize_options(normalize | %i(links unique))

  self
end

#valid?Boolean

Indicates if the Query is valid

Returns:

  • (Boolean)

    true if the query is valid



293
294
295
# File 'lib/dm-core/query.rb', line 293

def valid?
  conditions.valid?
end