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 =
[ :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



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

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



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

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



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

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:



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

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



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

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



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

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



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

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



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

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



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

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 (ProperySet)

    the key to extract the value from the resource

  • target_key (ProperySet)

    the key to match the resource with

Returns:

  • (AbstractComparison, AbstractOperation)

    the conditions to match the resources with



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
# File 'lib/dm-core/query.rb', line 49

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



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

def self.target_query(repository, model, source)
  if source.respond_to?(:query)
    source.query
  elsif source.kind_of?(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



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

def add_reversed?
  @add_reversed
end

#clearself

Clear conditions

Returns:

  • (self)


472
473
474
475
# File 'lib/dm-core/query.rb', line 472

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



627
628
629
630
631
632
633
634
635
636
637
# File 'lib/dm-core/query.rb', line 627

def condition_properties
  properties = Set.new

  each_comparison do |comparison|
    next unless comparison.respond_to?(:subject)
    subject = comparison.subject
    properties << subject if subject.kind_of?(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



461
462
463
# File 'lib/dm-core/query.rb', line 461

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



488
489
490
491
492
493
494
# File 'lib/dm-core/query.rb', line 488

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



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

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



445
446
447
448
# File 'lib/dm-core/query.rb', line 445

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



538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/dm-core/query.rb', line 538

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



505
506
507
508
# File 'lib/dm-core/query.rb', line 505

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



385
386
387
# File 'lib/dm-core/query.rb', line 385

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



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

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)


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

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



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

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



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

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:



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

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)


566
567
568
# File 'lib/dm-core/query.rb', line 566

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)


588
589
590
591
592
593
594
595
596
# File 'lib/dm-core/query.rb', line 588

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

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

  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



519
520
521
522
523
524
525
526
527
# File 'lib/dm-core/query.rb', line 519

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



645
646
647
# File 'lib/dm-core/query.rb', line 645

def sorted_fields
  fields.sort_by { |property| property.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



666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
# File 'lib/dm-core/query.rb', line 666

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



691
692
693
# File 'lib/dm-core/query.rb', line 691

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



655
656
657
658
# File 'lib/dm-core/query.rb', line 655

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



428
429
430
431
# File 'lib/dm-core/query.rb', line 428

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



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

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:



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

def update(other)
  other_options = if kind_of?(other.class)
    return self if self.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 | [ :links, :unique ])

  self
end

#valid?Boolean

Indicates if the Query is valid

Returns:

  • (Boolean)

    true if the query is valid



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

def valid?
  conditions.valid?
end