google-cloud-firestore 2.7.2 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
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