Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firestore Collection Group queries #3416

Merged
merged 2 commits into from
May 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions google-cloud-firestore/acceptance/firestore/query_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,101 @@
results.map(&:document_id).must_equal ["doc1", "doc2"]
results.map { |doc| doc[:foo] }.must_equal ["a", "b"]
end

describe "Collection Group" do
it "queries a collection group" do
collection_group = "b-#{SecureRandom.hex(4)}"
doc_paths = [
"abc/123/#{collection_group}/cg-doc1",
"abc/123/#{collection_group}/cg-doc2",
"#{collection_group}/cg-doc3",
"#{collection_group}/cg-doc4",
"def/456/#{collection_group}/cg-doc5",
"#{collection_group}/virtual-doc/nested-coll/not-cg-doc",
"x#{collection_group}/not-cg-doc",
"#{collection_group}x/not-cg-doc",
"abc/123/#{collection_group}x/not-cg-doc",
"abc/123/x#{collection_group}/not-cg-doc",
"abc/#{collection_group}"
]
firestore.batch do |b|
doc_paths.each do |doc_path|
doc_ref = firestore.document doc_path
b.set doc_ref, {x: 1}
end
end

query = firestore.collection_group collection_group
snapshots = query.get
snapshots.map(&:document_id).must_equal ["cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5"]
end

it "queries a collection group with start_at and end_at" do
collection_group = "b-#{SecureRandom.hex(4)}"
doc_paths = [
"a/a/#{collection_group}/cg-doc1",
"a/b/a/b/#{collection_group}/cg-doc2",
"a/b/#{collection_group}/cg-doc3",
"a/b/c/d/#{collection_group}/cg-doc4",
"a/c/#{collection_group}/cg-doc5",
"#{collection_group}/cg-doc6",
"a/b/nope/nope"
]
firestore.batch do |b|
doc_paths.each do |doc_path|
doc_ref = firestore.document doc_path
b.set doc_ref, {x: 1}
end
end

query = firestore.collection_group(collection_group)
.order_by("__name__")
.start_at(firestore.document("a/b"))
.end_at(firestore.document("a/b0"))

snapshots = query.get
snapshots.map(&:document_id).must_equal ["cg-doc2", "cg-doc3", "cg-doc4"]

query = firestore.collection_group(collection_group)
.order_by("__name__")
.start_after(firestore.document("a/b"))
.end_before(firestore.document("a/b/#{collection_group}/cg-doc3"))
snapshots = query.get
snapshots.map(&:document_id).must_equal ["cg-doc2"]
end

it "queries a collection group with filters" do
collection_group = "b-#{SecureRandom.hex(4)}"
doc_paths = [
"a/a/#{collection_group}/cg-doc1",
"a/b/a/b/#{collection_group}/cg-doc2",
"a/b/#{collection_group}/cg-doc3",
"a/b/c/d/#{collection_group}/cg-doc4",
"a/c/#{collection_group}/cg-doc5",
"#{collection_group}/cg-doc6",
"a/b/nope/nope"
]
firestore.batch do |b|
doc_paths.each do |doc_path|
doc_ref = firestore.document doc_path
b.set doc_ref, {x: 1}
end
end

query = firestore.collection_group(collection_group)
.where("__name__", ">=", firestore.document("a/b"))
.where("__name__", "<=", firestore.document("a/b0"))

snapshots = query.get
snapshots.map(&:document_id).must_equal ["cg-doc2", "cg-doc3", "cg-doc4"]

query = firestore.collection_group(collection_group)
.where("__name__", ">", firestore.document("a/b"))
.where(
"__name__", "<", firestore.document("a/b/#{collection_group}/cg-doc3")
)
snapshots = query.get
snapshots.map(&:document_id).must_equal ["cg-doc2"]
end
end
end
40 changes: 40 additions & 0 deletions google-cloud-firestore/lib/google/cloud/firestore/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,46 @@ def col collection_path
end
alias collection col

##
# Creates and returns a new Query that includes all documents in the
# database that are contained in a collection or subcollection with the
# given collection_id.
#
# @param [String] collection_id Identifies the collections to query
# over. Every collection or subcollection with this ID as the last
# segment of its path will be included. Cannot contain a slash (`/`).
#
# @return [Query] The created Query.
#
# @example
# require "google/cloud/firestore"
#
# firestore = Google::Cloud::Firestore.new
#
# # Get the cities collection group query
# query = firestore.col_group "cities"
#
# query.get do |city|
# puts "#{city.document_id} has #{city[:population]} residents."
# end
#
def col_group collection_id
if collection_id.include? "/"
raise ArgumentError, "Invalid collection_id: '#{collection_id}', " \
"must not contain '/'."
end
query = Google::Firestore::V1::StructuredQuery.new(
from: [
Google::Firestore::V1::StructuredQuery::CollectionSelector.new(
collection_id: collection_id, all_descendants: true
)
]
)

Query.start query, service.documents_path, self
end
alias collection_group col_group

##
# Retrieves a document reference.
#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module Firestore
##
# # CollectionReference
#
# A collection reference object ise used for adding documents, getting
# A collection reference object is used for adding documents, getting
# document references, and querying for documents (See {Query}).
#
# @example
Expand Down
2 changes: 1 addition & 1 deletion google-cloud-firestore/lib/google/cloud/firestore/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Query
attr_accessor :parent_path

##
# @private The Google::Firestore::V1::Query object.
# @private The Google::Firestore::V1::StructuredQuery object.
attr_accessor :query

##
Expand Down
8 changes: 8 additions & 0 deletions google-cloud-firestore/support/doctest_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ def mock_firestore
doctest.skip "Google::Cloud::Firestore::Client#collections"
doctest.skip "Google::Cloud::Firestore::Client#list_collections"

doctest.before "Google::Cloud::Firestore::Client#col_group" do
mock_firestore do |mock|
mock.expect :run_query, run_query_resp, run_query_args
end
end
# Skip aliased methods
doctest.skip "Google::Cloud::Firestore::Client#collection_group"

doctest.before "Google::Cloud::Firestore::Client#docs" do
mock_firestore do |mock|
mock.expect :run_query, run_query_resp, run_query_args
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require "helper"

describe Google::Cloud::Firestore::Client, :col_group, :mock_firestore do
let(:collection_id) { "my-collection-id" }
let(:collection_id_bad) { "a/b/my-collection-id" }

it "creates a collection group query" do
query = firestore.col_group(collection_id).where "foo", "==", "bar"

query.must_be_kind_of Google::Cloud::Firestore::Query
query_gapi = query.query
query_gapi.must_be_kind_of Google::Firestore::V1::StructuredQuery
query_gapi.from.size.must_equal 1
query_gapi.from.first.must_be_kind_of Google::Firestore::V1::StructuredQuery::CollectionSelector
query_gapi.from.first.all_descendants.must_equal true
query_gapi.from.first.collection_id.must_equal collection_id
end

it "creates a collection group query using collection_group alias" do
query = firestore.collection_group(collection_id).where "foo", "==", "bar"

query.must_be_kind_of Google::Cloud::Firestore::Query
query_gapi = query.query
query_gapi.must_be_kind_of Google::Firestore::V1::StructuredQuery
query_gapi.from.size.must_equal 1
query_gapi.from.first.must_be_kind_of Google::Firestore::V1::StructuredQuery::CollectionSelector
query_gapi.from.first.all_descendants.must_equal true
query_gapi.from.first.collection_id.must_equal collection_id
end

it "raises when collection_id contains a forward slash" do
error = expect do
firestore.col_group collection_id_bad
end.must_raise ArgumentError
error.message.must_equal "Invalid collection_id: 'a/b/my-collection-id', must not contain '/'."
end
end