google-cloud-firestore 2.7.2 → 2.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHENTICATION.md +8 -26
  3. data/CHANGELOG.md +69 -0
  4. data/LOGGING.md +1 -1
  5. data/lib/google/cloud/firestore/aggregate_query.rb +285 -0
  6. data/lib/google/cloud/firestore/aggregate_query_snapshot.rb +145 -0
  7. data/lib/google/cloud/firestore/bulk_commit_batch.rb +73 -0
  8. data/lib/google/cloud/firestore/bulk_writer.rb +558 -0
  9. data/lib/google/cloud/firestore/bulk_writer_exception.rb +40 -0
  10. data/lib/google/cloud/firestore/bulk_writer_operation.rb +126 -0
  11. data/lib/google/cloud/firestore/bulk_writer_scheduler.rb +164 -0
  12. data/lib/google/cloud/firestore/client.rb +161 -10
  13. data/lib/google/cloud/firestore/collection_group.rb +20 -4
  14. data/lib/google/cloud/firestore/collection_reference.rb +17 -2
  15. data/lib/google/cloud/firestore/collection_reference_list.rb +4 -3
  16. data/lib/google/cloud/firestore/convert.rb +6 -7
  17. data/lib/google/cloud/firestore/document_reference/list.rb +5 -3
  18. data/lib/google/cloud/firestore/document_reference.rb +20 -3
  19. data/lib/google/cloud/firestore/document_snapshot.rb +1 -1
  20. data/lib/google/cloud/firestore/errors.rb +60 -0
  21. data/lib/google/cloud/firestore/filter.rb +326 -0
  22. data/lib/google/cloud/firestore/promise/future.rb +97 -0
  23. data/lib/google/cloud/firestore/query.rb +112 -89
  24. data/lib/google/cloud/firestore/rate_limiter.rb +80 -0
  25. data/lib/google/cloud/firestore/service.rb +74 -23
  26. data/lib/google/cloud/firestore/transaction.rb +57 -4
  27. data/lib/google/cloud/firestore/version.rb +1 -1
  28. data/lib/google/cloud/firestore.rb +17 -7
  29. data/lib/google-cloud-firestore.rb +45 -8
  30. metadata +17 -146
@@ -17,6 +17,8 @@ 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"
21
+ require "google/cloud/firestore/filter"
20
22
  require "json"
21
23
 
22
24
  module Google
@@ -209,29 +211,48 @@ module Google
209
211
  end
210
212
 
211
213
  ##
212
- # Filters the query on a field.
213
- #
214
- # @param [FieldPath, String, Symbol] field A field path to filter
215
- # results with.
216
- #
217
- # If a {FieldPath} object is not provided then the field will be
218
- # treated as a dotted string, meaning the string represents individual
219
- # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
220
- # `.` cannot be in a dotted string, and should provided using a
221
- # {FieldPath} object instead.
222
- # @param [String, Symbol] operator The operation to compare the field
223
- # to. Acceptable values include:
224
- #
225
- # * less than: `<`, `lt`
226
- # * less than or equal: `<=`, `lte`
227
- # * greater than: `>`, `gt`
228
- # * greater than or equal: `>=`, `gte`
229
- # * equal: `=`, `==`, `eq`, `eql`, `is`
230
- # * not equal: `!=`
231
- # * in: `in`
232
- # * not in: `not-in`, `not_in`
233
- # * array contains: `array-contains`, `array_contains`
234
- # @param [Object] value A value the field is compared to.
214
+ # Adds filter to the where clause
215
+ #
216
+ # @overload where(filter)
217
+ # Pass Firestore::Filter to `where` via field_or_filter argument.
218
+ #
219
+ # @param filter [::Google::Cloud::Firestore::Filter]
220
+ #
221
+ # @overload where(field, operator, value)
222
+ # Pass arguments to `where` via positional arguments.
223
+ #
224
+ # @param field [FieldPath, String, Symbol] A field path to filter
225
+ # results with.
226
+ # If a {FieldPath} object is not provided then the field will be
227
+ # treated as a dotted string, meaning the string represents individual
228
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
229
+ # `.` cannot be in a dotted string, and should provided using a
230
+ # {FieldPath} object instead.
231
+ #
232
+ # @param operator [String, Symbol] The operation to compare the field
233
+ # to. Acceptable values include:
234
+ # * less than: `<`, `lt`
235
+ # * less than or equal: `<=`, `lte`
236
+ # * greater than: `>`, `gt`
237
+ # * greater than or equal: `>=`, `gte`
238
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
239
+ # * not equal: `!=`
240
+ # * in: `in`
241
+ # * not in: `not-in`, `not_in`
242
+ # * array contains: `array-contains`, `array_contains`
243
+ #
244
+ # @param value [Object] The value to compare the property to. Defaults to nil.
245
+ # Possible values are:
246
+ # * Integer
247
+ # * Float/BigDecimal
248
+ # * String
249
+ # * Boolean
250
+ # * Array
251
+ # * Date/Time
252
+ # * StringIO
253
+ # * Google::Cloud::Datastore::Key
254
+ # * Google::Cloud::Datastore::Entity
255
+ # * nil
235
256
  #
236
257
  # @return [Query] New query with `where` called on it.
237
258
  #
@@ -250,7 +271,25 @@ module Google
250
271
  # puts "#{city.document_id} has #{city[:population]} residents."
251
272
  # end
252
273
  #
253
- def where field, operator, value
274
+ # @example
275
+ # require "google/cloud/firestore"
276
+ #
277
+ # firestore = Google::Cloud::Firestore.new
278
+ #
279
+ # # Get a collection reference
280
+ # cities_col = firestore.col "cities"
281
+ #
282
+ # # Create a filter
283
+ # filter = Filter.create(:population, :>=, 1000000)
284
+ #
285
+ # # Add filter to where clause
286
+ # query = query.where filter
287
+ #
288
+ # query.get do |city|
289
+ # puts "#{city.document_id} has #{city[:population]} residents."
290
+ # end
291
+ #
292
+ def where filter_or_field = nil, operator = nil, value = nil
254
293
  if query_has_cursors?
255
294
  raise "cannot call where after calling " \
256
295
  "start_at, start_after, end_before, or end_at"
@@ -259,10 +298,12 @@ module Google
259
298
  new_query = @query.dup
260
299
  new_query ||= StructuredQuery.new
261
300
 
262
- field = FieldPath.parse field unless field.is_a? FieldPath
263
-
264
- new_filter = filter field.formatted_string, operator, value
265
- add_filters_to_query new_query, new_filter
301
+ if filter_or_field.is_a? Google::Cloud::Firestore::Filter
302
+ new_query.where = filter_or_field.filter
303
+ else
304
+ new_filter = Google::Cloud::Firestore::Filter.new filter_or_field, operator, value
305
+ add_filters_to_query new_query, new_filter.filter
306
+ end
266
307
 
267
308
  Query.start new_query, parent_path, client, limit_type: limit_type
268
309
  end
@@ -903,6 +944,9 @@ module Google
903
944
  ##
904
945
  # Retrieves document snapshots for the query.
905
946
  #
947
+ # @param [Time] read_time Reads documents as they were at the given time.
948
+ # This may not be older than 270 seconds. Optional
949
+ #
906
950
  # @yield [documents] The block for accessing the document snapshots.
907
951
  # @yieldparam [DocumentSnapshot] document A document snapshot.
908
952
  #
@@ -923,12 +967,29 @@ module Google
923
967
  # puts "#{city.document_id} has #{city[:population]} residents."
924
968
  # end
925
969
  #
926
- def get
970
+ # @example Get query with read time
971
+ # require "google/cloud/firestore"
972
+ #
973
+ # firestore = Google::Cloud::Firestore.new
974
+ #
975
+ # # Get a collection reference
976
+ # cities_col = firestore.col "cities"
977
+ #
978
+ # # Create a query
979
+ # query = cities_col.select(:population)
980
+ #
981
+ # read_time = Time.now
982
+ #
983
+ # query.get(read_time: read_time) do |city|
984
+ # puts "#{city.document_id} has #{city[:population]} residents."
985
+ # end
986
+ #
987
+ def get read_time: nil
927
988
  ensure_service!
928
989
 
929
- return enum_for :get unless block_given?
990
+ return enum_for :get, read_time: read_time unless block_given?
930
991
 
931
- results = service.run_query parent_path, @query
992
+ results = service.run_query parent_path, @query, read_time: read_time
932
993
 
933
994
  # Reverse the results for Query#limit_to_last queries since that method reversed the order_by directions.
934
995
  results = results.to_a.reverse if limit_type == :last
@@ -940,6 +1001,26 @@ module Google
940
1001
  end
941
1002
  alias run get
942
1003
 
1004
+ ##
1005
+ # Creates an AggregateQuery object for the query.
1006
+ #
1007
+ # @return [AggregateQuery] New empty aggregate query.
1008
+ #
1009
+ # @example
1010
+ # require "google/cloud/firestore"
1011
+ #
1012
+ # firestore = Google::Cloud::Firestore.new
1013
+ #
1014
+ # # Get a collection reference
1015
+ # query = firestore.col "cities"
1016
+ #
1017
+ # # Create an aggregate query
1018
+ # aggregate_query = query.aggregate_query
1019
+ #
1020
+ def aggregate_query
1021
+ AggregateQuery.new self, parent_path, client
1022
+ end
1023
+
943
1024
  ##
944
1025
  # Listen to this query for changes.
945
1026
  #
@@ -1046,34 +1127,6 @@ module Google
1046
1127
  # @private
1047
1128
  StructuredQuery = Google::Cloud::Firestore::V1::StructuredQuery
1048
1129
 
1049
- ##
1050
- # @private
1051
- FILTER_OPS = {
1052
- "<" => :LESS_THAN,
1053
- "lt" => :LESS_THAN,
1054
- "<=" => :LESS_THAN_OR_EQUAL,
1055
- "lte" => :LESS_THAN_OR_EQUAL,
1056
- ">" => :GREATER_THAN,
1057
- "gt" => :GREATER_THAN,
1058
- ">=" => :GREATER_THAN_OR_EQUAL,
1059
- "gte" => :GREATER_THAN_OR_EQUAL,
1060
- "=" => :EQUAL,
1061
- "==" => :EQUAL,
1062
- "eq" => :EQUAL,
1063
- "eql" => :EQUAL,
1064
- "is" => :EQUAL,
1065
- "!=" => :NOT_EQUAL,
1066
- "array_contains" => :ARRAY_CONTAINS,
1067
- "array-contains" => :ARRAY_CONTAINS,
1068
- "include" => :ARRAY_CONTAINS,
1069
- "include?" => :ARRAY_CONTAINS,
1070
- "has" => :ARRAY_CONTAINS,
1071
- "in" => :IN,
1072
- "not_in" => :NOT_IN,
1073
- "not-in" => :NOT_IN,
1074
- "array_contains_any" => :ARRAY_CONTAINS_ANY,
1075
- "array-contains-any" => :ARRAY_CONTAINS_ANY
1076
- }.freeze
1077
1130
  ##
1078
1131
  # @private
1079
1132
  INEQUALITY_FILTERS = [
@@ -1097,36 +1150,6 @@ module Google
1097
1150
  value_nil?(value) || value_nan?(value)
1098
1151
  end
1099
1152
 
1100
- def filter name, op_key, value
1101
- field = StructuredQuery::FieldReference.new field_path: name.to_s
1102
- operator = FILTER_OPS[op_key.to_s.downcase]
1103
- raise ArgumentError, "unknown operator #{op_key}" if operator.nil?
1104
-
1105
- if value_unary? value
1106
- operator = case operator
1107
- when :EQUAL
1108
- value_nan?(value) ? :IS_NAN : :IS_NULL
1109
- when :NOT_EQUAL
1110
- value_nan?(value) ? :IS_NOT_NAN : :IS_NOT_NULL
1111
- else
1112
- raise ArgumentError, "can only perform '==' and '!=' comparisons on #{value} values"
1113
- end
1114
-
1115
- return StructuredQuery::Filter.new(
1116
- unary_filter: StructuredQuery::UnaryFilter.new(
1117
- field: field, op: operator
1118
- )
1119
- )
1120
- end
1121
-
1122
- value = Convert.raw_to_value value
1123
- StructuredQuery::Filter.new(
1124
- field_filter: StructuredQuery::FieldFilter.new(
1125
- field: field, op: operator, value: value
1126
- )
1127
- )
1128
- end
1129
-
1130
1153
  def composite_filter
1131
1154
  StructuredQuery::Filter.new(
1132
1155
  composite_filter: StructuredQuery::CompositeFilter.new(op: :AND)
@@ -0,0 +1,80 @@
1
+ # Copyright 2023 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
+ # @private Implements 5/5/5 ramp-up via Token Bucket algorithm.
21
+ #
22
+ # 5/5/5 is a ramp up strategy that starts with a budget of 500 operations per
23
+ # second. Additionally, every 5 minutes, the maximum budget can increase by
24
+ # 50%. Thus, at 5:01 into a long bulk-writing process, the maximum budget
25
+ # becomes 750 operations per second. At 10:01, the budget becomes 1,125
26
+ # operations per second.
27
+ #
28
+ class RateLimiter
29
+ DEFAULT_STARTING_MAXIMUM_OPS_PER_SECOND = 500.0
30
+ DEFAULT_PHASE_LENGTH = 300.0
31
+
32
+ attr_reader :bandwidth
33
+
34
+ ##
35
+ # Initialize the object
36
+ def initialize starting_ops: nil, phase_length: nil
37
+ @start_time = time
38
+ @last_fetched = time
39
+ @bandwidth = (starting_ops || DEFAULT_STARTING_MAXIMUM_OPS_PER_SECOND).to_f
40
+ @phase_length = phase_length || DEFAULT_PHASE_LENGTH
41
+ end
42
+
43
+ ##
44
+ # Wait till the number of tokens is available
45
+ # Assumes that the bandwidth is distributed evenly across the entire second.
46
+ #
47
+ # Example - If the limit is 500 qps, then it has been further broken down to 2e+6 nsec
48
+ # per query
49
+ #
50
+ # @return [nil]
51
+ def wait_for_tokens size
52
+ available_time = @last_fetched + (size / @bandwidth)
53
+ waiting_time = [0, available_time - time].max
54
+ sleep waiting_time
55
+ @last_fetched = time
56
+ increase_bandwidth
57
+ end
58
+
59
+ private
60
+
61
+ ##
62
+ # Returns time elapsed since epoch.
63
+ #
64
+ # @return [Float] Float denoting time elapsed since epoch
65
+ def time
66
+ Time.now.to_f
67
+ end
68
+
69
+ ##
70
+ # Increase the bandwidth as per 555 rule
71
+ #
72
+ # @return [nil]
73
+ def increase_bandwidth
74
+ intervals = (time - @start_time) / @phase_length
75
+ @bandwidth = (DEFAULT_STARTING_MAXIMUM_OPS_PER_SECOND * (1.5**intervals.floor)).to_f
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -30,29 +30,34 @@ 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, transport: :grpc
37
38
  @project = project
38
39
  @credentials = credentials
39
40
  @host = host
40
41
  @timeout = timeout
42
+ @database = database
43
+ @transport = transport
41
44
  end
42
45
 
43
46
  def firestore
44
- @firestore ||= \
45
- V1::Firestore::Client.new do |config|
47
+ @firestore ||= begin
48
+ client_class = @transport == :rest ? V1::Firestore::Rest::Client : V1::Firestore::Client
49
+ client_class.new do |config|
46
50
  config.credentials = credentials if credentials
47
51
  config.timeout = timeout if timeout
48
52
  config.endpoint = host if host
49
53
  config.lib_name = "gccl"
50
54
  config.lib_version = Google::Cloud::Firestore::VERSION
51
- config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}/databases/(default)" }
55
+ config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}/databases/#{@database}" }
52
56
  end
57
+ end
53
58
  end
54
59
 
55
- def get_documents document_paths, mask: nil, transaction: nil
60
+ def get_documents document_paths, mask: nil, transaction: nil, read_time: nil
56
61
  batch_get_req = {
57
62
  database: database_path,
58
63
  documents: document_paths,
@@ -63,7 +68,9 @@ module Google
63
68
  elsif transaction
64
69
  batch_get_req[:new_transaction] = transaction
65
70
  end
66
-
71
+ if read_time
72
+ batch_get_req[:read_time] = read_time_to_timestamp(read_time)
73
+ end
67
74
  firestore.batch_get_documents batch_get_req, call_options(parent: database_path)
68
75
  end
69
76
 
@@ -74,23 +81,25 @@ module Google
74
81
  # the showMissing flag to true to support full document traversal. If
75
82
  # there are too many documents, recommendation will be not to call this
76
83
  # method.
77
- def list_documents parent, collection_id, token: nil, max: nil
84
+ def list_documents parent, collection_id, token: nil, max: nil, read_time: nil
78
85
  mask = { field_paths: [] }
79
- paged_enum = firestore.list_documents parent: parent,
86
+ paged_enum = firestore.list_documents parent: parent,
80
87
  collection_id: collection_id,
81
- page_size: max,
82
- page_token: token,
83
- mask: mask,
84
- show_missing: true
88
+ page_size: max,
89
+ page_token: token,
90
+ mask: mask,
91
+ show_missing: true,
92
+ read_time: read_time_to_timestamp(read_time)
85
93
  paged_enum.response
86
94
  end
87
95
 
88
- def list_collections parent, token: nil, max: nil
96
+ def list_collections parent, token: nil, max: nil, read_time: nil
89
97
  firestore.list_collection_ids(
90
98
  {
91
- parent: parent,
92
- page_size: max,
93
- page_token: token
99
+ parent: parent,
100
+ page_size: max,
101
+ page_token: token,
102
+ read_time: read_time_to_timestamp(read_time)
94
103
  },
95
104
  call_options(parent: database_path)
96
105
  )
@@ -98,19 +107,20 @@ module Google
98
107
 
99
108
  ##
100
109
  # Returns Google::Cloud::Firestore::V1::PartitionQueryResponse
101
- def partition_query parent, query_grpc, partition_count, token: nil, max: nil
110
+ def partition_query parent, query_grpc, partition_count, token: nil, max: nil, read_time: nil
102
111
  request = Google::Cloud::Firestore::V1::PartitionQueryRequest.new(
103
112
  parent: parent,
104
113
  structured_query: query_grpc,
105
114
  partition_count: partition_count,
106
115
  page_token: token,
107
- page_size: max
116
+ page_size: max,
117
+ read_time: read_time_to_timestamp(read_time)
108
118
  )
109
119
  paged_enum = firestore.partition_query request
110
120
  paged_enum.response
111
121
  end
112
122
 
113
- def run_query path, query_grpc, transaction: nil
123
+ def run_query path, query_grpc, transaction: nil, read_time: nil
114
124
  run_query_req = {
115
125
  parent: path,
116
126
  structured_query: query_grpc
@@ -120,10 +130,28 @@ module Google
120
130
  elsif transaction
121
131
  run_query_req[:new_transaction] = transaction
122
132
  end
133
+ if read_time
134
+ run_query_req[:read_time] = read_time_to_timestamp(read_time)
135
+ end
123
136
 
124
137
  firestore.run_query run_query_req, call_options(parent: database_path)
125
138
  end
126
139
 
140
+ ##
141
+ # Returns Google::Cloud::Firestore::V1::RunAggregationQueryResponse
142
+ def run_aggregate_query parent, structured_aggregation_query, transaction: nil
143
+ request = Google::Cloud::Firestore::V1::RunAggregationQueryRequest.new(
144
+ parent: parent,
145
+ structured_aggregation_query: structured_aggregation_query
146
+ )
147
+ if transaction.is_a? String
148
+ request.transaction = transaction
149
+ elsif transaction
150
+ request.new_transaction = transaction
151
+ end
152
+ firestore.run_aggregation_query request
153
+ end
154
+
127
155
  def listen enum
128
156
  firestore.listen enum, call_options(parent: database_path)
129
157
  end
@@ -158,18 +186,41 @@ module Google
158
186
  )
159
187
  end
160
188
 
161
- def database_path project_id: project, database_id: "(default)"
189
+ ##
190
+ # Makes the BatchWrite API call. Contains the list of write operations to be processed.
191
+ #
192
+ # @return [::Google::Cloud::Firestore::V1::BatchWriteResponse]
193
+ def batch_write writes
194
+ batch_write_req = {
195
+ database: database_path,
196
+ writes: writes
197
+ }
198
+ firestore.batch_write batch_write_req, call_options(parent: database_path)
199
+ end
200
+
201
+ def database_path project_id: project, database_id: database
162
202
  # Originally used V1::FirestoreClient.database_root_path until it was removed in #5405.
163
203
  "projects/#{project_id}/databases/#{database_id}"
164
204
  end
165
205
 
166
- def documents_path project_id: project, database_id: "(default)"
206
+ def documents_path project_id: project, database_id: database
167
207
  # Originally used V1::FirestoreClient.document_root_path until it was removed in #5405.
168
208
  "projects/#{project_id}/databases/#{database_id}/documents"
169
209
  end
170
210
 
171
211
  def inspect
172
- "#{self.class}(#{@project})"
212
+ "#{self.class}(#{@project})(#{@database})"
213
+ end
214
+
215
+ def read_time_to_timestamp read_time
216
+ return nil if read_time.nil?
217
+
218
+ raise TypeError, "read_time is expected to be a Time object" unless read_time.is_a? Time
219
+
220
+ Google::Protobuf::Timestamp.new(
221
+ seconds: read_time.to_i,
222
+ nanos: read_time.usec * 1000
223
+ )
173
224
  end
174
225
 
175
226
  protected
@@ -183,7 +234,7 @@ module Google
183
234
  Gapic::CallOptions.new(**{
184
235
  metadata: default_headers(parent),
185
236
  page_token: token
186
- }.delete_if { |_, v| v.nil? })
237
+ }.compact)
187
238
  end
188
239
 
189
240
  def document_mask mask
@@ -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.to_grpc,
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.15.0".freeze
20
20
  end
21
21
  end
22
22
  end