class Google::Cloud::Firestore::Query

# Query

Represents a query to the Firestore API.

Instances of this class are immutable. All methods that refine the query return new instances.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Create a query
query = firestore.col(:cities).select(:population)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Listen to a query for changes:

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Create a query
query = firestore.col(:cities).order(:population, :desc)

listener = query.listen do |snapshot|
  puts "The query snapshot has #{snapshot.docs.count} documents "
  puts "and has #{snapshot.changes.count} changes."
end

# When ready, stop the listen operation and close the stream.
listener.stop

Constants

FILTER_OPS

@private

INEQUALITY_FILTERS

@private

StructuredQuery

@private

Attributes

client[RW]

@private The firestore client object.

limit_type[R]

@private The type for limit queries.

parent_path[RW]

@private The parent path for the query.

query[RW]

@private The Google::Cloud::Firestore::V1::StructuredQuery object.

Public Class Methods

from_json(json, client) click to toggle source

Deserializes a JSON text string serialized from this class and returns it as a new instance. See also {#to_json}.

@param [String] json A JSON text string serialized using {#to_json}. @param [Google::Cloud::Firestore::Client] client A connected client instance.

@return [Query] A new query equal to the original query used to create the JSON text string.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new
query = firestore.col(:cities).select(:population)

json = query.to_json

new_query = Google::Cloud::Firestore::Query.from_json json, firestore

new_query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 1027
def self.from_json json, client
  raise ArgumentError, "client is required" unless client

  json = JSON.parse json
  query_json = json["query"]
  raise ArgumentError, "Field 'query' is required" unless query_json
  query = Google::Cloud::Firestore::V1::StructuredQuery.decode_json query_json.to_json
  start query, json["parent_path"], client, limit_type: json["limit_type"]&.to_sym
end
new(query, parent_path, client, limit_type: nil) click to toggle source

@private Creates a new Query.

# File lib/google/cloud/firestore/query.rb, line 80
def initialize query, parent_path, client, limit_type: nil
  query ||= StructuredQuery.new
  @query = query
  @parent_path = parent_path
  @limit_type = limit_type
  @client = client
end
start(query, parent_path, client, limit_type: nil) click to toggle source

@private Start a new Query.

# File lib/google/cloud/firestore/query.rb, line 1039
def self.start query, parent_path, client, limit_type: nil
  new query, parent_path, client, limit_type: limit_type
end

Public Instance Methods

all_descendants() click to toggle source

@private This is marked private and can't be removed.

Selects documents from all collections, immediate children and nested, of where the query was created from.

@return [Query] New query with `all_descendants` called on it.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.all_descendants

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 162
def all_descendants
  new_query = @query.dup
  new_query ||= StructuredQuery.new

  if new_query.from.empty?
    raise "missing collection_id to specify descendants"
  end

  new_query.from.last.all_descendants = true

  Query.start new_query, parent_path, client, limit_type: limit_type
end
direct_descendants() click to toggle source

@private This is marked private and can't be removed.

Selects only documents from collections that are immediate children of where the query was created from.

@return [Query] New query with `direct_descendants` called on it.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.direct_descendants

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 198
def direct_descendants
  new_query = @query.dup
  new_query ||= StructuredQuery.new

  if new_query.from.empty?
    raise "missing collection_id to specify descendants"
  end

  new_query.from.last.all_descendants = false

  Query.start new_query, parent_path, client, limit_type: limit_type
end
end_at(*values) click to toggle source

Ends query results at a set of field values. The field values can be specified explicitly as arguments, or can be specified implicitly by providing a {DocumentSnapshot} object instead. The result set will include the document specified by `values`.

If the current query already has specified `end_before` or `end_at`, this will overwrite it.

The values are associated with the field paths that have been provided to `order`, and must match the same sort order. An ArgumentError will be raised if more explicit values are given than are present in `order`.

@param [DocumentSnapshot, Object, Array<Object>] values The field

values to end the query at.

@return [Query] New query with `end_at` called on it.

@example Ending a query at a document reference id

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"
nyc_doc_id = "NYC"

# Create a query
query = cities_col.order(firestore.document_id)
                  .end_at(nyc_doc_id)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Ending a query at a document reference object

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"
nyc_doc_id = "NYC"
nyc_ref = cities_col.doc nyc_doc_id

# Create a query
query = cities_col.order(firestore.document_id)
                  .end_at(nyc_ref)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Ending a query at multiple explicit values

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.order(:population, :desc)
                  .order(:name)
                  .end_at(1000000, "New York City")

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Ending a query at a DocumentSnapshot

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Get a document snapshot
nyc_snap = firestore.doc("cities/NYC").get

# Create a query
query = cities_col.order(:population, :desc)
                  .order(:name)
                  .end_at(nyc_snap)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 885
def end_at *values
  raise ArgumentError, "must provide values" if values.empty?

  if limit_type == :last
    raise "cannot call end_at after calling limit_to_last"
  end


  new_query = @query.dup
  new_query ||= StructuredQuery.new

  cursor = values_to_cursor values, new_query
  cursor.before = false
  new_query.end_at = cursor

  Query.start new_query, parent_path, client, limit_type: limit_type
end
end_before(*values) click to toggle source

Ends query results before a set of field values. The field values can be specified explicitly as arguments, or can be specified implicitly by providing a {DocumentSnapshot} object instead. The result set will not include the document specified by `values`.

If the current query already has specified `end_before` or `end_at`, this will overwrite it.

The values are associated with the field paths that have been provided to `order`, and must match the same sort order. An ArgumentError will be raised if more explicit values are given than are present in `order`.

@param [DocumentSnapshot, Object, Array<Object>] values The field

values to end the query before.

@return [Query] New query with `end_before` called on it.

@example Ending a query before a document reference id

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"
nyc_doc_id = "NYC"

# Create a query
query = cities_col.order(firestore.document_id)
                  .end_before(nyc_doc_id)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Ending a query before a document reference object

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"
nyc_doc_id = "NYC"
nyc_ref = cities_col.doc nyc_doc_id

# Create a query
query = cities_col.order(firestore.document_id)
                  .end_before(nyc_ref)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Ending a query before multiple explicit values

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.order(:population, :desc)
                  .order(:name)
                  .end_before(1000000, "New York City")

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Ending a query before a DocumentSnapshot

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Get a document snapshot
nyc_snap = firestore.doc("cities/NYC").get

# Create a query
query = cities_col.order(:population, :desc)
                  .order(:name)
                  .end_before(nyc_snap)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 776
def end_before *values
  raise ArgumentError, "must provide values" if values.empty?

  if limit_type == :last
    raise "cannot call end_before after calling limit_to_last"
  end


  new_query = @query.dup
  new_query ||= StructuredQuery.new

  cursor = values_to_cursor values, new_query
  cursor.before = true
  new_query.end_at = cursor

  Query.start new_query, parent_path, client, limit_type: limit_type
end
get() { |from_query_result result, client| ... } click to toggle source

Retrieves document snapshots for the query.

@yield [documents] The block for accessing the document snapshots. @yieldparam [DocumentSnapshot] document A document snapshot.

@return [Enumerator<DocumentSnapshot>] A list of document snapshots.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.select(:population)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 926
def get
  ensure_service!

  return enum_for :get unless block_given?

  results = service.run_query parent_path, @query

  # Reverse the results for Query#limit_to_last queries since that method reversed the order_by directions.
  results = results.to_a.reverse if limit_type == :last

  results.each do |result|
    next if result.document.nil?
    yield DocumentSnapshot.from_query_result result, client
  end
end
Also aliased as: run
limit(num) click to toggle source

Limits a query to return only the first matching documents.

If the current query already has a limit set, this will overwrite it.

@param [Integer] num The maximum number of results to return.

@return [Query] New query with `limit` called on it.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.order(:name, :desc).offset(10).limit(5)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 395
def limit num
  if limit_type == :last
    raise "cannot call limit after calling limit_to_last"
  end

  new_query = @query.dup
  new_query ||= StructuredQuery.new

  new_query.limit = Google::Protobuf::Int32Value.new value: num

  Query.start new_query, parent_path, client, limit_type: :first
end
limit_to_last(num) click to toggle source

Limits a query to return only the last matching documents.

You must specify at least one “order by” clause for limitToLast queries. (See {#order}.)

Results for `limit_to_last` queries are only available once all documents are received. Hence, `limit_to_last` queries cannot be streamed using {#listen}.

@param [Integer] num The maximum number of results to return.

@return [Query] New query with `limit_to_last` called on it.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.order(:name, :desc).limit_to_last(5)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 437
def limit_to_last num
  new_query = @query.dup

  if new_query.nil? || new_query.order_by.nil? || new_query.order_by.empty?
    raise "specify at least one order clause before calling limit_to_last"
  end

  if limit_type != :last # Don't reverse order_by more than once.
    # Reverse the order_by directions since we want the last results.
    new_query.order_by.each do |order|
      order.direction = order.direction.to_sym == :DESCENDING ? :ASCENDING : :DESCENDING
    end

    # Swap the cursors to match the reversed query ordering.
    new_end_at = new_query.start_at.dup
    new_start_at = new_query.end_at.dup
    if new_end_at
      new_end_at.before = !new_end_at.before
      new_query.end_at = new_end_at
    end
    if new_start_at
      new_start_at.before = !new_start_at.before
      new_query.start_at = new_start_at
    end
  end

  new_query.limit = Google::Protobuf::Int32Value.new value: num

  Query.start new_query, parent_path, client, limit_type: :last
end
listen(&callback) click to toggle source

Listen to this query for changes.

@yield [callback] The block for accessing the query snapshot. @yieldparam [QuerySnapshot] snapshot A query snapshot.

@return [QueryListener] The ongoing listen operation on the query.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Create a query
query = firestore.col(:cities).order(:population, :desc)

listener = query.listen do |snapshot|
  puts "The query snapshot has #{snapshot.docs.count} documents "
  puts "and has #{snapshot.changes.count} changes."
end

# When ready, stop the listen operation and close the stream.
listener.stop
# File lib/google/cloud/firestore/query.rb, line 967
def listen &callback
  raise ArgumentError, "callback required" if callback.nil?

  ensure_service!

  QueryListener.new(self, &callback).start
end
Also aliased as: on_snapshot
offset(num) click to toggle source

Skips to an offset in a query. If the current query already has specified an offset, this will overwrite it.

@param [Integer] num The number of results to skip.

@return [Query] New query with `offset` called on it.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.limit(5).offset(10)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 362
def offset num
  new_query = @query.dup
  new_query ||= StructuredQuery.new

  new_query.offset = num

  Query.start new_query, parent_path, client, limit_type: limit_type
end
on_snapshot(&callback)
Alias for: listen
order(field, direction = :asc) click to toggle source

Specifies an “order by” clause on a field.

@param [FieldPath, String, Symbol] field A field path to order results

with.

If a {FieldPath} object is not provided then the field will be
treated as a dotted string, meaning the string represents individual
fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
`.` cannot be in a dotted string, and should provided using a
{FieldPath} object instead.

@param [String, Symbol] direction The direction to order the results

by. Values that start with "a" are considered `ascending`. Values
that start with "d" are considered `descending`. Default is
`ascending`. Optional.

@return [Query] New query with `order` called on it.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.order(:name)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Order by name descending:

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.order(:name, :desc)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 318
def order field, direction = :asc
  if query_has_cursors? || limit_type == :last
    raise "cannot call order after calling limit_to_last, start_at, start_after, end_before, or end_at"
  end

  new_query = @query.dup
  new_query ||= StructuredQuery.new

  field = FieldPath.parse field unless field.is_a? FieldPath

  new_query.order_by << StructuredQuery::Order.new(
    field:     StructuredQuery::FieldReference.new(
      field_path: field.formatted_string
    ),
    direction: order_direction(direction)
  )

  Query.start new_query, parent_path, client, limit_type: limit_type
end
Also aliased as: order_by
order_by(field, direction = :asc)
Alias for: order
run()
Alias for: get
select(*fields) click to toggle source

Restricts documents matching the query to return only data for the provided fields.

@param [FieldPath, String, Symbol, Array<FieldPath|String|Symbol>]

fields A field path to filter results with and return only the
specified fields. One or more field paths can be specified.

If a {FieldPath} object is not provided then the field will be
treated as a dotted string, meaning the string represents individual
fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
`.` cannot be in a dotted string, and should provided using a
{FieldPath} object instead.

@return [Query] New query with `select` called on it.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.select(:population)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 119
def select *fields
  new_query = @query.dup
  new_query ||= StructuredQuery.new

  fields = Array(fields).flatten.compact
  fields = [FieldPath.document_id] if fields.empty?
  field_refs = fields.flatten.compact.map do |field|
    field = FieldPath.parse field unless field.is_a? FieldPath
    StructuredQuery::FieldReference.new \
      field_path: field.formatted_string
  end

  new_query.select = StructuredQuery::Projection.new
  field_refs.each do |field_ref|
    new_query.select.fields << field_ref
  end

  Query.start new_query, parent_path, client, limit_type: limit_type
end
start_after(*values) click to toggle source

Starts query results after a set of field values. The field values can be specified explicitly as arguments, or can be specified implicitly by providing a {DocumentSnapshot} object instead. The result set will not include the document specified by `values`.

If the current query already has specified `start_at` or `start_after`, this will overwrite it.

The values are associated with the field paths that have been provided to `order`, and must match the same sort order. An ArgumentError will be raised if more explicit values are given than are present in `order`.

@param [DocumentSnapshot, Object, Array<Object>] values The field

values to start the query after.

@return [Query] New query with `start_after` called on it.

@example Starting a query after a document reference id

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"
nyc_doc_id = "NYC"

# Create a query
query = cities_col.order(firestore.document_id)
                  .start_after(nyc_doc_id)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Starting a query after a document reference object

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"
nyc_doc_id = "NYC"
nyc_ref = cities_col.doc nyc_doc_id

# Create a query
query = cities_col.order(firestore.document_id)
                  .start_after(nyc_ref)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Starting a query after multiple explicit values

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.order(:population, :desc)
                  .order(:name)
                  .start_after(1000000, "New York City")

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Starting a query after a DocumentSnapshot

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Get a document snapshot
nyc_snap = firestore.doc("cities/NYC").get

# Create a query
query = cities_col.order(:population, :desc)
                  .order(:name)
                  .start_after(nyc_snap)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 667
def start_after *values
  raise ArgumentError, "must provide values" if values.empty?

  if limit_type == :last
    raise "cannot call start_after after calling limit_to_last"
  end


  new_query = @query.dup
  new_query ||= StructuredQuery.new

  cursor = values_to_cursor values, new_query
  cursor.before = false
  new_query.start_at = cursor

  Query.start new_query, parent_path, client, limit_type: limit_type
end
start_at(*values) click to toggle source

Starts query results at a set of field values. The field values can be specified explicitly as arguments, or can be specified implicitly by providing a {DocumentSnapshot} object instead. The result set will include the document specified by `values`.

If the current query already has specified `start_at` or `start_after`, this will overwrite it.

The values are associated with the field paths that have been provided to `order`, and must match the same sort order. An ArgumentError will be raised if more explicit values are given than are present in `order`.

@param [DocumentSnapshot, Object, Array<Object>] values The field

values to start the query at.

@return [Query] New query with `start_at` called on it.

@example Starting a query at a document reference id

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"
nyc_doc_id = "NYC"

# Create a query
query = cities_col.order(firestore.document_id)
                  .start_at(nyc_doc_id)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Starting a query at a document reference object

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"
nyc_doc_id = "NYC"
nyc_ref = cities_col.doc nyc_doc_id

# Create a query
query = cities_col.order(firestore.document_id)
                  .start_at(nyc_ref)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Starting a query at multiple explicit values

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.order(:population, :desc)
                  .order(:name)
                  .start_at(1000000, "New York City")

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end

@example Starting a query at a DocumentSnapshot

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Get a document snapshot
nyc_snap = firestore.doc("cities/NYC").get

# Create a query
query = cities_col.order(:population, :desc)
                  .order(:name)
                  .start_at(nyc_snap)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 559
def start_at *values
  raise ArgumentError, "must provide values" if values.empty?

  if limit_type == :last
    raise "cannot call start_at after calling limit_to_last"
  end

  new_query = @query.dup
  new_query ||= StructuredQuery.new

  cursor = values_to_cursor values, new_query
  cursor.before = true
  new_query.start_at = cursor

  Query.start new_query, parent_path, client, limit_type: limit_type
end
to_json(options = nil) click to toggle source

Serializes the instance to a JSON text string. See also {Query.from_json}.

@return [String] A JSON text string.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new
query = firestore.col(:cities).select(:population)

json = query.to_json

new_query = Google::Cloud::Firestore::Query.from_json json, firestore

new_query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 995
def to_json options = nil
  query_json = Google::Cloud::Firestore::V1::StructuredQuery.encode_json query
  {
    "query" => JSON.parse(query_json),
    "parent_path" => parent_path,
    "limit_type" => limit_type
  }.to_json options
end
where(field, operator, value) click to toggle source

Filters the query on a field.

@param [FieldPath, String, Symbol] field A field path to filter

results with.

If a {FieldPath} object is not provided then the field will be
treated as a dotted string, meaning the string represents individual
fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
`.` cannot be in a dotted string, and should provided using a
{FieldPath} object instead.

@param [String, Symbol] operator The operation to compare the field

to. Acceptable values include:

* less than: `<`, `lt`
* less than or equal: `<=`, `lte`
* greater than: `>`, `gt`
* greater than or equal: `>=`, `gte`
* equal: `=`, `==`, `eq`, `eql`, `is`
* not equal: `!=`
* in: `in`
* not in: `not-in`, `not_in`
* array contains: `array-contains`, `array_contains`

@param [Object] value A value the field is compared to.

@return [Query] New query with `where` called on it.

@example

require "google/cloud/firestore"

firestore = Google::Cloud::Firestore.new

# Get a collection reference
cities_col = firestore.col "cities"

# Create a query
query = cities_col.where(:population, :>=, 1000000)

query.get do |city|
  puts "#{city.document_id} has #{city[:population]} residents."
end
# File lib/google/cloud/firestore/query.rb, line 253
def where field, operator, value
  if query_has_cursors?
    raise "cannot call where after calling " \
          "start_at, start_after, end_before, or end_at"
  end

  new_query = @query.dup
  new_query ||= StructuredQuery.new

  field = FieldPath.parse field unless field.is_a? FieldPath

  new_filter = filter field.formatted_string, operator, value
  add_filters_to_query new_query, new_filter

  Query.start new_query, parent_path, client, limit_type: limit_type
end

Protected Instance Methods

add_filters_to_query(query, filter) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1136
def add_filters_to_query query, filter
  if query.where.nil?
    query.where = filter
  elsif query.where.filter_type == :composite_filter
    query.where.composite_filter.filters << filter
  else
    old_filter = query.where
    query.where = composite_filter
    query.where.composite_filter.filters << old_filter
    query.where.composite_filter.filters << filter
  end
end
composite_filter() click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1130
def composite_filter
  StructuredQuery::Filter.new(
    composite_filter: StructuredQuery::CompositeFilter.new(op: :AND)
  )
end
doc_id_path() click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1280
def doc_id_path
  "__name__".freeze
end
document_reference(document_path) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1259
def document_reference document_path
  if document_path.to_s.split("/").count.even?
    raise ArgumentError, "document_path must refer to a document"
  end

  DocumentReference.from_path(
    "#{query_collection_path}/#{document_path}", client
  )
end
ensure_client!() click to toggle source

@private Raise an error unless an database available.

# File lib/google/cloud/firestore/query.rb, line 1286
def ensure_client!
  raise "Must have active connection to service" unless client
end
ensure_document_id_in_order_by!(query) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1221
def ensure_document_id_in_order_by! query
  return if order_by_field_paths(query).include? doc_id_path

  query.order_by.push StructuredQuery::Order.new(
    field:     StructuredQuery::FieldReference.new(
      field_path: doc_id_path
    ),
    direction: last_order_direction(query)
  )
end
ensure_inequality_field_paths_in_order_by!(query) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1205
def ensure_inequality_field_paths_in_order_by! query
  inequality_paths = inequality_filter_field_paths query
  orig_order = order_by_field_paths query

  inequality_paths.reverse_each do |field_path|
    next if orig_order.include? field_path

    query.order_by.unshift StructuredQuery::Order.new(
      field:     StructuredQuery::FieldReference.new(
        field_path: field_path
      ),
      direction: :ASCENDING
    )
  end
end
ensure_service!() click to toggle source

@private Raise an error unless an active connection to the service is available.

# File lib/google/cloud/firestore/query.rb, line 1301
def ensure_service!
  raise "Must have active connection to service" unless service
end
filter(name, op_key, value) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1100
def filter name, op_key, value
  field = StructuredQuery::FieldReference.new field_path: name.to_s
  operator = FILTER_OPS[op_key.to_s.downcase]
  raise ArgumentError, "unknown operator #{op_key}" if operator.nil?

  if value_unary? value
    operator = case operator
               when :EQUAL
                 value_nan?(value) ? :IS_NAN : :IS_NULL
               when :NOT_EQUAL
                 value_nan?(value) ? :IS_NOT_NAN : :IS_NOT_NULL
               else
                 raise ArgumentError, "can only perform '==' and '!=' comparisons on #{value} values"
               end

    return StructuredQuery::Filter.new(
      unary_filter: StructuredQuery::UnaryFilter.new(
        field: field, op: operator
      )
    )
  end

  value = Convert.raw_to_value value
  StructuredQuery::Filter.new(
    field_filter: StructuredQuery::FieldFilter.new(
      field: field, op: operator, value: value
    )
  )
end
inequality_filter_field_paths(query) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1232
def inequality_filter_field_paths query
  return [] if query.where.nil?

  # The way we construct a query, where is always a CompositeFilter
  filters = if query.where.filter_type == :composite_filter
              query.where.composite_filter.filters
            else
              [query.where]
            end
  ineq_filters = filters.select do |filter|
    if filter.filter_type == :field_filter
      INEQUALITY_FILTERS.include? filter.field_filter.op
    end
  end
  ineq_filters.map { |filter| filter.field_filter.field.field_path }
end
last_order_direction(query) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1253
def last_order_direction query
  last_order_by = query.order_by.last
  return :ASCENDING if last_order_by.nil?
  last_order_by.direction
end
order_by_field_paths(query) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1249
def order_by_field_paths query
  query.order_by.map { |order_by| order_by.field.field_path }
end
order_direction(direction) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1149
def order_direction direction
  return :DESCENDING if direction.to_s.downcase.start_with? "d"
  :ASCENDING
end
query_collection_id() click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1273
def query_collection_id
  # We trust that query.from is always set, since Query cannot be
  # created without it.
  return nil if query.from.empty?
  query.from.first.collection_id
end
query_collection_path() click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1269
def query_collection_path
  "#{parent_path}/#{query_collection_id}"
end
query_has_cursors?() click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1154
def query_has_cursors?
  query.start_at || query.end_at
end
service() click to toggle source

@private The Service object.

# File lib/google/cloud/firestore/query.rb, line 1292
def service
  ensure_client!

  client.service
end
snapshot_to_cursor(snapshot, query) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1183
def snapshot_to_cursor snapshot, query
  if snapshot.parent.path != query_collection_path
    raise ArgumentError, "cursor snapshot must belong to collection"
  end

  # first, add any inequality filters missing from existing order_by
  ensure_inequality_field_paths_in_order_by! query

  # second, make sure __name__ is present in order_by
  ensure_document_id_in_order_by! query

  # lastly, create cursor for all field_paths in order_by
  values = order_by_field_paths(query).map do |field_path|
    if field_path == doc_id_path
      snapshot.ref
    else
      snapshot[field_path]
    end
  end
  values_to_cursor values, query
end
value_nan?(value) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1090
def value_nan? value
  # Comparing NaN values raises, so check for #nan? first.
  return true if value.respond_to?(:nan?) && value.nan?
  [:nan].include? value
end
value_nil?(value) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1086
def value_nil? value
  [nil, :null, :nil].include? value
end
value_unary?(value) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1096
def value_unary? value
  value_nil?(value) || value_nan?(value)
end
values_to_cursor(values, query) click to toggle source
# File lib/google/cloud/firestore/query.rb, line 1158
def values_to_cursor values, query
  if values.count == 1 && values.first.is_a?(DocumentSnapshot)
    return snapshot_to_cursor values.first, query
  end

  # The *values param in start_at, start_after, etc. will wrap an array argument in an array, so unwrap it here.
  values = values.first if values.count == 1 && values.first.is_a?(Array)

  # pair values with their field_paths to ensure correct formatting
  order_field_paths = order_by_field_paths query
  if values.count > order_field_paths.count
    # raise if too many values provided for the cursor
    raise ArgumentError, "There cannot be more cursor values than order by fields"
  end

  values = values.zip(order_field_paths).map do |value, field_path|
    if field_path == doc_id_path && !value.is_a?(DocumentReference)
      value = document_reference value
    end
    Convert.raw_to_value value
  end

  Google::Cloud::Firestore::V1::Cursor.new values: values
end