google-cloud-firestore 0.20.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 (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