google-cloud-firestore 2.7.2 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cb9b1838e2d0de1dd7a122b6395a7506e1c9ffdc237edc88ece03b38902694d
4
- data.tar.gz: 92cd04357a9295dec00b34c748cf0e1a9b7f44165915d41e8dac3b5eff40ef84
3
+ metadata.gz: 11756f53d816d451097f95dfbddda050a3cbdcaf5b732f227e7c2eed5770300b
4
+ data.tar.gz: 4b140ef2eefbc2c4f990b27b340fd39f30eb395e25173093b1d041754c2ac969
5
5
  SHA512:
6
- metadata.gz: 3fc0b6e13ba161fc579a969fdf3848621d8aaf45bdd6e64fa5ad2dc2a4d4efd4a934034724f03f7bdc7b2fb50a4976ea3c3fb6dbc99576ef274bbf435e1d0e02
7
- data.tar.gz: 465c31a8a2a581f90107b305d0d465e4e8c4da368c5b00651075b45031708a38e47e3e7a2deae8bacae32b8f8779ca164b68a5a99dfec1f6d0832d1f70d67089
6
+ metadata.gz: 2961e4c3898492087255b15223c22136a30fa08984106c48e66fc8afc2ab6410df31d83ea4d96c03f484c0d1c6a093d8d976b192b03554c158a8880714b9d8e8
7
+ data.tar.gz: d9bc499cac0f2fa388ed139a50ddc1bc6ef380991a57a5cd8b38973b677b1f609d9d2c862472427870112690a28cf63a802a1be9cd2508a34ccf9da9734029d7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Release History
2
2
 
3
+ ### 2.10.0 (2023-02-09)
4
+
5
+ #### Features
6
+
7
+ * Added support for multiple databases ([#20029](https://github.com/googleapis/google-cloud-ruby/issues/20029))
8
+
9
+ ### 2.9.1 (2023-02-03)
10
+
11
+ #### Bug Fixes
12
+
13
+ * Change "aggregate_alias" to optional param ([#20082](https://github.com/googleapis/google-cloud-ruby/issues/20082))
14
+
15
+ ### 2.9.0 (2023-01-26)
16
+
17
+ #### Features
18
+
19
+ * Added support for read time ([#19851](https://github.com/googleapis/google-cloud-ruby/issues/19851))
20
+
21
+ ### 2.8.0 (2023-01-05)
22
+
23
+ #### Features
24
+
25
+ * Support query count for Firestore ([#19457](https://github.com/googleapis/google-cloud-ruby/issues/19457))
26
+ #### Bug Fixes
27
+
28
+ * Add support for merging null field in a document ([#19918](https://github.com/googleapis/google-cloud-ruby/issues/19918))
29
+
3
30
  ### 2.7.2 (2022-08-24)
4
31
 
5
32
  #### Documentation
data/LOGGING.md CHANGED
@@ -3,7 +3,7 @@
3
3
  To enable logging for this library, set the logger for the underlying
4
4
  [gRPC](https://github.com/grpc/grpc/tree/master/src/ruby) library. The logger
5
5
  that you set may be a Ruby stdlib
6
- [`Logger`](https://ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html) as
6
+ [`Logger`](https://ruby-doc.org/current/stdlibs/logger/Logger.html) as
7
7
  shown below, or a
8
8
  [`Google::Cloud::Logging::Logger`](https://googleapis.dev/ruby/google-cloud-logging/latest)
9
9
  that will write logs to [Stackdriver
@@ -0,0 +1,202 @@
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "google/cloud/firestore/v1"
16
+ require "google/cloud/firestore/aggregate_query_snapshot"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Firestore
21
+ ##
22
+ # # AggregateQuery
23
+ #
24
+ # An aggregate query can be used to fetch aggregate values (ex: count) for a query
25
+ #
26
+ # Instances of this class are immutable. All methods that refine the aggregate query
27
+ # return new instances.
28
+ #
29
+ # @example
30
+ # require "google/cloud/firestore"
31
+ #
32
+ # firestore = Google::Cloud::Firestore.new
33
+ #
34
+ # query = firestore.col "cities"
35
+ #
36
+ # # Create an aggregate query
37
+ # aggregate_query = query.aggregate_query
38
+ # .add_count
39
+ #
40
+ # aggregate_query.get do |aggregate_snapshot|
41
+ # puts aggregate_snapshot.get
42
+ # end
43
+ #
44
+ # @example Alias an aggregate query
45
+ # require "google/cloud/firestore"
46
+ #
47
+ # firestore = Google::Cloud::Firestore.new
48
+ #
49
+ # # Create a query
50
+ # query = firestore.col "cities"
51
+ #
52
+ # # Create an aggregate query
53
+ # aggregate_query = query.aggregate_query
54
+ # .add_count aggregate_alias: 'total_cities'
55
+ #
56
+ # aggregate_query.get do |aggregate_snapshot|
57
+ # puts aggregate_snapshot.get('total_cities')
58
+ # end
59
+ #
60
+ class AggregateQuery
61
+ ##
62
+ # @private The firestore client object.
63
+ attr_accessor :client
64
+
65
+ ##
66
+ # @private The type for limit queries.
67
+ attr_reader :parent_path
68
+
69
+ ##
70
+ # @private The Google::Cloud::Firestore::V1::StructuredQuery object.
71
+ attr_reader :query
72
+
73
+ ##
74
+ # @private Array of Google::Cloud::Firestore::V1::StructuredAggregationQuery::Aggregation objects
75
+ attr_reader :aggregates
76
+
77
+ ##
78
+ # @private Creates a new AggregateQuery
79
+ def initialize query, parent_path, client, aggregates: []
80
+ @query = query
81
+ @parent_path = parent_path
82
+ @aggregates = aggregates
83
+ @client = client
84
+ end
85
+
86
+ ##
87
+ # Adds a count aggregate.
88
+ #
89
+ # @param [aggregate_alias] Alias to refer to the aggregate. Optional
90
+ #
91
+ # @return [AggregateQuery] A new aggregate query with the added count aggregate.
92
+ #
93
+ # @example
94
+ # require "google/cloud/firestore"
95
+ #
96
+ # firestore = Google::Cloud::Firestore.new
97
+ #
98
+ # query = firestore.col "cities"
99
+ #
100
+ # # Create an aggregate query
101
+ # aggregate_query = query.aggregate_query
102
+ # .add_count
103
+ #
104
+ # aggregate_query.get do |aggregate_snapshot|
105
+ # puts aggregate_snapshot.get
106
+ # end
107
+ #
108
+ def add_count aggregate_alias: nil
109
+ aggregate_alias ||= ALIASES[:count]
110
+ new_aggregates = @aggregates.dup
111
+ new_aggregates << StructuredAggregationQuery::Aggregation.new(
112
+ count: StructuredAggregationQuery::Aggregation::Count.new,
113
+ alias: aggregate_alias
114
+ )
115
+ AggregateQuery.start query, new_aggregates, parent_path, client
116
+ end
117
+
118
+ ##
119
+ # Retrieves aggregate snapshot for the query.
120
+ #
121
+ # @yield [snapshot] The block for accessing the aggregate query snapshots.
122
+ # @yieldparam [AggregateQuerySnapshot] An aggregate query snapshot.
123
+ #
124
+ # @return [Enumerator<AggregateQuerySnapshot>] A list of aggregate query snapshots.
125
+ #
126
+ # @example
127
+ # require "google/cloud/firestore"
128
+ #
129
+ # firestore = Google::Cloud::Firestore.new
130
+ #
131
+ # query = firestore.col "cities"
132
+ #
133
+ # # Create an aggregate query
134
+ # aggregate_query = query.aggregate_query
135
+ # .add_count
136
+ #
137
+ # aggregate_query.get do |aggregate_snapshot|
138
+ # puts aggregate_snapshot.get
139
+ # end
140
+ #
141
+ def get
142
+ ensure_service!
143
+
144
+ return enum_for :get unless block_given?
145
+
146
+ responses = service.run_aggregate_query @parent_path, structured_aggregation_query
147
+ responses.each do |response|
148
+ next if response.result.nil?
149
+ yield AggregateQuerySnapshot.from_run_aggregate_query_response response
150
+ end
151
+ end
152
+
153
+ ##
154
+ # @private Creates a Google::Cloud::Firestore::V1::StructuredAggregationQuery object
155
+ def structured_aggregation_query
156
+ StructuredAggregationQuery.new(
157
+ structured_query: @query,
158
+ aggregations: @aggregates
159
+ )
160
+ end
161
+
162
+ ##
163
+ # @private Start a new AggregateQuery.
164
+ def self.start query, aggregates, parent_path, client
165
+ new query, parent_path, client, aggregates: aggregates
166
+ end
167
+
168
+ protected
169
+
170
+ ##
171
+ # @private
172
+ StructuredAggregationQuery = Google::Cloud::Firestore::V1::StructuredAggregationQuery
173
+
174
+ ##
175
+ # @private
176
+ ALIASES = {
177
+ count: "count"
178
+ }.freeze
179
+
180
+ ##
181
+ # @private Raise an error unless a database is available.
182
+ def ensure_client!
183
+ raise "Must have active connection to service" unless client
184
+ end
185
+
186
+ ##
187
+ # @private Raise an error unless an active connection to the service
188
+ # is available.
189
+ def ensure_service!
190
+ raise "Must have active connection to service" unless service
191
+ end
192
+
193
+ ##
194
+ # @private The Service object.
195
+ def service
196
+ ensure_client!
197
+ client.service
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,120 @@
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ module Cloud
18
+ module Firestore
19
+ ##
20
+ # # AggregateQuerySnapshot
21
+ #
22
+ # An aggregate query snapshot object is an immutable representation for
23
+ # an aggregate query result.
24
+ #
25
+ # @example
26
+ # require "google/cloud/firestore"
27
+ #
28
+ # firestore = Google::Cloud::Firestore.new
29
+ #
30
+ # query = firestore.col "cities"
31
+ #
32
+ # # Create an aggregate query
33
+ # aggregate_query = query.aggregate_query
34
+ # .add_count
35
+ #
36
+ # aggregate_query.get do |aggregate_snapshot|
37
+ # puts aggregate_snapshot.get
38
+ # end
39
+ # @return [Integer] The aggregate value.
40
+ #
41
+ # @example Alias an aggregate query
42
+ # require "google/cloud/firestore"
43
+ #
44
+ # firestore = Google::Cloud::Firestore.new
45
+ #
46
+ # query = firestore.col "cities"
47
+ #
48
+ # # Create an aggregate query
49
+ # aggregate_query = query.aggregate_query
50
+ # .add_count aggregate_alias: 'total'
51
+ #
52
+ # aggregate_query.get do |aggregate_snapshot|
53
+ # puts aggregate_snapshot.get('total')
54
+ # end
55
+ class AggregateQuerySnapshot
56
+ ##
57
+ # Retrieves the aggregate data.
58
+ #
59
+ # @param aggregate_alias [String] The alias used to access
60
+ # the aggregate value. For an AggregateQuery with a
61
+ # single aggregate field, this parameter can be omitted.
62
+ #
63
+ # @return [Integer] The aggregate value.
64
+ #
65
+ # @example
66
+ # require "google/cloud/firestore"
67
+ #
68
+ # firestore = Google::Cloud::Firestore.new
69
+ #
70
+ # query = firestore.col "cities"
71
+ #
72
+ # # Create an aggregate query
73
+ # aggregate_query = query.aggregate_query
74
+ # .add_count
75
+ #
76
+ # aggregate_query.get do |aggregate_snapshot|
77
+ # puts aggregate_snapshot.get
78
+ # end
79
+ # @return [Integer] The aggregate value.
80
+ #
81
+ # @example Alias an aggregate query
82
+ # require "google/cloud/firestore"
83
+ #
84
+ # firestore = Google::Cloud::Firestore.new
85
+ #
86
+ # query = firestore.col "cities"
87
+ #
88
+ # # Create an aggregate query
89
+ # aggregate_query = query.aggregate_query
90
+ # .add_count aggregate_alias: 'total'
91
+ #
92
+ # aggregate_query.get do |aggregate_snapshot|
93
+ # puts aggregate_snapshot.get('total')
94
+ # end
95
+ def get aggregate_alias = nil
96
+ if @aggregate_fields.count > 1 && aggregate_alias.nil?
97
+ raise ArgumentError, "Required param aggregate_alias for AggregateQuery with multiple aggregate fields"
98
+ end
99
+ aggregate_alias ||= @aggregate_fields.keys.first
100
+ @aggregate_fields[aggregate_alias]
101
+ end
102
+
103
+ ##
104
+ # @private New AggregateQuerySnapshot from a
105
+ # Google::Cloud::Firestore::V1::RunAggregationQueryResponse object.
106
+ def self.from_run_aggregate_query_response response
107
+ aggregate_fields = response
108
+ .result
109
+ .aggregate_fields
110
+ .to_h # convert from protobuf to ruby map
111
+ .transform_values { |v| v[:integer_value] }
112
+
113
+ new.tap do |s|
114
+ s.instance_variable_set :@aggregate_fields, aggregate_fields
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -69,7 +69,7 @@ module Google
69
69
  #
70
70
  # @return [String] database identifier.
71
71
  def database_id
72
- "(default)"
72
+ service.database
73
73
  end
74
74
 
75
75
  ##
@@ -85,6 +85,9 @@ module Google
85
85
  ##
86
86
  # Retrieves an enumerator for the root collections.
87
87
  #
88
+ # @param [Time] read_time Reads documents as they were at the given time.
89
+ # This may not be older than 270 seconds. Optional
90
+ #
88
91
  # @yield [collections] The block for accessing the collections.
89
92
  # @yieldparam [CollectionReference] collection A collection reference object.
90
93
  #
@@ -101,10 +104,21 @@ module Google
101
104
  # puts col.collection_id
102
105
  # end
103
106
  #
104
- def cols &block
107
+ # @example
108
+ # require "google/cloud/firestore"
109
+ #
110
+ # firestore = Google::Cloud::Firestore.new
111
+ # read_time = Time.now
112
+ #
113
+ # # Get the root collections
114
+ # firestore.cols(read_time: read_time).each do |col|
115
+ # puts col.collection_id
116
+ # end
117
+ #
118
+ def cols read_time: nil, &block
105
119
  ensure_service!
106
- grpc = service.list_collections "#{path}/documents"
107
- cols_enum = CollectionReferenceList.from_grpc(grpc, self, "#{path}/documents").all
120
+ grpc = service.list_collections "#{path}/documents", read_time: read_time
121
+ cols_enum = CollectionReferenceList.from_grpc(grpc, self, "#{path}/documents", read_time: read_time).all
108
122
  cols_enum.each(&block) if block_given?
109
123
  cols_enum
110
124
  end
@@ -217,6 +231,8 @@ module Google
217
231
  # individual fields joined by ".". Fields containing `~`, `*`, `/`,
218
232
  # `[`, `]`, and `.` cannot be in a dotted string, and should provided
219
233
  # using a {FieldPath} object instead. (See {#field_path}.)
234
+ # @param [Time] read_time Reads documents as they were at the given time.
235
+ # This may not be older than 270 seconds. Optional
220
236
  #
221
237
  # @yield [documents] The block for accessing the document snapshots.
222
238
  # @yieldparam [DocumentSnapshot] document A document snapshot.
@@ -245,11 +261,24 @@ module Google
245
261
  # puts "#{city.document_id} has #{city[:population]} residents."
246
262
  # end
247
263
  #
248
- def get_all *docs, field_mask: nil
264
+ # @example Get docs using a read_time:
265
+ # require "google/cloud/firestore"
266
+ #
267
+ # firestore = Google::Cloud::Firestore.new
268
+ #
269
+ # read_time = Time.now
270
+ #
271
+ # # Get and print city documents
272
+ # cities = ["cities/NYC", "cities/SF", "cities/LA"]
273
+ # firestore.get_all(cities, read_time: read_time).each do |city|
274
+ # puts "#{city.document_id} has #{city[:population]} residents."
275
+ # end
276
+ #
277
+ def get_all *docs, field_mask: nil, read_time: nil
249
278
  ensure_service!
250
279
 
251
280
  unless block_given?
252
- return enum_for :get_all, *docs, field_mask: field_mask
281
+ return enum_for :get_all, *docs, field_mask: field_mask, read_time: read_time
253
282
  end
254
283
 
255
284
  doc_paths = Array(docs).flatten.map do |doc_path|
@@ -264,7 +293,7 @@ module Google
264
293
  end
265
294
  mask = nil if mask.empty?
266
295
 
267
- results = service.get_documents doc_paths, mask: mask
296
+ results = service.get_documents doc_paths, mask: mask, read_time: read_time
268
297
  results.each do |result|
269
298
  next if result.result.nil?
270
299
  yield DocumentSnapshot.from_batch_result result, self
@@ -668,13 +697,54 @@ module Google
668
697
  end
669
698
  end
670
699
 
700
+ ##
701
+ # Create a transaction to perform multiple reads that are
702
+ # executed atomically at a single logical point in time in a database.
703
+ #
704
+ # All changes are accumulated in memory until the block completes.
705
+ # Transactions will be automatically retried when documents change
706
+ # before the transaction is committed. See {Transaction}.
707
+ #
708
+ # @see https://firebase.google.com/docs/firestore/manage-data/transactions
709
+ # Transactions and Batched Writes
710
+ #
711
+ # @param [Time] read_time The maximum number of retries for
712
+ # transactions failed due to errors. Default is 5. Optional.
713
+ #
714
+ # @yield [transaction] The block for reading data.
715
+ # @yieldparam [Transaction] transaction The transaction object for
716
+ # making changes.
717
+ #
718
+ # @return [Object] The return value of the provided
719
+ # yield block
720
+ #
721
+ # @example Read only transaction with read time
722
+ # require "google/cloud/firestore"
723
+ #
724
+ # firestore = Google::Cloud::Firestore.new
725
+ #
726
+ # # Get a document reference
727
+ # nyc_ref = firestore.doc "cities/NYC"
728
+ #
729
+ # read_time = Time.now
730
+ #
731
+ # firestore.read_only_transaction(read_time: read_time) do |tx|
732
+ # # Get a document snapshot
733
+ # nyc_snap = tx.get nyc_ref
734
+ # end
735
+ #
736
+ def read_only_transaction read_time: nil
737
+ transaction = Transaction.from_client self, read_time: read_time, read_only: true
738
+ yield transaction
739
+ end
740
+
671
741
  # @!endgroup
672
742
 
673
743
  # @private
674
- def list_documents parent, collection_id, token: nil, max: nil
744
+ def list_documents parent, collection_id, token: nil, max: nil, read_time: nil
675
745
  ensure_service!
676
- grpc = service.list_documents parent, collection_id, token: token, max: max
677
- DocumentReference::List.from_grpc grpc, self, parent, collection_id
746
+ grpc = service.list_documents parent, collection_id, token: token, max: max, read_time: read_time
747
+ DocumentReference::List.from_grpc grpc, self, parent, collection_id, read_time: read_time
678
748
  end
679
749
 
680
750
  protected
@@ -48,6 +48,8 @@ module Google
48
48
  #
49
49
  # @param [Integer] partition_count The desired maximum number of partition points. The number must be strictly
50
50
  # positive. The actual number of partitions returned may be fewer.
51
+ # @param [Time] read_time Reads documents as they were at the given time.
52
+ # This may not be older than 270 seconds. Optional
51
53
  #
52
54
  # @return [Array<QueryPartition>] An ordered array of query partitions.
53
55
  #
@@ -62,7 +64,20 @@ module Google
62
64
  #
63
65
  # queries = partitions.map(&:to_query)
64
66
  #
65
- def partitions partition_count
67
+ # @example partition with read time
68
+ # require "google/cloud/firestore"
69
+ #
70
+ # firestore = Google::Cloud::Firestore.new
71
+ #
72
+ # col_group = firestore.col_group "cities"
73
+ #
74
+ # read_time = Time.now
75
+ #
76
+ # partitions = col_group.partitions 3, read_time: read_time
77
+ #
78
+ # queries = partitions.map(&:to_query)
79
+ #
80
+ def partitions partition_count, read_time: nil
66
81
  ensure_service!
67
82
 
68
83
  raise ArgumentError, "partition_count must be > 0" unless partition_count.positive?
@@ -75,7 +90,7 @@ module Google
75
90
 
76
91
  grpc_partitions = if partition_count.positive?
77
92
  # Retrieve all pages, since cursor order is not guaranteed and they must be sorted.
78
- list_all partition_count, query_with_default_order
93
+ list_all partition_count, query_with_default_order, read_time
79
94
  else
80
95
  [] # Ensure that a single, empty QueryPartition is returned.
81
96
  end
@@ -118,11 +133,12 @@ module Google
118
133
 
119
134
  protected
120
135
 
121
- def list_all partition_count, query_with_default_order
136
+ def list_all partition_count, query_with_default_order, read_time
122
137
  grpc_partitions = []
123
138
  token = nil
124
139
  loop do
125
- grpc = service.partition_query parent_path, query_with_default_order.query, partition_count, token: token
140
+ grpc = service.partition_query parent_path, query_with_default_order.query, partition_count,
141
+ token: token, read_time: read_time
126
142
  grpc_partitions += Array(grpc.partitions)
127
143
  token = grpc.next_page_token
128
144
  token = nil if token == ""
@@ -147,6 +147,8 @@ module Google
147
147
  # @param [String] token A previously-returned page token representing
148
148
  # part of the larger set of results to view.
149
149
  # @param [Integer] max Maximum number of results to return.
150
+ # @param [Time] read_time Reads documents as they were at the given time.
151
+ # This may not be older than 270 seconds. Optional
150
152
  #
151
153
  # @return [Array<DocumentReference>] An array of document references.
152
154
  #
@@ -161,10 +163,23 @@ module Google
161
163
  # puts doc_ref.document_id
162
164
  # end
163
165
  #
164
- def list_documents token: nil, max: nil
166
+ # @example List documents with read time
167
+ # require "google/cloud/firestore"
168
+ #
169
+ # firestore = Google::Cloud::Firestore.new
170
+ #
171
+ # read_time = Time.now
172
+ #
173
+ # col = firestore.col "cities"
174
+ #
175
+ # col.list_documents(read_time: read_time).each do |doc_ref|
176
+ # puts doc_ref.document_id
177
+ # end
178
+ #
179
+ def list_documents token: nil, max: nil, read_time: nil
165
180
  ensure_client!
166
181
  client.list_documents \
167
- parent_path, collection_id, token: token, max: max
182
+ parent_path, collection_id, token: token, max: max, read_time: read_time
168
183
  end
169
184
 
170
185
  ##
@@ -52,8 +52,8 @@ module Google
52
52
  def next
53
53
  return nil unless next?
54
54
  ensure_service!
55
- grpc = @client.service.list_collections @parent, token: token, max: @max
56
- self.class.from_grpc grpc, @client, @parent, max: @max
55
+ grpc = @client.service.list_collections @parent, token: token, max: @max, read_time: @read_time
56
+ self.class.from_grpc grpc, @client, @parent, max: @max, read_time: @read_time
57
57
  end
58
58
 
59
59
  ##
@@ -110,7 +110,7 @@ module Google
110
110
  ##
111
111
  # @private New CollectionReference::List from a `Google::Cloud::Firestore::V1::ListCollectionIdsResponse`
112
112
  # object.
113
- def self.from_grpc grpc, client, parent, max: nil
113
+ def self.from_grpc grpc, client, parent, max: nil, read_time: nil
114
114
  raise ArgumentError, "parent is required" unless parent
115
115
  cols = CollectionReferenceList.new(Array(grpc.collection_ids).map do |collection_id|
116
116
  CollectionReference.from_path "#{parent}/#{collection_id}", client
@@ -121,6 +121,7 @@ module Google
121
121
  cols.instance_variable_set :@client, client
122
122
  cols.instance_variable_set :@parent, parent
123
123
  cols.instance_variable_set :@max, max
124
+ cols.instance_variable_set :@read_time, read_time
124
125
  cols
125
126
  end
126
127
 
@@ -498,7 +498,6 @@ module Google
498
498
  dup_hash = dup_hash[field]
499
499
  end
500
500
  prev_hash[last_field] = dup_hash
501
- prev_hash.delete_if { |_k, v| v.nil? }
502
501
  ret_hash
503
502
  end
504
503
 
@@ -86,8 +86,9 @@ module Google
86
86
  def next
87
87
  return nil unless next?
88
88
  ensure_client!
89
- grpc = @client.service.list_documents @parent, @collection_id, token: token, max: @max
90
- self.class.from_grpc grpc, @client, @parent, @collection_id, @max
89
+ grpc = @client.service.list_documents @parent, @collection_id, token: token, max: @max, \
90
+ read_time: @read_time
91
+ self.class.from_grpc grpc, @client, @parent, @collection_id, @max, read_time: @read_time
91
92
  end
92
93
 
93
94
  ##
@@ -162,7 +163,7 @@ module Google
162
163
  ##
163
164
  # @private New DocumentReference::List from a
164
165
  # Google::Cloud::Firestore::V1::ListDocumentsResponse object.
165
- def self.from_grpc grpc, client, parent, collection_id, max = nil
166
+ def self.from_grpc grpc, client, parent, collection_id, max = nil, read_time: nil
166
167
  documents = List.new(Array(grpc.documents).map do |document|
167
168
  DocumentReference.from_path document.name, client
168
169
  end)
@@ -173,6 +174,7 @@ module Google
173
174
  documents.instance_variable_set :@token, token
174
175
  documents.instance_variable_set :@client, client
175
176
  documents.instance_variable_set :@max, max
177
+ documents.instance_variable_set :@read_time, read_time
176
178
  documents
177
179
  end
178
180
 
@@ -73,6 +73,9 @@ module Google
73
73
  ##
74
74
  # Retrieves an enumerator for the collections nested under the document snapshot.
75
75
  #
76
+ # @param [Time] read_time Reads documents as they were at the given time.
77
+ # This may not be older than 270 seconds. Optional
78
+ #
76
79
  # @yield [collections] The block for accessing the collections.
77
80
  # @yieldparam [CollectionReference] collection A collection reference object.
78
81
  #
@@ -91,10 +94,24 @@ module Google
91
94
  # puts col.collection_id
92
95
  # end
93
96
  #
94
- def cols &block
97
+ # @example Get collection with read time
98
+ # require "google/cloud/firestore"
99
+ #
100
+ # firestore = Google::Cloud::Firestore.new
101
+ #
102
+ # read_time = Time.now
103
+ #
104
+ # # Get a document reference
105
+ # nyc_ref = firestore.doc "cities/NYC"
106
+ #
107
+ # nyc_ref.cols(read_time: read_time).each do |col|
108
+ # puts col.collection_id
109
+ # end
110
+ #
111
+ def cols read_time: nil, &block
95
112
  ensure_service!
96
- grpc = service.list_collections path
97
- cols_enum = CollectionReferenceList.from_grpc(grpc, client, path).all
113
+ grpc = service.list_collections path, read_time: read_time
114
+ cols_enum = CollectionReferenceList.from_grpc(grpc, client, path, read_time: read_time).all
98
115
  cols_enum.each(&block) if block_given?
99
116
  cols_enum
100
117
  end
@@ -17,6 +17,7 @@ require "google/cloud/firestore/v1"
17
17
  require "google/cloud/firestore/document_snapshot"
18
18
  require "google/cloud/firestore/query_listener"
19
19
  require "google/cloud/firestore/convert"
20
+ require "google/cloud/firestore/aggregate_query"
20
21
  require "json"
21
22
 
22
23
  module Google
@@ -903,6 +904,9 @@ module Google
903
904
  ##
904
905
  # Retrieves document snapshots for the query.
905
906
  #
907
+ # @param [Time] read_time Reads documents as they were at the given time.
908
+ # This may not be older than 270 seconds. Optional
909
+ #
906
910
  # @yield [documents] The block for accessing the document snapshots.
907
911
  # @yieldparam [DocumentSnapshot] document A document snapshot.
908
912
  #
@@ -923,12 +927,29 @@ module Google
923
927
  # puts "#{city.document_id} has #{city[:population]} residents."
924
928
  # end
925
929
  #
926
- def get
930
+ # @example Get query with read time
931
+ # require "google/cloud/firestore"
932
+ #
933
+ # firestore = Google::Cloud::Firestore.new
934
+ #
935
+ # # Get a collection reference
936
+ # cities_col = firestore.col "cities"
937
+ #
938
+ # # Create a query
939
+ # query = cities_col.select(:population)
940
+ #
941
+ # read_time = Time.now
942
+ #
943
+ # query.get(read_time: read_time) do |city|
944
+ # puts "#{city.document_id} has #{city[:population]} residents."
945
+ # end
946
+ #
947
+ def get read_time: nil
927
948
  ensure_service!
928
949
 
929
- return enum_for :get unless block_given?
950
+ return enum_for :get, read_time: read_time unless block_given?
930
951
 
931
- results = service.run_query parent_path, @query
952
+ results = service.run_query parent_path, @query, read_time: read_time
932
953
 
933
954
  # Reverse the results for Query#limit_to_last queries since that method reversed the order_by directions.
934
955
  results = results.to_a.reverse if limit_type == :last
@@ -940,6 +961,26 @@ module Google
940
961
  end
941
962
  alias run get
942
963
 
964
+ ##
965
+ # Creates an AggregateQuery object for the query.
966
+ #
967
+ # @return [AggregateQuery] New empty aggregate query.
968
+ #
969
+ # @example
970
+ # require "google/cloud/firestore"
971
+ #
972
+ # firestore = Google::Cloud::Firestore.new
973
+ #
974
+ # # Get a collection reference
975
+ # query = firestore.col "cities"
976
+ #
977
+ # # Create an aggregate query
978
+ # aggregate_query = query.aggregate_query
979
+ #
980
+ def aggregate_query
981
+ AggregateQuery.new query, parent_path, client
982
+ end
983
+
943
984
  ##
944
985
  # Listen to this query for changes.
945
986
  #
@@ -30,14 +30,16 @@ module Google
30
30
  attr_accessor :credentials
31
31
  attr_accessor :timeout
32
32
  attr_accessor :host
33
+ attr_accessor :database
33
34
 
34
35
  ##
35
36
  # Creates a new Service instance.
36
- def initialize project, credentials, host: nil, timeout: nil
37
+ def initialize project, credentials, host: nil, timeout: nil, database: nil
37
38
  @project = project
38
39
  @credentials = credentials
39
40
  @host = host
40
41
  @timeout = timeout
42
+ @database = database
41
43
  end
42
44
 
43
45
  def firestore
@@ -48,11 +50,11 @@ module Google
48
50
  config.endpoint = host if host
49
51
  config.lib_name = "gccl"
50
52
  config.lib_version = Google::Cloud::Firestore::VERSION
51
- config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}/databases/(default)" }
53
+ config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}/databases/#{@database}" }
52
54
  end
53
55
  end
54
56
 
55
- def get_documents document_paths, mask: nil, transaction: nil
57
+ def get_documents document_paths, mask: nil, transaction: nil, read_time: nil
56
58
  batch_get_req = {
57
59
  database: database_path,
58
60
  documents: document_paths,
@@ -63,7 +65,9 @@ module Google
63
65
  elsif transaction
64
66
  batch_get_req[:new_transaction] = transaction
65
67
  end
66
-
68
+ if read_time
69
+ batch_get_req[:read_time] = read_time_to_timestamp(read_time)
70
+ end
67
71
  firestore.batch_get_documents batch_get_req, call_options(parent: database_path)
68
72
  end
69
73
 
@@ -74,23 +78,25 @@ module Google
74
78
  # the showMissing flag to true to support full document traversal. If
75
79
  # there are too many documents, recommendation will be not to call this
76
80
  # method.
77
- def list_documents parent, collection_id, token: nil, max: nil
81
+ def list_documents parent, collection_id, token: nil, max: nil, read_time: nil
78
82
  mask = { field_paths: [] }
79
- paged_enum = firestore.list_documents parent: parent,
83
+ paged_enum = firestore.list_documents parent: parent,
80
84
  collection_id: collection_id,
81
- page_size: max,
82
- page_token: token,
83
- mask: mask,
84
- show_missing: true
85
+ page_size: max,
86
+ page_token: token,
87
+ mask: mask,
88
+ show_missing: true,
89
+ read_time: read_time_to_timestamp(read_time)
85
90
  paged_enum.response
86
91
  end
87
92
 
88
- def list_collections parent, token: nil, max: nil
93
+ def list_collections parent, token: nil, max: nil, read_time: nil
89
94
  firestore.list_collection_ids(
90
95
  {
91
- parent: parent,
92
- page_size: max,
93
- page_token: token
96
+ parent: parent,
97
+ page_size: max,
98
+ page_token: token,
99
+ read_time: read_time_to_timestamp(read_time)
94
100
  },
95
101
  call_options(parent: database_path)
96
102
  )
@@ -98,19 +104,20 @@ module Google
98
104
 
99
105
  ##
100
106
  # Returns Google::Cloud::Firestore::V1::PartitionQueryResponse
101
- def partition_query parent, query_grpc, partition_count, token: nil, max: nil
107
+ def partition_query parent, query_grpc, partition_count, token: nil, max: nil, read_time: nil
102
108
  request = Google::Cloud::Firestore::V1::PartitionQueryRequest.new(
103
109
  parent: parent,
104
110
  structured_query: query_grpc,
105
111
  partition_count: partition_count,
106
112
  page_token: token,
107
- page_size: max
113
+ page_size: max,
114
+ read_time: read_time_to_timestamp(read_time)
108
115
  )
109
116
  paged_enum = firestore.partition_query request
110
117
  paged_enum.response
111
118
  end
112
119
 
113
- def run_query path, query_grpc, transaction: nil
120
+ def run_query path, query_grpc, transaction: nil, read_time: nil
114
121
  run_query_req = {
115
122
  parent: path,
116
123
  structured_query: query_grpc
@@ -120,10 +127,28 @@ module Google
120
127
  elsif transaction
121
128
  run_query_req[:new_transaction] = transaction
122
129
  end
130
+ if read_time
131
+ run_query_req[:read_time] = read_time_to_timestamp(read_time)
132
+ end
123
133
 
124
134
  firestore.run_query run_query_req, call_options(parent: database_path)
125
135
  end
126
136
 
137
+ ##
138
+ # Returns Google::Cloud::Firestore::V1::RunAggregationQueryResponse
139
+ def run_aggregate_query parent, structured_aggregation_query, transaction: nil
140
+ request = Google::Cloud::Firestore::V1::RunAggregationQueryRequest.new(
141
+ parent: parent,
142
+ structured_aggregation_query: structured_aggregation_query
143
+ )
144
+ if transaction.is_a? String
145
+ request.transaction = transaction
146
+ elsif transaction
147
+ request.new_transaction = transaction
148
+ end
149
+ firestore.run_aggregation_query request
150
+ end
151
+
127
152
  def listen enum
128
153
  firestore.listen enum, call_options(parent: database_path)
129
154
  end
@@ -158,18 +183,29 @@ module Google
158
183
  )
159
184
  end
160
185
 
161
- def database_path project_id: project, database_id: "(default)"
186
+ def database_path project_id: project, database_id: database
162
187
  # Originally used V1::FirestoreClient.database_root_path until it was removed in #5405.
163
188
  "projects/#{project_id}/databases/#{database_id}"
164
189
  end
165
190
 
166
- def documents_path project_id: project, database_id: "(default)"
191
+ def documents_path project_id: project, database_id: database
167
192
  # Originally used V1::FirestoreClient.document_root_path until it was removed in #5405.
168
193
  "projects/#{project_id}/databases/#{database_id}/documents"
169
194
  end
170
195
 
171
196
  def inspect
172
- "#{self.class}(#{@project})"
197
+ "#{self.class}(#{@project})(#{@database})"
198
+ end
199
+
200
+ def read_time_to_timestamp read_time
201
+ return nil if read_time.nil?
202
+
203
+ raise TypeError, "read_time is expected to be a Time object" unless read_time.is_a? Time
204
+
205
+ Google::Protobuf::Timestamp.new(
206
+ seconds: read_time.to_i,
207
+ nanos: read_time.usec * 1000
208
+ )
173
209
  end
174
210
 
175
211
  protected
@@ -269,6 +269,51 @@ module Google
269
269
  end
270
270
  alias run get
271
271
 
272
+ ##
273
+ # Retrieves aggregate query snapshots for the given value. Valid values can be
274
+ # a string representing either a document or a collection of documents,
275
+ # a document reference object, a collection reference object, or a query
276
+ # to be run.
277
+ #
278
+ # @param [AggregateQuery] aggregate_query
279
+ # An AggregateQuery object
280
+ #
281
+ # @yield [documents] The block for accessing the aggregate query snapshot.
282
+ # @yieldparam [AggregateQuerySnapshot] aggregate_snapshot An aggregate query snapshot.
283
+ #
284
+ # @example
285
+ # require "google/cloud/firestore"
286
+ #
287
+ # firestore = Google::Cloud::Firestore.new
288
+ #
289
+ # query = firestore.col "cities"
290
+ #
291
+ # # Create an aggregate query
292
+ # aq = query.aggregate_query
293
+ # .add_count
294
+ #
295
+ # firestore.transaction do |tx|
296
+ # tx.get_aggregate aq do |aggregate_snapshot|
297
+ # puts aggregate_snapshot.get
298
+ # end
299
+ # end
300
+ #
301
+ def get_aggregate aggregate_query
302
+ ensure_not_closed!
303
+ ensure_service!
304
+
305
+ return enum_for :get_aggregate, aggregate_query unless block_given?
306
+
307
+ results = service.run_aggregate_query aggregate_query.parent_path,
308
+ aggregate_query.structured_aggregation_query,
309
+ transaction: transaction_or_create
310
+ results.each do |result|
311
+ extract_transaction_from_result! result
312
+ next if result.result.nil?
313
+ yield AggregateQuerySnapshot.from_run_aggregate_query_response result
314
+ end
315
+ end
316
+
272
317
  # @!endgroup
273
318
 
274
319
  # @!group Modifications
@@ -643,10 +688,12 @@ module Google
643
688
 
644
689
  ##
645
690
  # @private New Transaction reference object from a path.
646
- def self.from_client client, previous_transaction: nil
691
+ def self.from_client client, previous_transaction: nil, read_time: nil, read_only: nil
647
692
  new.tap do |s|
648
693
  s.instance_variable_set :@client, client
649
694
  s.instance_variable_set :@previous_transaction, previous_transaction
695
+ s.instance_variable_set :@read_time, read_time
696
+ s.instance_variable_set :@read_only, read_only
650
697
  end
651
698
  end
652
699
 
@@ -699,6 +746,10 @@ module Google
699
746
  ##
700
747
  # @private
701
748
  def transaction_opt
749
+ read_only = \
750
+ Google::Cloud::Firestore::V1::TransactionOptions::ReadOnly.new \
751
+ read_time: service.read_time_to_timestamp(@read_time)
752
+
702
753
  read_write = \
703
754
  Google::Cloud::Firestore::V1::TransactionOptions::ReadWrite.new
704
755
 
@@ -707,9 +758,11 @@ module Google
707
758
  @previous_transaction = nil
708
759
  end
709
760
 
710
- Google::Cloud::Firestore::V1::TransactionOptions.new(
711
- read_write: read_write
712
- )
761
+ if @read_only
762
+ Google::Cloud::Firestore::V1::TransactionOptions.new read_only: read_only
763
+ else
764
+ Google::Cloud::Firestore::V1::TransactionOptions.new read_write: read_write
765
+ end
713
766
  end
714
767
 
715
768
  ##
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module Firestore
19
- VERSION = "2.7.2".freeze
19
+ VERSION = "2.10.0".freeze
20
20
  end
21
21
  end
22
22
  end
@@ -59,6 +59,8 @@ module Google
59
59
  # If the param is nil, uses the default endpoint.
60
60
  # @param [String] emulator_host Firestore emulator host. Optional.
61
61
  # If the param is nil, uses the value of the `emulator_host` config.
62
+ # @param [String] database_id Identifier for a Firestore database. If not
63
+ # present, the default database of the project is used.
62
64
  # @param [String] project Alias for the `project_id` argument. Deprecated.
63
65
  # @param [String] keyfile Alias for the `credentials` argument.
64
66
  # Deprecated.
@@ -76,19 +78,22 @@ module Google
76
78
  timeout: nil,
77
79
  endpoint: nil,
78
80
  emulator_host: nil,
81
+ database_id: nil,
79
82
  project: nil,
80
83
  keyfile: nil
81
- project_id ||= (project || default_project_id)
82
- scope ||= configure.scope
83
- timeout ||= configure.timeout
84
- endpoint ||= configure.endpoint
84
+ project_id ||= (project || default_project_id)
85
+ scope ||= configure.scope
86
+ timeout ||= configure.timeout
87
+ endpoint ||= configure.endpoint
85
88
  emulator_host ||= configure.emulator_host
89
+ database_id ||= configure.database_id
86
90
 
87
91
  if emulator_host
88
92
  project_id = project_id.to_s
89
93
  raise ArgumentError, "project_id is missing" if project_id.empty?
90
94
 
91
- service = Firestore::Service.new project_id, :this_channel_is_insecure, host: emulator_host, timeout: timeout
95
+ service = Firestore::Service.new project_id, :this_channel_is_insecure, host: emulator_host,
96
+ timeout: timeout, database: database_id
92
97
  return Firestore::Client.new service
93
98
  end
94
99
 
@@ -103,7 +108,8 @@ module Google
103
108
  project_id = project_id.to_s
104
109
  raise ArgumentError, "project_id is missing" if project_id.empty?
105
110
 
106
- service = Firestore::Service.new project_id, credentials, host: endpoint, timeout: timeout
111
+ service = Firestore::Service.new project_id, credentials, host: endpoint,
112
+ timeout: timeout, database: database_id
107
113
  Firestore::Client.new service
108
114
  end
109
115
 
@@ -42,6 +42,8 @@ module Google
42
42
  #
43
43
  # * `https://www.googleapis.com/auth/datastore`
44
44
  # @param [Integer] timeout Default timeout to use in requests. Optional.
45
+ # @param [String] database_id Identifier for a Firestore database. If not
46
+ # present, the default database of the project is used.
45
47
  #
46
48
  # @return [Google::Cloud::Firestore::Client]
47
49
  #
@@ -58,8 +60,15 @@ module Google
58
60
  # platform_scope = "https://www.googleapis.com/auth/cloud-platform"
59
61
  # firestore = gcloud.firestore scope: platform_scope
60
62
  #
61
- def firestore scope: nil, timeout: nil
62
- Google::Cloud.firestore @project, @keyfile, scope: scope, timeout: (timeout || @timeout)
63
+ # @example The default database can be overridden with the `database_id` option:
64
+ # require "google/cloud"
65
+ #
66
+ # gcloud = Google::Cloud.new
67
+ # database_id = "my-todo-database"
68
+ # firestore = gcloud.firestore database_id: database_id
69
+ #
70
+ def firestore scope: nil, timeout: nil, database_id: nil
71
+ Google::Cloud.firestore @project, @keyfile, scope: scope, timeout: (timeout || @timeout), database_id: database_id
63
72
  end
64
73
 
65
74
  ##
@@ -83,6 +92,8 @@ module Google
83
92
  #
84
93
  # * `https://www.googleapis.com/auth/datastore`
85
94
  # @param [Integer] timeout Default timeout to use in requests. Optional.
95
+ # @param [String] database_id Identifier for a Firestore database. If not
96
+ # present, the default database of the project is used.
86
97
  #
87
98
  # @return [Google::Cloud::Firestore::Client]
88
99
  #
@@ -91,12 +102,13 @@ module Google
91
102
  #
92
103
  # firestore = Google::Cloud.firestore
93
104
  #
94
- def self.firestore project_id = nil, credentials = nil, scope: nil, timeout: nil
105
+ def self.firestore project_id = nil, credentials = nil, scope: nil, timeout: nil, database_id: nil
95
106
  require "google/cloud/firestore"
96
- Google::Cloud::Firestore.new project_id: project_id,
107
+ Google::Cloud::Firestore.new project_id: project_id,
97
108
  credentials: credentials,
98
- scope: scope,
99
- timeout: timeout
109
+ scope: scope,
110
+ timeout: timeout,
111
+ database_id: database_id
100
112
  end
101
113
  end
102
114
  end
@@ -116,8 +128,7 @@ Google::Cloud.configure.add_config! :firestore do |config|
116
128
  ENV["FIRESTORE_EMULATOR_HOST"]
117
129
  end
118
130
  default_scopes = [
119
- "https://www.googleapis.com/auth/cloud-platform",
120
- "https://www.googleapis.com/auth/datastore"
131
+ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/datastore"
121
132
  ]
122
133
 
123
134
  config.add_field! :project_id, default_project, match: String, allow_nil: true
@@ -129,4 +140,5 @@ Google::Cloud.configure.add_config! :firestore do |config|
129
140
  config.add_field! :timeout, nil, match: Integer
130
141
  config.add_field! :emulator_host, default_emulator, match: String, allow_nil: true
131
142
  config.add_field! :endpoint, "firestore.googleapis.com", match: String
143
+ config.add_field! :database_id, "(default)", match: String
132
144
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-cloud-firestore
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.2
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-24 00:00:00.000000000 Z
11
+ date: 2023-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-cloud-core
@@ -225,6 +225,8 @@ files:
225
225
  - TROUBLESHOOTING.md
226
226
  - lib/google-cloud-firestore.rb
227
227
  - lib/google/cloud/firestore.rb
228
+ - lib/google/cloud/firestore/aggregate_query.rb
229
+ - lib/google/cloud/firestore/aggregate_query_snapshot.rb
228
230
  - lib/google/cloud/firestore/batch.rb
229
231
  - lib/google/cloud/firestore/client.rb
230
232
  - lib/google/cloud/firestore/collection_group.rb
@@ -272,7 +274,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
272
274
  - !ruby/object:Gem::Version
273
275
  version: '0'
274
276
  requirements: []
275
- rubygems_version: 3.3.14
277
+ rubygems_version: 3.4.2
276
278
  signing_key:
277
279
  specification_version: 4
278
280
  summary: API Client library for Google Cloud Firestore API