google-cloud-firestore 0.23.0 → 0.24.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/.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 }