google-cloud-firestore 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +8 -0
  3. data/LICENSE +201 -0
  4. data/README.md +30 -0
  5. data/lib/google-cloud-firestore.rb +106 -0
  6. data/lib/google/cloud/firestore.rb +514 -0
  7. data/lib/google/cloud/firestore/batch.rb +462 -0
  8. data/lib/google/cloud/firestore/client.rb +449 -0
  9. data/lib/google/cloud/firestore/collection_reference.rb +249 -0
  10. data/lib/google/cloud/firestore/commit_response.rb +145 -0
  11. data/lib/google/cloud/firestore/convert.rb +561 -0
  12. data/lib/google/cloud/firestore/credentials.rb +35 -0
  13. data/lib/google/cloud/firestore/document_reference.rb +468 -0
  14. data/lib/google/cloud/firestore/document_snapshot.rb +324 -0
  15. data/lib/google/cloud/firestore/field_path.rb +216 -0
  16. data/lib/google/cloud/firestore/field_value.rb +113 -0
  17. data/lib/google/cloud/firestore/generate.rb +35 -0
  18. data/lib/google/cloud/firestore/query.rb +651 -0
  19. data/lib/google/cloud/firestore/service.rb +176 -0
  20. data/lib/google/cloud/firestore/transaction.rb +726 -0
  21. data/lib/google/cloud/firestore/v1beta1.rb +121 -0
  22. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/common.rb +63 -0
  23. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/document.rb +134 -0
  24. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/firestore.rb +584 -0
  25. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/query.rb +215 -0
  26. data/lib/google/cloud/firestore/v1beta1/doc/google/firestore/v1beta1/write.rb +167 -0
  27. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/any.rb +124 -0
  28. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/timestamp.rb +106 -0
  29. data/lib/google/cloud/firestore/v1beta1/doc/google/protobuf/wrappers.rb +89 -0
  30. data/lib/google/cloud/firestore/v1beta1/doc/google/rpc/status.rb +83 -0
  31. data/lib/google/cloud/firestore/v1beta1/doc/overview.rb +53 -0
  32. data/lib/google/cloud/firestore/v1beta1/firestore_client.rb +974 -0
  33. data/lib/google/cloud/firestore/v1beta1/firestore_client_config.json +100 -0
  34. data/lib/google/cloud/firestore/version.rb +22 -0
  35. data/lib/google/firestore/v1beta1/common_pb.rb +44 -0
  36. data/lib/google/firestore/v1beta1/document_pb.rb +49 -0
  37. data/lib/google/firestore/v1beta1/firestore_pb.rb +219 -0
  38. data/lib/google/firestore/v1beta1/firestore_services_pb.rb +87 -0
  39. data/lib/google/firestore/v1beta1/query_pb.rb +103 -0
  40. data/lib/google/firestore/v1beta1/write_pb.rb +73 -0
  41. metadata +251 -0
@@ -0,0 +1,113 @@
1
+ # Copyright 2017 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
+ # # FieldValue
21
+ #
22
+ # Represents a change to be made to fields in document data in the
23
+ # Firestore API.
24
+ #
25
+ # @example
26
+ # require "google/cloud/firestore"
27
+ #
28
+ # firestore = Google::Cloud::Firestore.new
29
+ #
30
+ # user_snap = firestore.doc("users/frank").get
31
+ #
32
+ # # TODO
33
+ #
34
+ class FieldValue
35
+ ##
36
+ # @private Creates a field value object representing changes made to
37
+ # fields in document data.
38
+ def initialize type
39
+ @type = type
40
+ end
41
+
42
+ ##
43
+ # The type of change to make to an individual field in document data.
44
+ #
45
+ # @return [Symbol] The type.
46
+ #
47
+ # @example
48
+ # require "google/cloud/firestore"
49
+ #
50
+ # firestore = Google::Cloud::Firestore.new
51
+ #
52
+ # # Get a document reference
53
+ # nyc_ref = firestore.doc "cities/NYC"
54
+ #
55
+ # field_delete = Google::Cloud::Firestore::FieldValue.delete
56
+ # field_delete.type #=> :delete
57
+ #
58
+ # nyc_ref.update({ name: "New York City",
59
+ # trash: field_delete })
60
+ #
61
+ def type
62
+ @type
63
+ end
64
+
65
+ ##
66
+ # Creates a field value object representing the deletion of a field in
67
+ # document data.
68
+ #
69
+ # @return [FieldValue] The delete field value object.
70
+ #
71
+ # @example
72
+ # require "google/cloud/firestore"
73
+ #
74
+ # firestore = Google::Cloud::Firestore.new
75
+ #
76
+ # # Get a document reference
77
+ # nyc_ref = firestore.doc "cities/NYC"
78
+ #
79
+ # field_delete = Google::Cloud::Firestore::FieldValue.delete
80
+ #
81
+ # nyc_ref.update({ name: "New York City",
82
+ # trash: field_delete })
83
+ #
84
+ def self.delete
85
+ new :delete
86
+ end
87
+
88
+ ##
89
+ # Creates a field value object representing set a field's value to
90
+ # the server timestamp when accessing the document data.
91
+ #
92
+ # @return [FieldValue] The server time field value object.
93
+ #
94
+ # @example
95
+ # require "google/cloud/firestore"
96
+ #
97
+ # firestore = Google::Cloud::Firestore.new
98
+ #
99
+ # # Get a document reference
100
+ # nyc_ref = firestore.doc "cities/NYC"
101
+ #
102
+ # field_server_time = Google::Cloud::Firestore::FieldValue.server_time
103
+ #
104
+ # nyc_ref.update({ name: "New York City",
105
+ # updated_at: field_server_time })
106
+ #
107
+ def self.server_time
108
+ new :server_time
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright 2017 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 "securerandom"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Firestore
21
+ ##
22
+ # @private Helper module for generating random values
23
+ module Generate
24
+ CHARS = [*"a".."z", *"A".."Z", *"0".."9"]
25
+
26
+ def self.unique_id length: 20, chars: CHARS
27
+ size = chars.size
28
+ length.times.map do
29
+ chars[SecureRandom.random_number(size)]
30
+ end.join
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,651 @@
1
+ # Copyright 2017 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/v1beta1"
17
+ require "google/cloud/firestore/document_snapshot"
18
+ require "google/cloud/firestore/convert"
19
+
20
+ module Google
21
+ module Cloud
22
+ module Firestore
23
+ ##
24
+ # # Query
25
+ #
26
+ # Represents a query to the Firestore API.
27
+ #
28
+ # Instances of this class are immutable. All methods that refine the query
29
+ # return new instances.
30
+ #
31
+ # @example
32
+ # require "google/cloud/firestore"
33
+ #
34
+ # firestore = Google::Cloud::Firestore.new
35
+ #
36
+ # # Create a query
37
+ # query = firestore.col(:cities).select(:population)
38
+ #
39
+ # query.get do |city|
40
+ # puts "#{city.document_id} has #{city[:population]} residents."
41
+ # end
42
+ #
43
+ class Query
44
+ ##
45
+ # @private The parent path for the query.
46
+ attr_accessor :parent_path
47
+
48
+ ##
49
+ # @private The Google::Firestore::V1beta1::Query object.
50
+ attr_accessor :query
51
+
52
+ ##
53
+ # @private The firestore client object.
54
+ attr_accessor :client
55
+
56
+ ##
57
+ # Restricts documents matching the query to return only data for the
58
+ # provided fields.
59
+ #
60
+ # @param [FieldPath, String, Symbol] fields A field path to
61
+ # filter results with and return only the specified fields. One or
62
+ # more field paths can be specified.
63
+ #
64
+ # If a {FieldPath} object is not provided then the field will be
65
+ # treated as a dotted string, meaning the string represents individual
66
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
67
+ # `.` cannot be in a dotted string, and should provided using a
68
+ # {FieldPath} object instead.
69
+ #
70
+ # @return [Query] New query with `select` called on it.
71
+ #
72
+ # @example
73
+ # require "google/cloud/firestore"
74
+ #
75
+ # firestore = Google::Cloud::Firestore.new
76
+ #
77
+ # # Get a collection reference
78
+ # cities_col = firestore.col "cities"
79
+ #
80
+ # # Create a query
81
+ # query = cities_col.select(:population)
82
+ #
83
+ # query.get do |city|
84
+ # puts "#{city.document_id} has #{city[:population]} residents."
85
+ # end
86
+ #
87
+ def select *fields
88
+ new_query = @query.dup
89
+ new_query ||= StructuredQuery.new
90
+
91
+ field_refs = fields.flatten.compact.map do |field|
92
+ field = FieldPath.parse field unless field.is_a? FieldPath
93
+ StructuredQuery::FieldReference.new \
94
+ field_path: field.formatted_string
95
+ end
96
+
97
+ new_query.select ||= StructuredQuery::Projection.new
98
+ field_refs.each do |field_ref|
99
+ new_query.select.fields << field_ref
100
+ end
101
+
102
+ Query.start new_query, parent_path, client
103
+ end
104
+
105
+ ##
106
+ # @private This is marked private and can't be removed.
107
+ #
108
+ # Selects documents from all collections, immediate children and nested,
109
+ # of where the query was created from.
110
+ #
111
+ # @return [Query] New query with `all_descendants` called on it.
112
+ #
113
+ # @example
114
+ # require "google/cloud/firestore"
115
+ #
116
+ # firestore = Google::Cloud::Firestore.new
117
+ #
118
+ # # Get a collection reference
119
+ # cities_col = firestore.col "cities"
120
+ #
121
+ # # Create a query
122
+ # query = cities_col.all_descendants
123
+ #
124
+ # query.get do |city|
125
+ # puts "#{city.document_id} has #{city[:population]} residents."
126
+ # end
127
+ #
128
+ def all_descendants
129
+ new_query = @query.dup
130
+ new_query ||= StructuredQuery.new
131
+
132
+ if new_query.from.empty?
133
+ fail "missing collection_id to specify descendants."
134
+ end
135
+
136
+ new_query.from.last.all_descendants = true
137
+
138
+ Query.start new_query, parent_path, client
139
+ end
140
+
141
+ ##
142
+ # @private This is marked private and can't be removed.
143
+ #
144
+ # Selects only documents from collections that are immediate children of
145
+ # where the query was created from.
146
+ #
147
+ # @return [Query] New query with `direct_descendants` called on it.
148
+ #
149
+ # @example
150
+ # require "google/cloud/firestore"
151
+ #
152
+ # firestore = Google::Cloud::Firestore.new
153
+ #
154
+ # # Get a collection reference
155
+ # cities_col = firestore.col "cities"
156
+ #
157
+ # # Create a query
158
+ # query = cities_col.direct_descendants
159
+ #
160
+ # query.get do |city|
161
+ # puts "#{city.document_id} has #{city[:population]} residents."
162
+ # end
163
+ #
164
+ def direct_descendants
165
+ new_query = @query.dup
166
+ new_query ||= StructuredQuery.new
167
+
168
+ if new_query.from.empty?
169
+ fail "missing collection_id to specify descendants."
170
+ end
171
+
172
+ new_query.from.last.all_descendants = false
173
+
174
+ Query.start new_query, parent_path, client
175
+ end
176
+
177
+ ##
178
+ # Filters the query on a field.
179
+ #
180
+ # @param [FieldPath, String, Symbol] field A field path to filter
181
+ # results with.
182
+ #
183
+ # If a {FieldPath} object is not provided then the field will be
184
+ # treated as a dotted string, meaning the string represents individual
185
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
186
+ # `.` cannot be in a dotted string, and should provided using a
187
+ # {FieldPath} object instead.
188
+ # @param [String, Symbol] operator The operation to compare the field
189
+ # to. Acceptable values include:
190
+ #
191
+ # * less than: `<`, `lt`
192
+ # * less than or equal: `<=`, `lte`
193
+ # * greater than: `>`, `gt`
194
+ # * greater than or equal: `>=`, `gte`
195
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
196
+ # @param [Object] value A value the field is compared to.
197
+ #
198
+ # @return [Query] New query with `where` called on it.
199
+ #
200
+ # @example
201
+ # require "google/cloud/firestore"
202
+ #
203
+ # firestore = Google::Cloud::Firestore.new
204
+ #
205
+ # # Get a collection reference
206
+ # cities_col = firestore.col "cities"
207
+ #
208
+ # # Create a query
209
+ # query = cities_col.where(:population, :>=, 1000000)
210
+ #
211
+ # query.get do |city|
212
+ # puts "#{city.document_id} has #{city[:population]} residents."
213
+ # end
214
+ #
215
+ def where field, operator, value
216
+ new_query = @query.dup
217
+ new_query ||= StructuredQuery.new
218
+
219
+ field = FieldPath.parse field unless field.is_a? FieldPath
220
+
221
+ new_query.where ||= default_filter
222
+ new_query.where.composite_filter.filters << \
223
+ filter(field.formatted_string, operator, value)
224
+
225
+ Query.start new_query, parent_path, client
226
+ end
227
+
228
+ ##
229
+ # Specifies an "order by" clause on a field.
230
+ #
231
+ # @param [FieldPath, String, Symbol] field A field path to order results
232
+ # with.
233
+ #
234
+ # If a {FieldPath} object is not provided then the field will be
235
+ # treated as a dotted string, meaning the string represents individual
236
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
237
+ # `.` cannot be in a dotted string, and should provided using a
238
+ # {FieldPath} object instead.
239
+ # @param [String, Symbol] direction The direction to order the results
240
+ # by. Values that start with "a" are considered `ascending`. Values
241
+ # that start with "d" are considered `descending`. Default is
242
+ # `ascending`. Optional.
243
+ #
244
+ # @return [Query] New query with `order` called on it.
245
+ #
246
+ # @example
247
+ # require "google/cloud/firestore"
248
+ #
249
+ # firestore = Google::Cloud::Firestore.new
250
+ #
251
+ # # Get a collection reference
252
+ # cities_col = firestore.col "cities"
253
+ #
254
+ # # Create a query
255
+ # query = cities_col.order(:name)
256
+ #
257
+ # query.get do |city|
258
+ # puts "#{city.document_id} has #{city[:population]} residents."
259
+ # end
260
+ #
261
+ # @example Order by name descending:
262
+ # require "google/cloud/firestore"
263
+ #
264
+ # firestore = Google::Cloud::Firestore.new
265
+ #
266
+ # # Get a collection reference
267
+ # cities_col = firestore.col "cities"
268
+ #
269
+ # # Create a query
270
+ # query = cities_col.order(:name, :desc)
271
+ #
272
+ # query.get do |city|
273
+ # puts "#{city.document_id} has #{city[:population]} residents."
274
+ # end
275
+ #
276
+ def order field, direction = :asc
277
+ new_query = @query.dup
278
+ new_query ||= StructuredQuery.new
279
+
280
+ field = FieldPath.parse field unless field.is_a? FieldPath
281
+
282
+ new_query.order_by << StructuredQuery::Order.new(
283
+ field: StructuredQuery::FieldReference.new(
284
+ field_path: field.formatted_string
285
+ ),
286
+ direction: order_direction(direction))
287
+
288
+ Query.start new_query, parent_path, client
289
+ end
290
+ alias_method :order_by, :order
291
+
292
+ ##
293
+ # Skips to an offset in a query. If the current query already has
294
+ # specified an offset, this will overwrite it.
295
+ #
296
+ # @param [Integer] num The number of results to skip.
297
+ #
298
+ # @return [Query] New query with `offset` called on it.
299
+ #
300
+ # @example
301
+ # require "google/cloud/firestore"
302
+ #
303
+ # firestore = Google::Cloud::Firestore.new
304
+ #
305
+ # # Get a collection reference
306
+ # cities_col = firestore.col "cities"
307
+ #
308
+ # # Create a query
309
+ # query = cities_col.limit(5).offset(10)
310
+ #
311
+ # query.get do |city|
312
+ # puts "#{city.document_id} has #{city[:population]} residents."
313
+ # end
314
+ #
315
+ def offset num
316
+ new_query = @query.dup
317
+ new_query ||= StructuredQuery.new
318
+
319
+ new_query.offset = num
320
+
321
+ Query.start new_query, parent_path, client
322
+ end
323
+
324
+ ##
325
+ # Limits a query to return a fixed number of results. If the current
326
+ # query already has a limit set, this will overwrite it.
327
+ #
328
+ # @param [Integer] num The maximum number of results to return.
329
+ #
330
+ # @return [Query] New query with `limit` called on it.
331
+ #
332
+ # @example
333
+ # require "google/cloud/firestore"
334
+ #
335
+ # firestore = Google::Cloud::Firestore.new
336
+ #
337
+ # # Get a collection reference
338
+ # cities_col = firestore.col "cities"
339
+ #
340
+ # # Create a query
341
+ # query = cities_col.offset(10).limit(5)
342
+ #
343
+ # query.get do |city|
344
+ # puts "#{city.document_id} has #{city[:population]} residents."
345
+ # end
346
+ #
347
+ def limit num
348
+ new_query = @query.dup
349
+ new_query ||= StructuredQuery.new
350
+
351
+ new_query.limit = Google::Protobuf::Int32Value.new(value: num)
352
+
353
+ Query.start new_query, parent_path, client
354
+ end
355
+
356
+ ##
357
+ # Starts query results at a set of field values. The result set will
358
+ # include the document specified by `values`.
359
+ #
360
+ # If the current query already has specified `start_at` or
361
+ # `start_after`, this will overwrite it.
362
+ #
363
+ # The values provided here are for the field paths provides to `order`.
364
+ # Values provided to `start_at` without an associated field path
365
+ # provided to `order` will result in an error.
366
+ #
367
+ # @param [Object] values The field value to start the query at.
368
+ #
369
+ # @return [Query] New query with `start_at` called on it.
370
+ #
371
+ # @example
372
+ # require "google/cloud/firestore"
373
+ #
374
+ # firestore = Google::Cloud::Firestore.new
375
+ #
376
+ # # Get a collection reference
377
+ # cities_col = firestore.col "cities"
378
+ #
379
+ # # Create a query
380
+ # query = cities_col.start_at("NYC").order(firestore.document_id)
381
+ #
382
+ # query.get do |city|
383
+ # puts "#{city.document_id} has #{city[:population]} residents."
384
+ # end
385
+ #
386
+ def start_at *values
387
+ new_query = @query.dup
388
+ new_query ||= StructuredQuery.new
389
+
390
+ values = values.flatten.map { |value| Convert.raw_to_value value }
391
+ new_query.start_at = Google::Firestore::V1beta1::Cursor.new(
392
+ values: values, before: true)
393
+
394
+ Query.start new_query, parent_path, client
395
+ end
396
+
397
+
398
+ ##
399
+ # Starts query results after a set of field values. The result set will
400
+ # not include the document specified by `values`.
401
+ #
402
+ # If the current query already has specified `start_at` or
403
+ # `start_after`, this will overwrite it.
404
+ #
405
+ # The values provided here are for the field paths provides to `order`.
406
+ # Values provided to `start_after` without an associated field path
407
+ # provided to `order` will result in an error.
408
+ #
409
+ # @param [Object] values The field value to start the query after.
410
+ #
411
+ # @return [Query] New query with `start_after` called on it.
412
+ #
413
+ # @example
414
+ # require "google/cloud/firestore"
415
+ #
416
+ # firestore = Google::Cloud::Firestore.new
417
+ #
418
+ # # Get a collection reference
419
+ # cities_col = firestore.col "cities"
420
+ #
421
+ # # Create a query
422
+ # query = cities_col.start_after("NYC").order(firestore.document_id)
423
+ #
424
+ # query.get do |city|
425
+ # puts "#{city.document_id} has #{city[:population]} residents."
426
+ # end
427
+ #
428
+ def start_after *values
429
+ new_query = @query.dup
430
+ new_query ||= StructuredQuery.new
431
+
432
+ values = values.flatten.map { |value| Convert.raw_to_value value }
433
+ new_query.start_at = Google::Firestore::V1beta1::Cursor.new(
434
+ values: values, before: false)
435
+
436
+ Query.start new_query, parent_path, client
437
+ end
438
+
439
+ ##
440
+ # Ends query results before a set of field values. The result set will
441
+ # not include the document specified by `values`.
442
+ #
443
+ # If the current query already has specified `end_before` or
444
+ # `end_at`, this will overwrite it.
445
+ #
446
+ # The values provided here are for the field paths provides to `order`.
447
+ # Values provided to `end_before` without an associated field path
448
+ # provided to `order` will result in an error.
449
+ #
450
+ # @param [Object] values The field value to end the query before.
451
+ #
452
+ # @return [Query] New query with `end_before` called on it.
453
+ #
454
+ # @example
455
+ # require "google/cloud/firestore"
456
+ #
457
+ # firestore = Google::Cloud::Firestore.new
458
+ #
459
+ # # Get a collection reference
460
+ # cities_col = firestore.col "cities"
461
+ #
462
+ # # Create a query
463
+ # query = cities_col.end_before("NYC").order(firestore.document_id)
464
+ #
465
+ # query.get do |city|
466
+ # puts "#{city.document_id} has #{city[:population]} residents."
467
+ # end
468
+ #
469
+ def end_before *values
470
+ new_query = @query.dup
471
+ new_query ||= StructuredQuery.new
472
+
473
+ values = values.flatten.map { |value| Convert.raw_to_value value }
474
+ new_query.end_at = Google::Firestore::V1beta1::Cursor.new(
475
+ values: values, before: true)
476
+
477
+ Query.start new_query, parent_path, client
478
+ end
479
+
480
+ ##
481
+ # Ends query results at a set of field values. The result set will
482
+ # include the document specified by `values`.
483
+ #
484
+ # If the current query already has specified `end_before` or
485
+ # `end_at`, this will overwrite it.
486
+ #
487
+ # The values provided here are for the field paths provides to `order`.
488
+ # Values provided to `end_at` without an associated field path provided
489
+ # to `order` will result in an error.
490
+ #
491
+ # @param [Object] values The field value to end the query at.
492
+ #
493
+ # @return [Query] New query with `end_at` called on it.
494
+ #
495
+ # @example
496
+ # require "google/cloud/firestore"
497
+ #
498
+ # firestore = Google::Cloud::Firestore.new
499
+ #
500
+ # # Get a collection reference
501
+ # cities_col = firestore.col "cities"
502
+ #
503
+ # # Create a query
504
+ # query = cities_col.end_at("NYC").order(firestore.document_id)
505
+ #
506
+ # query.get do |city|
507
+ # puts "#{city.document_id} has #{city[:population]} residents."
508
+ # end
509
+ #
510
+ def end_at *values
511
+ new_query = @query.dup
512
+ new_query ||= StructuredQuery.new
513
+
514
+ values = values.flatten.map { |value| Convert.raw_to_value value }
515
+ new_query.end_at = Google::Firestore::V1beta1::Cursor.new(
516
+ values: values, before: false)
517
+
518
+ Query.start new_query, parent_path, client
519
+ end
520
+
521
+ ##
522
+ # Retrieves document snapshots for the query.
523
+ #
524
+ # @yield [documents] The block for accessing the document snapshots.
525
+ # @yieldparam [DocumentReference] document A document snapshot.
526
+ #
527
+ # @return [Enumerator<DocumentReference>] A list of document snapshots.
528
+ #
529
+ # @example
530
+ # require "google/cloud/firestore"
531
+ #
532
+ # firestore = Google::Cloud::Firestore.new
533
+ #
534
+ # # Get a collection reference
535
+ # cities_col = firestore.col "cities"
536
+ #
537
+ # # Create a query
538
+ # query = cities_col.select(:population)
539
+ #
540
+ # query.get do |city|
541
+ # puts "#{city.document_id} has #{city[:population]} residents."
542
+ # end
543
+ #
544
+ def get
545
+ ensure_service!
546
+
547
+ return enum_for(:run) unless block_given?
548
+
549
+ results = service.run_query parent_path, @query
550
+ results.each do |result|
551
+ next if result.document.nil?
552
+ yield DocumentSnapshot.from_query_result(result, self)
553
+ end
554
+ end
555
+ alias_method :run, :get
556
+
557
+ ##
558
+ # @private Start a new Query.
559
+ def self.start query, parent_path, client
560
+ query ||= StructuredQuery.new
561
+ Query.new.tap do |q|
562
+ q.instance_variable_set :@query, query
563
+ q.instance_variable_set :@parent_path, parent_path
564
+ q.instance_variable_set :@client, client
565
+ end
566
+ end
567
+
568
+ protected
569
+
570
+ StructuredQuery = Google::Firestore::V1beta1::StructuredQuery
571
+
572
+ FILTER_OPS = {
573
+ "<" => :LESS_THAN,
574
+ "lt" => :LESS_THAN,
575
+ "<=" => :LESS_THAN_OR_EQUAL,
576
+ "lte" => :LESS_THAN_OR_EQUAL,
577
+ ">" => :GREATER_THAN,
578
+ "gt" => :GREATER_THAN,
579
+ ">=" => :GREATER_THAN_OR_EQUAL,
580
+ "gte" => :GREATER_THAN_OR_EQUAL,
581
+ "=" => :EQUAL,
582
+ "==" => :EQUAL,
583
+ "eq" => :EQUAL,
584
+ "eql" => :EQUAL,
585
+ "is" => :EQUAL }
586
+ UNARY_NIL_VALUES = [nil, :null, :nil]
587
+ UNARY_NAN_VALUES = [:nan, Float::NAN]
588
+ UNARY_VALUES = UNARY_NIL_VALUES + UNARY_NAN_VALUES
589
+
590
+ def filter name, op, value
591
+ field = StructuredQuery::FieldReference.new field_path: name.to_s
592
+ op = FILTER_OPS[op.to_s.downcase] || :EQUAL
593
+
594
+ is_value_nan = value.respond_to?(:nan?) && value.nan?
595
+ if UNARY_VALUES.include?(value) || is_value_nan
596
+ if op != :EQUAL
597
+ fail ArgumentError, "can only check equality for #{value} values."
598
+ end
599
+
600
+ op = :IS_NULL
601
+ op = :IS_NAN if UNARY_NAN_VALUES.include?(value) || is_value_nan
602
+
603
+ return StructuredQuery::Filter.new(unary_filter:
604
+ StructuredQuery::UnaryFilter.new(field: field, op: op))
605
+ end
606
+
607
+ value = Convert.raw_to_value value
608
+ StructuredQuery::Filter.new(field_filter:
609
+ StructuredQuery::FieldFilter.new(field: field, op: op,
610
+ value: value))
611
+ end
612
+
613
+ def default_filter
614
+ StructuredQuery::Filter.new(composite_filter:
615
+ StructuredQuery::CompositeFilter.new(op: :AND))
616
+ end
617
+
618
+ def order_direction direction
619
+ if direction.to_s.downcase.start_with? "a"
620
+ :ASCENDING
621
+ elsif direction.to_s.downcase.start_with? "d"
622
+ :DESCENDING
623
+ else
624
+ :DIRECTION_UNSPECIFIED
625
+ end
626
+ end
627
+
628
+ ##
629
+ # @private Raise an error unless an database available.
630
+ def ensure_client!
631
+ fail "Must have active connection to service" unless client
632
+ end
633
+
634
+ ##
635
+ # @private The Service object.
636
+ def service
637
+ ensure_client!
638
+
639
+ client.service
640
+ end
641
+
642
+ ##
643
+ # @private Raise an error unless an active connection to the service
644
+ # is available.
645
+ def ensure_service!
646
+ fail "Must have active connection to service" unless service
647
+ end
648
+ end
649
+ end
650
+ end
651
+ end