google-cloud-firestore 2.7.2 → 2.15.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.
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