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
@@ -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
@@ -318,7 +318,7 @@ module Google
318
318
  ##
319
319
  # @private
320
320
  def eql? other
321
- return nil unless other.is_a? DocumentSnapshot
321
+ return false unless other.is_a? DocumentSnapshot
322
322
  return data.eql? other.data if path == other.path
323
323
  path.eql? other.path
324
324
  end
@@ -0,0 +1,60 @@
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
+ require "google/cloud/errors"
16
+
17
+ module Google
18
+ module Cloud
19
+ module Firestore
20
+ ##
21
+ # Indicates that the an error was reported while scheduling
22
+ # BulkWriter operations.
23
+ #
24
+ class BulkWriterSchedulerError < Google::Cloud::Error
25
+ def initialize message
26
+ super "BulkWriterSchedulerError : #{message}"
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Indicates that the an error was reported while committing a
32
+ # batch of operations.
33
+ #
34
+ class BulkCommitBatchError < Google::Cloud::Error
35
+ def initialize message
36
+ super "BulkCommitBatchError : #{message}"
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Indicates that the an error was reported while parsing response for
42
+ # BulkWriterOperation.
43
+ #
44
+ class BulkWriterOperationError < Google::Cloud::Error
45
+ def initialize message
46
+ super "BulkWriterOperationError : #{message}"
47
+ end
48
+ end
49
+
50
+ ##
51
+ # Indicates that the an error was reported in BulkWriter.
52
+ #
53
+ class BulkWriterError < Google::Cloud::Error
54
+ def initialize message
55
+ super "BulkWriterError : #{message}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,326 @@
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
+ require "google/cloud/firestore/v1"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Firestore
21
+ ##
22
+ # Represents the filter for structured query.
23
+ #
24
+ class Filter
25
+ ##
26
+ # @private Object of type
27
+ # Google::Cloud::Firestore::V1::StructuredQuery::Filter
28
+ attr_accessor :filter
29
+
30
+ ##
31
+ # Create a Filter object.
32
+ #
33
+ # @param field [FieldPath, String, Symbol] A field path to filter
34
+ # results with.
35
+ # If a {FieldPath} object is not provided then the field will be
36
+ # treated as a dotted string, meaning the string represents individual
37
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
38
+ # `.` cannot be in a dotted string, and should provided using a
39
+ # {FieldPath} object instead.
40
+ #
41
+ # @param operator [String, Symbol] The operation to compare the field
42
+ # to. Acceptable values include:
43
+ # * less than: `<`, `lt`
44
+ # * less than or equal: `<=`, `lte`
45
+ # * greater than: `>`, `gt`
46
+ # * greater than or equal: `>=`, `gte`
47
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
48
+ # * not equal: `!=`
49
+ # * in: `in`
50
+ # * not in: `not-in`, `not_in`
51
+ # * array contains: `array-contains`, `array_contains`
52
+ #
53
+ # @param value [Object] The value to compare the property to. Defaults to nil.
54
+ # Possible values are:
55
+ # * Integer
56
+ # * Float/BigDecimal
57
+ # * String
58
+ # * Boolean
59
+ # * Array
60
+ # * Date/Time
61
+ # * StringIO
62
+ # * Google::Cloud::Datastore::Key
63
+ # * Google::Cloud::Datastore::Entity
64
+ # * nil
65
+ #
66
+ # @return [Google::Cloud::Firestore::Filter] New filter for the given condition
67
+ #
68
+ # @example
69
+ # require "google/cloud/firestore"
70
+ #
71
+ # firestore = Google::Cloud::Firestore.new
72
+ #
73
+ # # Create a Filter
74
+ # Google::Cloud::Firestore::Filter.new(:population, :>=, 1000000)
75
+ #
76
+ def initialize field, operator, value
77
+ @filter = create_filter field, operator, value
78
+ end
79
+
80
+ ##
81
+ # Joins filter using AND operator.
82
+ #
83
+ # @overload where(filter)
84
+ # Pass Firestore::Filter to `where` via field_or_filter argument.
85
+ #
86
+ # @param filter [::Google::Cloud::Firestore::Filter]
87
+ #
88
+ # @overload where(field, operator, value)
89
+ # Pass arguments to `where` via positional arguments.
90
+ #
91
+ # @param field [FieldPath, String, Symbol] A field path to filter
92
+ # results with.
93
+ # If a {FieldPath} object is not provided then the field will be
94
+ # treated as a dotted string, meaning the string represents individual
95
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
96
+ # `.` cannot be in a dotted string, and should provided using a
97
+ # {FieldPath} object instead.
98
+ #
99
+ # @param operator [String, Symbol] The operation to compare the field
100
+ # to. Acceptable values include:
101
+ # * less than: `<`, `lt`
102
+ # * less than or equal: `<=`, `lte`
103
+ # * greater than: `>`, `gt`
104
+ # * greater than or equal: `>=`, `gte`
105
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
106
+ # * not equal: `!=`
107
+ # * in: `in`
108
+ # * not in: `not-in`, `not_in`
109
+ # * array contains: `array-contains`, `array_contains`
110
+ #
111
+ # @param value [Object] The value to compare the property to. Defaults to nil.
112
+ # Possible values are:
113
+ # * Integer
114
+ # * Float/BigDecimal
115
+ # * String
116
+ # * Boolean
117
+ # * Array
118
+ # * Date/Time
119
+ # * StringIO
120
+ # * Google::Cloud::Datastore::Key
121
+ # * Google::Cloud::Datastore::Entity
122
+ # * nil
123
+ #
124
+ # @return [Filter] New Filter object.
125
+ #
126
+ # @example Pass a Filter type object in argument
127
+ # require "google/cloud/firestore"
128
+ #
129
+ # filter_1 = Google::Cloud::Firestore.Firestore.new(:population, :>=, 1000000)
130
+ # filter_2 = Google::Cloud::Firestore.Firestore.new("done", "=", "false")
131
+ #
132
+ # filter = filter_1.and(filter_2)
133
+ #
134
+ # @example Pass filter conditions in the argument
135
+ # require "google/cloud/firestore"
136
+ #
137
+ # filter_1 = Google::Cloud::Firestore.Firestore.new(:population, :>=, 1000000)
138
+ #
139
+ # filter = filter_1.and("done", "=", "false")
140
+ #
141
+ def and filter_or_field = nil, operator = nil, value = nil
142
+ combine_filters composite_filter_and, filter_or_field, operator, value
143
+ end
144
+
145
+ ##
146
+ # Joins filter using OR operator.
147
+ #
148
+ # @overload where(filter)
149
+ # Pass Firestore::Filter to `where` via field_or_filter argument.
150
+ #
151
+ # @param filter [::Google::Cloud::Firestore::Filter]
152
+ #
153
+ # @overload where(field, operator, value)
154
+ # Pass arguments to `where` via positional arguments.
155
+ #
156
+ # @param field [FieldPath, String, Symbol] A field path to filter
157
+ # results with.
158
+ # If a {FieldPath} object is not provided then the field will be
159
+ # treated as a dotted string, meaning the string represents individual
160
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
161
+ # `.` cannot be in a dotted string, and should provided using a
162
+ # {FieldPath} object instead.
163
+ #
164
+ # @param operator [String, Symbol] The operation to compare the field
165
+ # to. Acceptable values include:
166
+ # * less than: `<`, `lt`
167
+ # * less than or equal: `<=`, `lte`
168
+ # * greater than: `>`, `gt`
169
+ # * greater than or equal: `>=`, `gte`
170
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
171
+ # * not equal: `!=`
172
+ # * in: `in`
173
+ # * not in: `not-in`, `not_in`
174
+ # * array contains: `array-contains`, `array_contains`
175
+ #
176
+ # @param value [Object] The value to compare the property to. Defaults to nil.
177
+ # Possible values are:
178
+ # * Integer
179
+ # * Float/BigDecimal
180
+ # * String
181
+ # * Boolean
182
+ # * Array
183
+ # * Date/Time
184
+ # * StringIO
185
+ # * Google::Cloud::Datastore::Key
186
+ # * Google::Cloud::Datastore::Entity
187
+ # * nil
188
+ #
189
+ # @return [Filter] New Filter object.
190
+ #
191
+ # @example Pass a Filter type object in argument
192
+ # require "google/cloud/firestore"
193
+ #
194
+ # filter_1 = Google::Cloud::Firestore.Firestore.new(:population, :>=, 1000000)
195
+ # filter_2 = Google::Cloud::Firestore.Firestore.new("done", "=", "false")
196
+ #
197
+ # filter = filter_1.or(filter_2)
198
+ #
199
+ # @example Pass filter conditions in the argument
200
+ # require "google/cloud/firestore"
201
+ #
202
+ # filter_1 = Google::Cloud::Firestore.Firestore.new(:population, :>=, 1000000)
203
+ #
204
+ # filter = filter_1.or("done", "=", "false")
205
+ #
206
+ def or filter_or_field = nil, operator = nil, value = nil
207
+ combine_filters composite_filter_or, filter_or_field, operator, value
208
+ end
209
+
210
+ private
211
+
212
+ ##
213
+ # @private
214
+ StructuredQuery = Google::Cloud::Firestore::V1::StructuredQuery
215
+
216
+ ##
217
+ # @private
218
+ FILTER_OPS = {
219
+ "<" => :LESS_THAN,
220
+ "lt" => :LESS_THAN,
221
+ "<=" => :LESS_THAN_OR_EQUAL,
222
+ "lte" => :LESS_THAN_OR_EQUAL,
223
+ ">" => :GREATER_THAN,
224
+ "gt" => :GREATER_THAN,
225
+ ">=" => :GREATER_THAN_OR_EQUAL,
226
+ "gte" => :GREATER_THAN_OR_EQUAL,
227
+ "=" => :EQUAL,
228
+ "==" => :EQUAL,
229
+ "eq" => :EQUAL,
230
+ "eql" => :EQUAL,
231
+ "is" => :EQUAL,
232
+ "!=" => :NOT_EQUAL,
233
+ "array_contains" => :ARRAY_CONTAINS,
234
+ "array-contains" => :ARRAY_CONTAINS,
235
+ "include" => :ARRAY_CONTAINS,
236
+ "include?" => :ARRAY_CONTAINS,
237
+ "has" => :ARRAY_CONTAINS,
238
+ "in" => :IN,
239
+ "not_in" => :NOT_IN,
240
+ "not-in" => :NOT_IN,
241
+ "array_contains_any" => :ARRAY_CONTAINS_ANY,
242
+ "array-contains-any" => :ARRAY_CONTAINS_ANY
243
+ }.freeze
244
+
245
+ ##
246
+ # @private
247
+ INEQUALITY_FILTERS = [
248
+ :LESS_THAN,
249
+ :LESS_THAN_OR_EQUAL,
250
+ :GREATER_THAN,
251
+ :GREATER_THAN_OR_EQUAL
252
+ ].freeze
253
+
254
+ def composite_filter_and
255
+ StructuredQuery::Filter.new(
256
+ composite_filter: StructuredQuery::CompositeFilter.new(op: :AND)
257
+ )
258
+ end
259
+
260
+ def composite_filter_or
261
+ StructuredQuery::Filter.new(
262
+ composite_filter: StructuredQuery::CompositeFilter.new(op: :OR)
263
+ )
264
+ end
265
+
266
+ def combine_filters new_filter, filter_or_field, operator, value
267
+ new_filter.composite_filter.filters << @filter
268
+ new_filter.composite_filter.filters << if filter_or_field.is_a? Google::Cloud::Firestore::Filter
269
+ filter_or_field.filter
270
+ else
271
+ create_filter filter_or_field, operator, value
272
+ end
273
+ dup.tap do |f|
274
+ f.filter = new_filter
275
+ end
276
+ end
277
+
278
+ def value_nil? value
279
+ [nil, :null, :nil].include? value
280
+ end
281
+
282
+ def value_nan? value
283
+ # Comparing NaN values raises, so check for #nan? first.
284
+ return true if value.respond_to?(:nan?) && value.nan?
285
+ [:nan].include? value
286
+ end
287
+
288
+ def value_unary? value
289
+ value_nil?(value) || value_nan?(value)
290
+ end
291
+
292
+ def create_filter field, op_key, value
293
+ return if field.nil? && op_key.nil? && value.nil?
294
+ field = FieldPath.parse field unless field.is_a? FieldPath
295
+ field = StructuredQuery::FieldReference.new field_path: field.formatted_string.to_s
296
+ operator = FILTER_OPS[op_key.to_s.downcase]
297
+ raise ArgumentError, "unknown operator #{op_key}" if operator.nil?
298
+
299
+ if value_unary? value
300
+ operator = case operator
301
+ when :EQUAL
302
+ value_nan?(value) ? :IS_NAN : :IS_NULL
303
+ when :NOT_EQUAL
304
+ value_nan?(value) ? :IS_NOT_NAN : :IS_NOT_NULL
305
+ else
306
+ raise ArgumentError, "can only perform '==' and '!=' comparisons on #{value} values"
307
+ end
308
+
309
+ return StructuredQuery::Filter.new(
310
+ unary_filter: StructuredQuery::UnaryFilter.new(
311
+ field: field, op: operator
312
+ )
313
+ )
314
+ end
315
+
316
+ value = Convert.raw_to_value value
317
+ StructuredQuery::Filter.new(
318
+ field_filter: StructuredQuery::FieldFilter.new(
319
+ field: field, op: operator, value: value
320
+ )
321
+ )
322
+ end
323
+ end
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,97 @@
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
+ module Google
16
+ module Cloud
17
+ module Firestore
18
+ module Promise
19
+ ##
20
+ # # Future
21
+ #
22
+ # A Future object represents a value which will become available in future.
23
+ # May reject with a reason instead, e.g. when the tasks raises an exception.
24
+ #
25
+ class Future
26
+ ##
27
+ # Initialize the future object
28
+ #
29
+ def initialize future
30
+ @future = future
31
+ end
32
+
33
+ # Is it in fulfilled state?
34
+ #
35
+ # @return [Boolean]
36
+ def fulfilled?
37
+ @future.fulfilled?
38
+ end
39
+
40
+ # Is it in rejected state?
41
+ #
42
+ # @return [Boolean]
43
+ def rejected?
44
+ @future.rejected?
45
+ end
46
+
47
+ ##
48
+ # Method waits for the timeout duration and return the value of the future if
49
+ # fulfilled, timeout value in case of timeout and nil in case of rejection.
50
+ #
51
+ # @param [Integer] timeout the maximum time in seconds to wait
52
+ # @param [Object] timeout_value a value returned by the method when it times out
53
+ # @return [Object, nil, timeout_value] the value of the Future when fulfilled,
54
+ # timeout_value on timeout, nil on rejection.
55
+ def value timeout = nil, timeout_value = nil
56
+ @future.value timeout, timeout_value
57
+ end
58
+
59
+ # Returns reason of future's rejection.
60
+ #
61
+ # @return [Object, timeout_value] the reason, or timeout_value on timeout, or nil on fulfillment.
62
+ def reason timeout = nil, timeout_value = nil
63
+ @future.reason timeout, timeout_value
64
+ end
65
+
66
+ ##
67
+ # Method waits for the timeout duration and raise exception on rejection
68
+ #
69
+ # @param [Integer] timeout the maximum time in seconds to wait
70
+ def wait! timeout = nil
71
+ @future.wait! timeout
72
+ end
73
+
74
+ ##
75
+ # Chains the task to be executed synchronously after it fulfills. Does not run
76
+ # the task if it rejects. It will resolve though, triggering any dependent futures.
77
+ #
78
+ # @return [Future]
79
+ # @yield [reason, *args] to the task.
80
+ def then *args, &task
81
+ Future.new @future.then(*args, &task)
82
+ end
83
+
84
+ # Chains the task to be executed synchronously on executor after it rejects. Does
85
+ # not run the task if it fulfills. It will resolve though, triggering any
86
+ # dependent futures.
87
+ #
88
+ # @return [Future]
89
+ # @yield [reason, *args] to the task.
90
+ def rescue *args, &task
91
+ Future.new @future.rescue(*args, &task)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end