google-cloud-firestore 0.23.0 → 0.24.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/.yardopts +10 -2
  3. data/README.md +4 -4
  4. data/lib/google-cloud-firestore.rb +5 -4
  5. data/lib/google/cloud/firestore.rb +2 -505
  6. data/lib/google/cloud/firestore/client.rb +50 -0
  7. data/lib/google/cloud/firestore/convert.rb +69 -50
  8. data/lib/google/cloud/firestore/field_value.rb +96 -1
  9. data/lib/google/cloud/firestore/query.rb +34 -14
  10. data/lib/google/cloud/firestore/v1beta1.rb +6 -0
  11. data/lib/google/cloud/firestore/v1beta1/credentials.rb +1 -0
  12. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/common.rb +1 -0
  13. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/document.rb +3 -2
  14. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/firestore.rb +21 -20
  15. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/query.rb +1 -0
  16. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/write.rb +2 -1
  17. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/any.rb +14 -8
  18. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/empty.rb +1 -0
  19. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/timestamp.rb +9 -6
  20. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/wrappers.rb +1 -0
  21. data/lib/google/cloud/firestore/v1beta1/doc/google/rpc/status.rb +1 -0
  22. data/lib/google/cloud/firestore/v1beta1/firestore_client.rb +16 -14
  23. data/lib/google/cloud/firestore/version.rb +1 -1
  24. data/lib/google/firestore/v1beta1/common_pb.rb +1 -0
  25. data/lib/google/firestore/v1beta1/document_pb.rb +1 -0
  26. data/lib/google/firestore/v1beta1/firestore_pb.rb +1 -0
  27. data/lib/google/firestore/v1beta1/firestore_services_pb.rb +1 -0
  28. data/lib/google/firestore/v1beta1/query_pb.rb +1 -0
  29. data/lib/google/firestore/v1beta1/write_pb.rb +1 -0
  30. metadata +6 -6
@@ -302,6 +302,56 @@ module Google
302
302
  FieldValue.server_time
303
303
  end
304
304
 
305
+ ##
306
+ # Creates a sentinel value to indicate the union of the given values
307
+ # with an array.
308
+ #
309
+ # @param [Object] values The values to add to the array. Required.
310
+ #
311
+ # @return [FieldValue] The array union field value object.
312
+ #
313
+ # @example
314
+ # require "google/cloud/firestore"
315
+ #
316
+ # firestore = Google::Cloud::Firestore.new
317
+ #
318
+ # # Get a document reference
319
+ # nyc_ref = firestore.doc "cities/NYC"
320
+ #
321
+ # array_union = firestore.field_array_union 1, 2, 3
322
+ #
323
+ # nyc_ref.update({ name: "New York City",
324
+ # lucky_numbers: array_union })
325
+ #
326
+ def field_array_union *values
327
+ FieldValue.array_union(*values)
328
+ end
329
+
330
+ ##
331
+ # Creates a sentinel value to indicate the removal of the given values
332
+ # with an array.
333
+ #
334
+ # @param [Object] values The values to remove from the array. Required.
335
+ #
336
+ # @return [FieldValue] The array delete field value object.
337
+ #
338
+ # @example
339
+ # require "google/cloud/firestore"
340
+ #
341
+ # firestore = Google::Cloud::Firestore.new
342
+ #
343
+ # # Get a document reference
344
+ # nyc_ref = firestore.doc "cities/NYC"
345
+ #
346
+ # array_delete = firestore.field_array_delete 7, 8, 9
347
+ #
348
+ # nyc_ref.update({ name: "New York City",
349
+ # lucky_numbers: array_delete })
350
+ #
351
+ def field_array_delete *values
352
+ FieldValue.array_delete(*values)
353
+ end
354
+
305
355
  # @!endgroup
306
356
 
307
357
  # @!group Operations
@@ -153,9 +153,9 @@ module Google
153
153
  end
154
154
  raise ArgumentError, "data is required" unless data.is_a? Hash
155
155
 
156
- data, server_time_paths = remove_field_value_from data, :server_time
156
+ data, field_paths_and_values = remove_field_value_from data
157
157
 
158
- if data.any? || server_time_paths.empty?
158
+ if data.any? || field_paths_and_values.empty?
159
159
  write = Google::Firestore::V1beta1::Write.new(
160
160
  update: Google::Firestore::V1beta1::Document.new(
161
161
  name: doc_path,
@@ -166,8 +166,8 @@ module Google
166
166
  writes << write
167
167
  end
168
168
 
169
- if server_time_paths.any?
170
- transform_write = transform_write doc_path, server_time_paths
169
+ if field_paths_and_values.any?
170
+ transform_write = transform_write doc_path, field_paths_and_values
171
171
 
172
172
  if data.empty?
173
173
  transform_write.current_document = \
@@ -205,7 +205,7 @@ module Google
205
205
  raise ArgumentError, "DELETE not allowed on set"
206
206
  end
207
207
 
208
- data, server_time_paths = remove_field_value_from data, :server_time
208
+ data, field_paths_and_values = remove_field_value_from data
209
209
 
210
210
  writes << Google::Firestore::V1beta1::Write.new(
211
211
  update: Google::Firestore::V1beta1::Document.new(
@@ -213,8 +213,8 @@ module Google
213
213
  fields: hash_to_fields(data))
214
214
  )
215
215
 
216
- if server_time_paths.any?
217
- writes << transform_write(doc_path, server_time_paths)
216
+ if field_paths_and_values.any?
217
+ writes << transform_write(doc_path, field_paths_and_values)
218
218
  end
219
219
 
220
220
  writes
@@ -242,15 +242,15 @@ module Google
242
242
  all_valid_check = all_valid_check.include? false
243
243
  raise ArgumentError, "all fields must be in data" if all_valid_check
244
244
 
245
- data, delete_paths = remove_field_value_from data, :delete
246
- data, server_time_paths = remove_field_value_from data, :server_time
245
+ data, delete_field_paths_and_values = remove_field_value_from data, :delete
246
+ data, field_paths_and_values = remove_field_value_from data
247
247
 
248
- delete_valid_check = delete_paths.map do |delete_path|
249
- if field_paths.include?(delete_path)
248
+ delete_valid_check = delete_field_paths_and_values.keys.map do |delete_field_path|
249
+ if field_paths.include? delete_field_path
250
250
  true
251
251
  else
252
252
  found_in_field_paths = field_paths.select do |fp|
253
- fp.formatted_string.start_with? "#{delete_path.formatted_string}."
253
+ fp.formatted_string.start_with? "#{delete_field_path.formatted_string}."
254
254
  end
255
255
  found_in_field_paths.any?
256
256
  end
@@ -258,26 +258,26 @@ module Google
258
258
  delete_valid_check = delete_valid_check.include? false
259
259
  raise ArgumentError, "deleted field not included in merge" if delete_valid_check
260
260
 
261
- server_time_paths.select! do |server_time_path|
261
+ field_paths_and_values.select! do |server_time_path|
262
262
  field_paths.any? do |field_path|
263
263
  server_time_path.formatted_string.start_with? field_path.formatted_string
264
264
  end
265
265
  end
266
266
 
267
267
  # Choose only the data there are field paths for
268
- field_paths -= delete_paths
269
- field_paths -= server_time_paths
268
+ field_paths -= delete_field_paths_and_values.keys
269
+ field_paths -= field_paths_and_values.keys
270
270
  data = select_by_field_paths data, field_paths
271
271
  # Restore delete paths
272
- field_paths += delete_paths
272
+ field_paths += delete_field_paths_and_values.keys
273
273
 
274
274
  if data.empty? && !allow_empty
275
- if server_time_paths.empty? && delete_paths.empty?
275
+ if field_paths_and_values.empty? && delete_field_paths_and_values.empty?
276
276
  raise ArgumentError, "data required for set with merge"
277
277
  end
278
278
  end
279
279
 
280
- if data.any? || field_paths.any? || (allow_empty && server_time_paths.empty?)
280
+ if data.any? || field_paths.any? || (allow_empty && field_paths_and_values.empty?)
281
281
  writes << Google::Firestore::V1beta1::Write.new(
282
282
  update: Google::Firestore::V1beta1::Document.new(
283
283
  name: doc_path,
@@ -287,8 +287,8 @@ module Google
287
287
  )
288
288
  end
289
289
 
290
- if server_time_paths.any?
291
- writes << transform_write(doc_path, server_time_paths)
290
+ if field_paths_and_values.any?
291
+ writes << transform_write(doc_path, field_paths_and_values)
292
292
  end
293
293
 
294
294
  writes
@@ -312,29 +312,29 @@ module Google
312
312
  value.is_a?(FieldValue) && value.type == :delete
313
313
  end
314
314
 
315
- root_server_time_paths, new_data_pairs = new_data_pairs.partition do |field_path, value|
316
- value.is_a?(FieldValue) && value.type == :server_time
315
+ root_field_paths_and_values, new_data_pairs = new_data_pairs.partition do |field_path, value|
316
+ value.is_a? FieldValue
317
317
  end
318
318
 
319
319
  data = build_hash_from_field_paths_and_values new_data_pairs
320
320
  field_paths = new_data_pairs.map(&:first)
321
321
 
322
322
  delete_paths.map!(&:first)
323
- root_server_time_paths.map!(&:first)
323
+ root_field_paths_and_values = Hash[root_field_paths_and_values]
324
324
 
325
325
  data, nested_deletes = remove_field_value_from data, :delete
326
326
  raise ArgumentError, "DELETE cannot be nested" if nested_deletes.any?
327
327
 
328
- data, nested_server_time_paths = remove_field_value_from data, :server_time
328
+ data, nested_field_paths_and_values = remove_field_value_from data
329
329
 
330
- server_time_paths = root_server_time_paths + nested_server_time_paths
330
+ field_paths_and_values = root_field_paths_and_values.merge nested_field_paths_and_values
331
331
 
332
332
  field_paths = (field_paths + delete_paths).uniq
333
333
  field_paths.each do |field_path|
334
334
  raise ArgumentError, "empty paths not allowed" if field_path.fields.empty?
335
335
  end
336
336
 
337
- if data.empty? && delete_paths.empty? && server_time_paths.empty?
337
+ if data.empty? && delete_paths.empty? && field_paths_and_values.empty?
338
338
  raise ArgumentError, "data is required"
339
339
  end
340
340
 
@@ -356,8 +356,8 @@ module Google
356
356
  writes << write
357
357
  end
358
358
 
359
- if server_time_paths.any?
360
- transform_write = transform_write doc_path, server_time_paths
359
+ if field_paths_and_values.any?
360
+ transform_write = transform_write doc_path, field_paths_and_values
361
361
  if data.empty?
362
362
  transform_write.current_document = \
363
363
  Google::Firestore::V1beta1::Precondition.new(exists: true)
@@ -387,32 +387,34 @@ module Google
387
387
  write
388
388
  end
389
389
 
390
- def is_field_value_nested obj, field_value_type
391
- return true if obj.is_a?(FieldValue) && obj.type == field_value_type
390
+ def is_field_value_nested obj, field_value_type = nil
391
+ return obj if obj.is_a?(FieldValue) && (field_value_type.nil? || obj.type == field_value_type)
392
392
 
393
393
  if obj.is_a? Array
394
- obj.each { |o| val = is_field_value_nested o, field_value_type; return true if val }
394
+ obj.each { |o| val = is_field_value_nested o, field_value_type; return val if val }
395
395
  elsif obj.is_a? Hash
396
- obj.each { |_k, v| val = is_field_value_nested v, field_value_type; return true if val }
396
+ obj.each { |_k, v| val = is_field_value_nested v, field_value_type; return val if val }
397
397
  end
398
- false
398
+ nil
399
399
  end
400
400
 
401
- def remove_field_value_from obj, field_value_type
401
+ def remove_field_value_from obj, field_value_type = nil
402
402
  return [nil, []] unless obj.is_a? Hash
403
403
 
404
404
  paths = []
405
405
  new_pairs = obj.map do |key, value|
406
- if value.is_a?(FieldValue) && value.type == field_value_type
407
- paths << [key]
406
+ if value.is_a?(FieldValue) && (field_value_type.nil? || value.type == field_value_type)
407
+ paths << [FieldPath.new(*key), value]
408
408
  nil # will be removed by calling compact
409
409
  else
410
410
  if value.is_a? Hash
411
411
  unless value.empty?
412
412
  nested_hash, nested_paths = remove_field_value_from value, field_value_type
413
413
  if nested_paths.any?
414
- nested_paths.each do |nested_path|
415
- paths << (([key] + nested_path.fields).flatten)
414
+ nested_paths.each do |nested_field_path, nested_field_value|
415
+ updated_field_paths = ([key] + nested_field_path.fields).flatten
416
+ updated_field_path = FieldPath.new *updated_field_paths
417
+ paths << [updated_field_path, nested_field_value]
416
418
  end
417
419
  end
418
420
  if nested_hash.empty?
@@ -425,8 +427,9 @@ module Google
425
427
  end
426
428
  else
427
429
  if value.is_a? Array
428
- if is_field_value_nested value, field_value_type
429
- raise ArgumentError, "cannot nest #{field_value_type} under arrays"
430
+ nested_field_value = is_field_value_nested value, field_value_type
431
+ if nested_field_value
432
+ raise ArgumentError, "cannot nest #{nested_field_value.type} under arrays"
430
433
  end
431
434
  end
432
435
 
@@ -435,10 +438,8 @@ module Google
435
438
  end
436
439
  end
437
440
 
438
- paths.map! { |path| FieldPath.new *path }
439
-
440
- # return a new hash and paths
441
- [Hash[new_pairs.compact], paths]
441
+ # return new data hash and field path/values hash
442
+ [Hash[new_pairs.compact], Hash[paths]]
442
443
  end
443
444
 
444
445
  def identify_leaf_nodes hash
@@ -578,12 +579,9 @@ module Google
578
579
  "`#{str}`"
579
580
  end
580
581
 
581
- def transform_write doc_path, paths, server_value: :REQUEST_TIME
582
- field_transforms = paths.map do |path|
583
- Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
584
- field_path: path.formatted_string,
585
- set_to_server_value: server_value
586
- )
582
+ def transform_write doc_path, paths
583
+ field_transforms = paths.map do |field_path, field_value|
584
+ to_field_transform field_path, field_value
587
585
  end
588
586
 
589
587
  Google::Firestore::V1beta1::Write.new(
@@ -593,6 +591,27 @@ module Google
593
591
  )
594
592
  )
595
593
  end
594
+
595
+ def to_field_transform field_path, field_value
596
+ if field_value.type == :server_time
597
+ Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
598
+ field_path: field_path.formatted_string,
599
+ set_to_server_value: :REQUEST_TIME
600
+ )
601
+ elsif field_value.type == :array_union
602
+ Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
603
+ field_path: field_path.formatted_string,
604
+ append_missing_elements: raw_to_value(Array(field_value.values)).array_value
605
+ )
606
+ elsif field_value.type == :array_delete
607
+ Google::Firestore::V1beta1::DocumentTransform::FieldTransform.new(
608
+ field_path: field_path.formatted_string,
609
+ remove_all_from_array: raw_to_value(Array(field_value.values)).array_value
610
+ )
611
+ else
612
+ raise ArgumentError, "unknown field transform #{field_value.type}"
613
+ end
614
+ end
596
615
  end
597
616
  # rubocop:enable all
598
617
 
@@ -35,8 +35,9 @@ module Google
35
35
  ##
36
36
  # @private Creates a field value object representing changes made to
37
37
  # fields in document data.
38
- def initialize type
38
+ def initialize type, values = nil
39
39
  @type = type
40
+ @values = values
40
41
  end
41
42
 
42
43
  ##
@@ -62,6 +63,34 @@ module Google
62
63
  @type
63
64
  end
64
65
 
66
+ ##
67
+ # @private
68
+ # The values to change to an individual field in document data,
69
+ # depending on the type of change.
70
+ #
71
+ # @return [Array<Object>] The values.
72
+ #
73
+ # @example
74
+ # require "google/cloud/firestore"
75
+ #
76
+ # firestore = Google::Cloud::Firestore.new
77
+ #
78
+ # # Get a document reference
79
+ # nyc_ref = firestore.doc "cities/NYC"
80
+ #
81
+ # array_union = Google::Cloud::Firestore::FieldValue.array_union(
82
+ # 1, 2, 3
83
+ # )
84
+ # array_union.type #=> :array_union
85
+ # array_union.values #=> [1, 2, 3]
86
+ #
87
+ # nyc_ref.update({ name: "New York City",
88
+ # lucky_numbers: array_union })
89
+ #
90
+ def values
91
+ @values
92
+ end
93
+
65
94
  ##
66
95
  # Creates a field value object representing the deletion of a field in
67
96
  # document data.
@@ -107,6 +136,72 @@ module Google
107
136
  def self.server_time
108
137
  new :server_time
109
138
  end
139
+
140
+ ##
141
+ # Creates a sentinel value to indicate the union of the given values
142
+ # with an array.
143
+ #
144
+ # @param [Object] values The values to add to the array. Required.
145
+ #
146
+ # @return [FieldValue] The array union field value object.
147
+ #
148
+ # @example
149
+ # require "google/cloud/firestore"
150
+ #
151
+ # firestore = Google::Cloud::Firestore.new
152
+ #
153
+ # # Get a document reference
154
+ # nyc_ref = firestore.doc "cities/NYC"
155
+ #
156
+ # array_union = Google::Cloud::Firestore::FieldValue.array_union(
157
+ # 1, 2, 3
158
+ # )
159
+ #
160
+ # nyc_ref.update({ name: "New York City",
161
+ # lucky_numbers: array_union })
162
+ #
163
+ def self.array_union *values
164
+ # We can flatten the values because arrays don't support sub-arrays
165
+ values.flatten!
166
+ raise ArgumentError, "values must be provided" if values.nil?
167
+ # verify the values are the correct types
168
+ Convert.raw_to_value values
169
+
170
+ new :array_union, values
171
+ end
172
+
173
+ ##
174
+ # Creates a sentinel value to indicate the removal of the given values
175
+ # with an array.
176
+ #
177
+ # @param [Object] values The values to remove from the array. Required.
178
+ #
179
+ # @return [FieldValue] The array delete field value object.
180
+ #
181
+ # @example
182
+ # require "google/cloud/firestore"
183
+ #
184
+ # firestore = Google::Cloud::Firestore.new
185
+ #
186
+ # # Get a document reference
187
+ # nyc_ref = firestore.doc "cities/NYC"
188
+ #
189
+ # array_delete = Google::Cloud::Firestore::FieldValue.array_delete(
190
+ # 7, 8, 9
191
+ # )
192
+ #
193
+ # nyc_ref.update({ name: "New York City",
194
+ # lucky_numbers: array_delete })
195
+ #
196
+ def self.array_delete *values
197
+ # We can flatten the values because arrays don't support sub-arrays
198
+ values.flatten!
199
+ raise ArgumentError, "values must be provided" if values.nil?
200
+ # verify the values are the correct types
201
+ Convert.raw_to_value values
202
+
203
+ new :array_delete, values
204
+ end
110
205
  end
111
206
  end
112
207
  end
@@ -212,6 +212,7 @@ module Google
212
212
  # * greater than: `>`, `gt`
213
213
  # * greater than or equal: `>=`, `gte`
214
214
  # * equal: `=`, `==`, `eq`, `eql`, `is`
215
+ # * array contains: `array-contains`, `array_contains`
215
216
  # @param [Object] value A value the field is compared to.
216
217
  #
217
218
  # @return [Query] New query with `where` called on it.
@@ -887,22 +888,41 @@ module Google
887
888
  ##
888
889
  # @private
889
890
  FILTER_OPS = {
890
- "<" => :LESS_THAN,
891
- "lt" => :LESS_THAN,
892
- "<=" => :LESS_THAN_OR_EQUAL,
893
- "lte" => :LESS_THAN_OR_EQUAL,
894
- ">" => :GREATER_THAN,
895
- "gt" => :GREATER_THAN,
896
- ">=" => :GREATER_THAN_OR_EQUAL,
897
- "gte" => :GREATER_THAN_OR_EQUAL,
898
- "=" => :EQUAL,
899
- "==" => :EQUAL,
900
- "eq" => :EQUAL,
901
- "eql" => :EQUAL,
902
- "is" => :EQUAL
891
+ "<" => :LESS_THAN,
892
+ "lt" => :LESS_THAN,
893
+ "<=" => :LESS_THAN_OR_EQUAL,
894
+ "lte" => :LESS_THAN_OR_EQUAL,
895
+ ">" => :GREATER_THAN,
896
+ "gt" => :GREATER_THAN,
897
+ ">=" => :GREATER_THAN_OR_EQUAL,
898
+ "gte" => :GREATER_THAN_OR_EQUAL,
899
+ "=" => :EQUAL,
900
+ "==" => :EQUAL,
901
+ "eq" => :EQUAL,
902
+ "eql" => :EQUAL,
903
+ "is" => :EQUAL,
904
+ "array_contains" => :ARRAY_CONTAINS,
905
+ "array-contains" => :ARRAY_CONTAINS,
906
+ "include" => :ARRAY_CONTAINS,
907
+ "include?" => :ARRAY_CONTAINS,
908
+ "has" => :ARRAY_CONTAINS
903
909
  }.freeze
904
910
  ##
905
911
  # @private
912
+ EQUALITY_FILTERS = %i[
913
+ EQUAL
914
+ ARRAY_CONTAINS
915
+ ].freeze
916
+ ##
917
+ # @private
918
+ INEQUALITY_FILTERS = %i[
919
+ LESS_THAN
920
+ LESS_THAN_OR_EQUAL
921
+ GREATER_THAN
922
+ GREATER_THAN_OR_EQUAL
923
+ ].freeze
924
+ ##
925
+ # @private
906
926
  UNARY_NIL_VALUES = [nil, :null, :nil].freeze
907
927
  ##
908
928
  # @private
@@ -1048,7 +1068,7 @@ module Google
1048
1068
  end
1049
1069
  ineq_filters = filters.select do |filter|
1050
1070
  if filter.filter_type == :field_filter
1051
- filter.field_filter.op != :EQUAL
1071
+ INEQUALITY_FILTERS.include? filter.field_filter.op
1052
1072
  end
1053
1073
  end
1054
1074
  ineq_filters.map { |filter| filter.field_filter.field.field_path }