google-cloud-firestore 2.0.0 → 2.4.1

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.
@@ -14,11 +14,14 @@
14
14
 
15
15
 
16
16
  require "google/cloud/firestore/watch/listener"
17
+ require "monitor"
17
18
 
18
19
  module Google
19
20
  module Cloud
20
21
  module Firestore
21
22
  ##
23
+ # # DocumentListener
24
+ #
22
25
  # An ongoing listen operation on a document reference. This is returned by
23
26
  # calling {DocumentReference#listen}.
24
27
  #
@@ -31,25 +34,28 @@ module Google
31
34
  # nyc_ref = firestore.doc "cities/NYC"
32
35
  #
33
36
  # listener = nyc_ref.listen do |snapshot|
34
- # puts "The population of #{snapshot[:name]} "
35
- # puts "is #{snapshot[:population]}."
37
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
36
38
  # end
37
39
  #
38
40
  # # When ready, stop the listen operation and close the stream.
39
41
  # listener.stop
40
42
  #
41
43
  class DocumentListener
44
+ include MonitorMixin
42
45
  ##
43
46
  # @private
44
47
  # Creates the watch stream and listener object.
45
48
  def initialize doc_ref, &callback
49
+ super() # to init MonitorMixin
50
+
46
51
  @doc_ref = doc_ref
47
52
  raise ArgumentError if @doc_ref.nil?
48
53
 
49
54
  @callback = callback
50
55
  raise ArgumentError if @callback.nil?
56
+ @error_callbacks = []
51
57
 
52
- @listener = Watch::Listener.for_doc_ref doc_ref do |query_snp|
58
+ @listener = Watch::Listener.for_doc_ref self, doc_ref do |query_snp|
53
59
  doc_snp = query_snp.docs.find { |doc| doc.path == @doc_ref.path }
54
60
 
55
61
  if doc_snp.nil?
@@ -80,8 +86,7 @@ module Google
80
86
  # nyc_ref = firestore.doc "cities/NYC"
81
87
  #
82
88
  # listener = nyc_ref.listen do |snapshot|
83
- # puts "The population of #{snapshot[:name]} "
84
- # puts "is #{snapshot[:population]}."
89
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
85
90
  # end
86
91
  #
87
92
  # # When ready, stop the listen operation and close the stream.
@@ -103,8 +108,7 @@ module Google
103
108
  # nyc_ref = firestore.doc "cities/NYC"
104
109
  #
105
110
  # listener = nyc_ref.listen do |snapshot|
106
- # puts "The population of #{snapshot[:name]} "
107
- # puts "is #{snapshot[:population]}."
111
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
108
112
  # end
109
113
  #
110
114
  # # Checks if the listener is stopped.
@@ -119,6 +123,81 @@ module Google
119
123
  def stopped?
120
124
  @listener.stopped?
121
125
  end
126
+
127
+ ##
128
+ # Register to be notified of errors when raised.
129
+ #
130
+ # If an unhandled error has occurred the listener will attempt to
131
+ # recover from the error and resume listening.
132
+ #
133
+ # Multiple error handlers can be added.
134
+ #
135
+ # @yield [callback] The block to be called when an error is raised.
136
+ # @yieldparam [Exception] error The error raised.
137
+ #
138
+ # @example
139
+ # require "google/cloud/firestore"
140
+ #
141
+ # firestore = Google::Cloud::Firestore.new
142
+ #
143
+ # # Get a document reference
144
+ # nyc_ref = firestore.doc "cities/NYC"
145
+ #
146
+ # listener = nyc_ref.listen do |snapshot|
147
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
148
+ # end
149
+ #
150
+ # # Register to be notified when unhandled errors occur.
151
+ # listener.on_error do |error|
152
+ # puts error
153
+ # end
154
+ #
155
+ # # When ready, stop the listen operation and close the stream.
156
+ # listener.stop
157
+ #
158
+ def on_error &block
159
+ raise ArgumentError, "on_error must be called with a block" unless block_given?
160
+ synchronize { @error_callbacks << block }
161
+ end
162
+
163
+ ##
164
+ # The most recent unhandled error to occur while listening for changes.
165
+ #
166
+ # If an unhandled error has occurred the listener will attempt to
167
+ # recover from the error and resume listening.
168
+ #
169
+ # @return [Exception, nil] error The most recent error raised.
170
+ #
171
+ # @example
172
+ # require "google/cloud/firestore"
173
+ #
174
+ # firestore = Google::Cloud::Firestore.new
175
+ #
176
+ # # Get a document reference
177
+ # nyc_ref = firestore.doc "cities/NYC"
178
+ #
179
+ # listener = nyc_ref.listen do |snapshot|
180
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
181
+ # end
182
+ #
183
+ # # If an error was raised, it can be retrieved here:
184
+ # listener.last_error #=> nil
185
+ #
186
+ # # When ready, stop the listen operation and close the stream.
187
+ # listener.stop
188
+ #
189
+ def last_error
190
+ synchronize { @last_error }
191
+ end
192
+
193
+ # @private Pass the error to user-provided error callbacks.
194
+ def error! error
195
+ error_callbacks = synchronize do
196
+ @last_error = error
197
+ @error_callbacks.dup
198
+ end
199
+ error_callbacks.each { |error_callback| error_callback.call error }
200
+ end
122
201
  end
123
202
  end
124
203
  end
@@ -168,8 +168,7 @@ module Google
168
168
  # nyc_ref = firestore.doc "cities/NYC"
169
169
  #
170
170
  # listener = nyc_ref.listen do |snapshot|
171
- # puts "The population of #{snapshot[:name]} "
172
- # puts "is #{snapshot[:population]}."
171
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
173
172
  # end
174
173
  #
175
174
  # # When ready, stop the listen operation and close the stream.
@@ -53,8 +53,7 @@ module Google
53
53
  # nyc_ref = firestore.doc "cities/NYC"
54
54
  #
55
55
  # listener = nyc_ref.listen do |snapshot|
56
- # puts "The population of #{snapshot[:name]} "
57
- # puts "is #{snapshot[:population]}."
56
+ # puts "The population of #{snapshot[:name]} is #{snapshot[:population]}."
58
57
  # end
59
58
  #
60
59
  # # When ready, stop the listen operation and close the stream.
@@ -62,6 +62,10 @@ module Google
62
62
  # @private The parent path for the query.
63
63
  attr_accessor :parent_path
64
64
 
65
+ ##
66
+ # @private The type for limit queries.
67
+ attr_reader :limit_type
68
+
65
69
  ##
66
70
  # @private The Google::Cloud::Firestore::V1::StructuredQuery object.
67
71
  attr_accessor :query
@@ -118,7 +122,7 @@ module Google
118
122
  new_query.select.fields << field_ref
119
123
  end
120
124
 
121
- Query.start new_query, parent_path, client
125
+ Query.start new_query, parent_path, client, limit_type: limit_type
122
126
  end
123
127
 
124
128
  ##
@@ -154,7 +158,7 @@ module Google
154
158
 
155
159
  new_query.from.last.all_descendants = true
156
160
 
157
- Query.start new_query, parent_path, client
161
+ Query.start new_query, parent_path, client, limit_type: limit_type
158
162
  end
159
163
 
160
164
  ##
@@ -190,7 +194,7 @@ module Google
190
194
 
191
195
  new_query.from.last.all_descendants = false
192
196
 
193
- Query.start new_query, parent_path, client
197
+ Query.start new_query, parent_path, client, limit_type: limit_type
194
198
  end
195
199
 
196
200
  ##
@@ -212,6 +216,9 @@ module Google
212
216
  # * greater than: `>`, `gt`
213
217
  # * greater than or equal: `>=`, `gte`
214
218
  # * equal: `=`, `==`, `eq`, `eql`, `is`
219
+ # * not equal: `!=`
220
+ # * in: `in`
221
+ # * not in: `not-in`, `not_in`
215
222
  # * array contains: `array-contains`, `array_contains`
216
223
  # @param [Object] value A value the field is compared to.
217
224
  #
@@ -246,7 +253,7 @@ module Google
246
253
  new_filter = filter field.formatted_string, operator, value
247
254
  add_filters_to_query new_query, new_filter
248
255
 
249
- Query.start new_query, parent_path, client
256
+ Query.start new_query, parent_path, client, limit_type: limit_type
250
257
  end
251
258
 
252
259
  ##
@@ -298,9 +305,8 @@ module Google
298
305
  # end
299
306
  #
300
307
  def order field, direction = :asc
301
- if query_has_cursors?
302
- raise "cannot call order after calling " \
303
- "start_at, start_after, end_before, or end_at"
308
+ if query_has_cursors? || limit_type == :last
309
+ raise "cannot call order after calling limit_to_last, start_at, start_after, end_before, or end_at"
304
310
  end
305
311
 
306
312
  new_query = @query.dup
@@ -315,7 +321,7 @@ module Google
315
321
  direction: order_direction(direction)
316
322
  )
317
323
 
318
- Query.start new_query, parent_path, client
324
+ Query.start new_query, parent_path, client, limit_type: limit_type
319
325
  end
320
326
  alias order_by order
321
327
 
@@ -348,12 +354,13 @@ module Google
348
354
 
349
355
  new_query.offset = num
350
356
 
351
- Query.start new_query, parent_path, client
357
+ Query.start new_query, parent_path, client, limit_type: limit_type
352
358
  end
353
359
 
354
360
  ##
355
- # Limits a query to return a fixed number of results. If the current
356
- # query already has a limit set, this will overwrite it.
361
+ # Limits a query to return only the first matching documents.
362
+ #
363
+ # If the current query already has a limit set, this will overwrite it.
357
364
  #
358
365
  # @param [Integer] num The maximum number of results to return.
359
366
  #
@@ -368,19 +375,83 @@ module Google
368
375
  # cities_col = firestore.col "cities"
369
376
  #
370
377
  # # Create a query
371
- # query = cities_col.offset(10).limit(5)
378
+ # query = cities_col.order(:name, :desc).offset(10).limit(5)
372
379
  #
373
380
  # query.get do |city|
374
381
  # puts "#{city.document_id} has #{city[:population]} residents."
375
382
  # end
376
383
  #
377
384
  def limit num
385
+ if limit_type == :last
386
+ raise "cannot call limit after calling limit_to_last"
387
+ end
388
+
378
389
  new_query = @query.dup
379
390
  new_query ||= StructuredQuery.new
380
391
 
381
392
  new_query.limit = Google::Protobuf::Int32Value.new value: num
382
393
 
383
- Query.start new_query, parent_path, client
394
+ Query.start new_query, parent_path, client, limit_type: :first
395
+ end
396
+
397
+ ##
398
+ # Limits a query to return only the last matching documents.
399
+ #
400
+ # You must specify at least one "order by" clause for limitToLast queries.
401
+ # (See {#order}.)
402
+ #
403
+ # Results for `limit_to_last` queries are only available once all documents
404
+ # are received. Hence, `limit_to_last` queries cannot be streamed using
405
+ # {#listen}.
406
+ #
407
+ # @param [Integer] num The maximum number of results to return.
408
+ #
409
+ # @return [Query] New query with `limit_to_last` called on it.
410
+ #
411
+ # @example
412
+ # require "google/cloud/firestore"
413
+ #
414
+ # firestore = Google::Cloud::Firestore.new
415
+ #
416
+ # # Get a collection reference
417
+ # cities_col = firestore.col "cities"
418
+ #
419
+ # # Create a query
420
+ # query = cities_col.order(:name, :desc).limit_to_last(5)
421
+ #
422
+ # query.get do |city|
423
+ # puts "#{city.document_id} has #{city[:population]} residents."
424
+ # end
425
+ #
426
+ def limit_to_last num
427
+ new_query = @query.dup
428
+
429
+ if new_query.nil? || new_query.order_by.nil? || new_query.order_by.empty?
430
+ raise "specify at least one order clause before calling limit_to_last"
431
+ end
432
+
433
+ if limit_type != :last # Don't reverse order_by more than once.
434
+ # Reverse the order_by directions since we want the last results.
435
+ new_query.order_by.each do |order|
436
+ order.direction = order.direction.to_sym == :DESCENDING ? :ASCENDING : :DESCENDING
437
+ end
438
+
439
+ # Swap the cursors to match the reversed query ordering.
440
+ new_end_at = new_query.start_at.dup
441
+ new_start_at = new_query.end_at.dup
442
+ if new_end_at
443
+ new_end_at.before = !new_end_at.before
444
+ new_query.end_at = new_end_at
445
+ end
446
+ if new_start_at
447
+ new_start_at.before = !new_start_at.before
448
+ new_query.start_at = new_start_at
449
+ end
450
+ end
451
+
452
+ new_query.limit = Google::Protobuf::Int32Value.new value: num
453
+
454
+ Query.start new_query, parent_path, client, limit_type: :last
384
455
  end
385
456
 
386
457
  ##
@@ -477,6 +548,10 @@ module Google
477
548
  def start_at *values
478
549
  raise ArgumentError, "must provide values" if values.empty?
479
550
 
551
+ if limit_type == :last
552
+ raise "cannot call start_at after calling limit_to_last"
553
+ end
554
+
480
555
  new_query = @query.dup
481
556
  new_query ||= StructuredQuery.new
482
557
 
@@ -484,7 +559,7 @@ module Google
484
559
  cursor.before = true
485
560
  new_query.start_at = cursor
486
561
 
487
- Query.start new_query, parent_path, client
562
+ Query.start new_query, parent_path, client, limit_type: limit_type
488
563
  end
489
564
 
490
565
  ##
@@ -581,6 +656,11 @@ module Google
581
656
  def start_after *values
582
657
  raise ArgumentError, "must provide values" if values.empty?
583
658
 
659
+ if limit_type == :last
660
+ raise "cannot call start_after after calling limit_to_last"
661
+ end
662
+
663
+
584
664
  new_query = @query.dup
585
665
  new_query ||= StructuredQuery.new
586
666
 
@@ -588,7 +668,7 @@ module Google
588
668
  cursor.before = false
589
669
  new_query.start_at = cursor
590
670
 
591
- Query.start new_query, parent_path, client
671
+ Query.start new_query, parent_path, client, limit_type: limit_type
592
672
  end
593
673
 
594
674
  ##
@@ -685,6 +765,11 @@ module Google
685
765
  def end_before *values
686
766
  raise ArgumentError, "must provide values" if values.empty?
687
767
 
768
+ if limit_type == :last
769
+ raise "cannot call end_before after calling limit_to_last"
770
+ end
771
+
772
+
688
773
  new_query = @query.dup
689
774
  new_query ||= StructuredQuery.new
690
775
 
@@ -692,7 +777,7 @@ module Google
692
777
  cursor.before = true
693
778
  new_query.end_at = cursor
694
779
 
695
- Query.start new_query, parent_path, client
780
+ Query.start new_query, parent_path, client, limit_type: limit_type
696
781
  end
697
782
 
698
783
  ##
@@ -789,6 +874,11 @@ module Google
789
874
  def end_at *values
790
875
  raise ArgumentError, "must provide values" if values.empty?
791
876
 
877
+ if limit_type == :last
878
+ raise "cannot call end_at after calling limit_to_last"
879
+ end
880
+
881
+
792
882
  new_query = @query.dup
793
883
  new_query ||= StructuredQuery.new
794
884
 
@@ -796,7 +886,7 @@ module Google
796
886
  cursor.before = false
797
887
  new_query.end_at = cursor
798
888
 
799
- Query.start new_query, parent_path, client
889
+ Query.start new_query, parent_path, client, limit_type: limit_type
800
890
  end
801
891
 
802
892
  ##
@@ -828,6 +918,10 @@ module Google
828
918
  return enum_for :get unless block_given?
829
919
 
830
920
  results = service.run_query parent_path, @query
921
+
922
+ # Reverse the results for Query#limit_to_last queries since that method reversed the order_by directions.
923
+ results = results.to_a.reverse if limit_type == :last
924
+
831
925
  results.each do |result|
832
926
  next if result.document.nil?
833
927
  yield DocumentSnapshot.from_query_result result, client
@@ -870,11 +964,12 @@ module Google
870
964
 
871
965
  ##
872
966
  # @private Start a new Query.
873
- def self.start query, parent_path, client
967
+ def self.start query, parent_path, client, limit_type: nil
874
968
  query ||= StructuredQuery.new
875
969
  Query.new.tap do |q|
876
970
  q.instance_variable_set :@query, query
877
971
  q.instance_variable_set :@parent_path, parent_path
972
+ q.instance_variable_set :@limit_type, limit_type
878
973
  q.instance_variable_set :@client, client
879
974
  end
880
975
  end
@@ -901,23 +996,20 @@ module Google
901
996
  "eq" => :EQUAL,
902
997
  "eql" => :EQUAL,
903
998
  "is" => :EQUAL,
999
+ "!=" => :NOT_EQUAL,
904
1000
  "array_contains" => :ARRAY_CONTAINS,
905
1001
  "array-contains" => :ARRAY_CONTAINS,
906
1002
  "include" => :ARRAY_CONTAINS,
907
1003
  "include?" => :ARRAY_CONTAINS,
908
1004
  "has" => :ARRAY_CONTAINS,
909
1005
  "in" => :IN,
1006
+ "not_in" => :NOT_IN,
1007
+ "not-in" => :NOT_IN,
910
1008
  "array_contains_any" => :ARRAY_CONTAINS_ANY,
911
1009
  "array-contains-any" => :ARRAY_CONTAINS_ANY
912
1010
  }.freeze
913
1011
  ##
914
1012
  # @private
915
- EQUALITY_FILTERS = %i[
916
- EQUAL
917
- ARRAY_CONTAINS
918
- ].freeze
919
- ##
920
- # @private
921
1013
  INEQUALITY_FILTERS = %i[
922
1014
  LESS_THAN
923
1015
  LESS_THAN_OR_EQUAL
@@ -945,12 +1037,13 @@ module Google
945
1037
  raise ArgumentError, "unknown operator #{op}" if operator.nil?
946
1038
 
947
1039
  if value_unary? value
948
- if operator != :EQUAL
949
- raise ArgumentError,
950
- "can only check equality for #{value} values"
951
- end
952
-
953
- operator = value_nan?(value) ? :IS_NAN : :IS_NULL
1040
+ operator = if operator == :EQUAL
1041
+ value_nan?(value) ? :IS_NAN : :IS_NULL
1042
+ elsif operator == :NOT_EQUAL
1043
+ value_nan?(value) ? :IS_NOT_NAN : :IS_NOT_NULL
1044
+ else
1045
+ raise ArgumentError, "can only perform '==' and '!=' comparisons on #{value} values"
1046
+ end
954
1047
 
955
1048
  return StructuredQuery::Filter.new(
956
1049
  unary_filter: StructuredQuery::UnaryFilter.new(