google-cloud-firestore 2.3.0 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/CONTRIBUTING.md +4 -5
- data/LOGGING.md +1 -1
- data/lib/google/cloud/firestore/batch.rb +3 -4
- data/lib/google/cloud/firestore/client.rb +17 -23
- data/lib/google/cloud/firestore/collection_group.rb +136 -0
- data/lib/google/cloud/firestore/collection_reference.rb +9 -6
- data/lib/google/cloud/firestore/collection_reference_list.rb +3 -3
- data/lib/google/cloud/firestore/convert.rb +151 -173
- data/lib/google/cloud/firestore/document_reference.rb +9 -2
- data/lib/google/cloud/firestore/document_reference/list.rb +5 -6
- data/lib/google/cloud/firestore/field_path.rb +2 -2
- data/lib/google/cloud/firestore/field_value.rb +7 -2
- data/lib/google/cloud/firestore/query.rb +99 -29
- data/lib/google/cloud/firestore/query_partition.rb +80 -0
- data/lib/google/cloud/firestore/resource_path.rb +58 -0
- data/lib/google/cloud/firestore/service.rb +18 -1
- data/lib/google/cloud/firestore/transaction.rb +5 -5
- data/lib/google/cloud/firestore/version.rb +1 -1
- data/lib/google/cloud/firestore/watch/inventory.rb +9 -8
- data/lib/google/cloud/firestore/watch/listener.rb +3 -4
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3141b004673c1db562f162d9218ac680c0693e4d91a8eb48b7d17f444646e74a
|
4
|
+
data.tar.gz: 4ab2bb6f69da5f8ad2e53464ae3712647b3642d49a465dc5f75d1dbc923979f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd5dccbe067d6fea81556aae40b9fc9de2ca75fc389c919335217371d84578b68ad7c9854c84831bb368dd58982849c06f044cef0c864be586cdace0ea48994a
|
7
|
+
data.tar.gz: 7a7f0ee2a3259f2a65119b0b31dbf781d7336f7c530301f4c7f0a7fdf95fea8a7773caa4ed172c9ce8c5ff9f9fe80dab830f37ef1e6d204ae7d770fdf9c066c0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,40 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
### 2.6.0 / 2021-06-15
|
4
|
+
|
5
|
+
#### Features
|
6
|
+
|
7
|
+
* Add support for Query Partitions
|
8
|
+
* Add CollectionGroup
|
9
|
+
* Update Client#col_group to return CollectionGroup (subclass of Query)
|
10
|
+
* Add QueryPartition
|
11
|
+
* Add QueryPartition::List
|
12
|
+
* Add Query#to_json and Query.from_json
|
13
|
+
|
14
|
+
### 2.5.1 / 2021-04-19
|
15
|
+
|
16
|
+
#### Bug Fixes
|
17
|
+
|
18
|
+
* Add GRPC::Unknown to retryable errors in Watch::Listener
|
19
|
+
|
20
|
+
### 2.5.0 / 2021-03-10
|
21
|
+
|
22
|
+
#### Features
|
23
|
+
|
24
|
+
* Drop support for Ruby 2.4 and add support for Ruby 3.0
|
25
|
+
|
26
|
+
### 2.4.1 / 2021-01-06
|
27
|
+
|
28
|
+
#### Bug Fixes
|
29
|
+
|
30
|
+
* Replace usage of Write.transform with Write.update_transforms
|
31
|
+
|
32
|
+
### 2.4.0 / 2020-11-19
|
33
|
+
|
34
|
+
#### Features
|
35
|
+
|
36
|
+
* add support for != and NOT_IN queries
|
37
|
+
|
3
38
|
### 2.3.0 / 2020-09-30
|
4
39
|
|
5
40
|
#### Features
|
data/CONTRIBUTING.md
CHANGED
@@ -24,7 +24,7 @@ be able to accept your pull requests.
|
|
24
24
|
In order to use the google-cloud-firestore console and run the project's tests,
|
25
25
|
there is a small amount of setup:
|
26
26
|
|
27
|
-
1. Install Ruby. google-cloud-firestore requires Ruby 2.
|
27
|
+
1. Install Ruby. google-cloud-firestore requires Ruby 2.5+. You may choose to
|
28
28
|
manage your Ruby and gem installations with [RVM](https://rvm.io/),
|
29
29
|
[rbenv](https://github.com/rbenv/rbenv), or
|
30
30
|
[chruby](https://github.com/postmodern/chruby).
|
@@ -45,7 +45,7 @@ there is a small amount of setup:
|
|
45
45
|
|
46
46
|
```sh
|
47
47
|
$ cd google-cloud-firestore/
|
48
|
-
$ bundle
|
48
|
+
$ bundle install
|
49
49
|
```
|
50
50
|
|
51
51
|
## Console
|
@@ -119,15 +119,14 @@ If you alter an example's title, you may encounter breaking tests.
|
|
119
119
|
### Firestore Acceptance Tests
|
120
120
|
|
121
121
|
The Firestore acceptance tests interact with the live service API. Follow the
|
122
|
-
instructions in the {file:AUTHENTICATION.md Authentication
|
122
|
+
instructions in the {file:AUTHENTICATION.md Authentication Guide} for enabling
|
123
123
|
the Firestore API. Occasionally, some API features may not yet be generally
|
124
124
|
available, making it difficult for some contributors to successfully run the
|
125
125
|
entire acceptance test suite. However, please ensure that you do successfully
|
126
126
|
run acceptance tests for any code areas covered by your pull request.
|
127
127
|
|
128
128
|
To run the acceptance tests, first create and configure a project in the Google
|
129
|
-
Developers Console, as described in the {file:AUTHENTICATION.md Authentication
|
130
|
-
guide}. Be sure to download the JSON KEY file. Make note of the PROJECT_ID and
|
129
|
+
Developers Console, as described in the {file:AUTHENTICATION.md Authentication Guide}. Be sure to download the JSON KEY file. Make note of the PROJECT_ID and
|
131
130
|
the KEYFILE location on your system.
|
132
131
|
|
133
132
|
Before you can run the Firestore acceptance tests, you must first create indexes
|
data/LOGGING.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
To enable logging for this library, set the logger for the underlying
|
4
4
|
[gRPC](https://github.com/grpc/grpc/tree/master/src/ruby) library. The logger
|
5
5
|
that you set may be a Ruby stdlib
|
6
|
-
[`Logger`](https://ruby-doc.org/stdlib
|
6
|
+
[`Logger`](https://ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html) as
|
7
7
|
shown below, or a
|
8
8
|
[`Google::Cloud::Logging::Logger`](https://googleapis.dev/ruby/google-cloud-logging/latest)
|
9
9
|
that will write logs to [Stackdriver
|
@@ -119,7 +119,7 @@ module Google
|
|
119
119
|
|
120
120
|
doc_path = coalesce_doc_path_argument doc
|
121
121
|
|
122
|
-
@writes << Convert.
|
122
|
+
@writes << Convert.write_for_create(doc_path, data)
|
123
123
|
|
124
124
|
nil
|
125
125
|
end
|
@@ -218,7 +218,7 @@ module Google
|
|
218
218
|
|
219
219
|
doc_path = coalesce_doc_path_argument doc
|
220
220
|
|
221
|
-
@writes << Convert.
|
221
|
+
@writes << Convert.write_for_set(doc_path, data, merge: merge)
|
222
222
|
|
223
223
|
nil
|
224
224
|
end
|
@@ -322,8 +322,7 @@ module Google
|
|
322
322
|
|
323
323
|
doc_path = coalesce_doc_path_argument doc
|
324
324
|
|
325
|
-
@writes << Convert.
|
326
|
-
update_time: update_time)
|
325
|
+
@writes << Convert.write_for_update(doc_path, data, update_time: update_time)
|
327
326
|
|
328
327
|
nil
|
329
328
|
end
|
@@ -20,6 +20,7 @@ require "google/cloud/firestore/field_value"
|
|
20
20
|
require "google/cloud/firestore/collection_reference"
|
21
21
|
require "google/cloud/firestore/document_reference"
|
22
22
|
require "google/cloud/firestore/document_snapshot"
|
23
|
+
require "google/cloud/firestore/collection_group"
|
23
24
|
require "google/cloud/firestore/batch"
|
24
25
|
require "google/cloud/firestore/transaction"
|
25
26
|
|
@@ -100,11 +101,11 @@ module Google
|
|
100
101
|
# puts col.collection_id
|
101
102
|
# end
|
102
103
|
#
|
103
|
-
def cols
|
104
|
+
def cols &block
|
104
105
|
ensure_service!
|
105
106
|
grpc = service.list_collections "#{path}/documents"
|
106
107
|
cols_enum = CollectionReferenceList.from_grpc(grpc, self, "#{path}/documents").all
|
107
|
-
cols_enum.each
|
108
|
+
cols_enum.each(&block) if block_given?
|
108
109
|
cols_enum
|
109
110
|
end
|
110
111
|
alias collections cols
|
@@ -139,7 +140,7 @@ module Google
|
|
139
140
|
alias collection col
|
140
141
|
|
141
142
|
##
|
142
|
-
# Creates and returns a new
|
143
|
+
# Creates and returns a new collection group that includes all documents in the
|
143
144
|
# database that are contained in a collection or subcollection with the
|
144
145
|
# given collection_id.
|
145
146
|
#
|
@@ -147,7 +148,7 @@ module Google
|
|
147
148
|
# over. Every collection or subcollection with this ID as the last
|
148
149
|
# segment of its path will be included. Cannot contain a slash (`/`).
|
149
150
|
#
|
150
|
-
# @return [
|
151
|
+
# @return [CollectionGroup] The created collection group.
|
151
152
|
#
|
152
153
|
# @example
|
153
154
|
# require "google/cloud/firestore"
|
@@ -155,9 +156,9 @@ module Google
|
|
155
156
|
# firestore = Google::Cloud::Firestore.new
|
156
157
|
#
|
157
158
|
# # Get the cities collection group query
|
158
|
-
#
|
159
|
+
# col_group = firestore.col_group "cities"
|
159
160
|
#
|
160
|
-
#
|
161
|
+
# col_group.get do |city|
|
161
162
|
# puts "#{city.document_id} has #{city[:population]} residents."
|
162
163
|
# end
|
163
164
|
#
|
@@ -166,15 +167,8 @@ module Google
|
|
166
167
|
raise ArgumentError, "Invalid collection_id: '#{collection_id}', " \
|
167
168
|
"must not contain '/'."
|
168
169
|
end
|
169
|
-
|
170
|
-
|
171
|
-
Google::Cloud::Firestore::V1::StructuredQuery::CollectionSelector.new(
|
172
|
-
collection_id: collection_id, all_descendants: true
|
173
|
-
)
|
174
|
-
]
|
175
|
-
)
|
176
|
-
|
177
|
-
Query.start query, service.documents_path, self
|
170
|
+
|
171
|
+
CollectionGroup.from_collection_id service.documents_path, collection_id, self
|
178
172
|
end
|
179
173
|
alias collection_group col_group
|
180
174
|
|
@@ -256,7 +250,7 @@ module Google
|
|
256
250
|
ensure_service!
|
257
251
|
|
258
252
|
unless block_given?
|
259
|
-
return enum_for :get_all, docs, field_mask: field_mask
|
253
|
+
return enum_for :get_all, *docs, field_mask: field_mask
|
260
254
|
end
|
261
255
|
|
262
256
|
doc_paths = Array(docs).flatten.map do |doc_path|
|
@@ -634,9 +628,9 @@ module Google
|
|
634
628
|
commit_return = transaction.commit
|
635
629
|
# Conditional return value, depending on truthy commit_response
|
636
630
|
commit_response ? commit_return : transaction_return
|
637
|
-
rescue Google::Cloud::UnavailableError =>
|
631
|
+
rescue Google::Cloud::UnavailableError => e
|
638
632
|
# Re-raise if retried more than the max
|
639
|
-
raise
|
633
|
+
raise e if backoff[:current] > backoff[:max]
|
640
634
|
|
641
635
|
# Sleep with incremental backoff before restarting
|
642
636
|
sleep backoff[:delay]
|
@@ -649,18 +643,18 @@ module Google
|
|
649
643
|
transaction = Transaction.from_client \
|
650
644
|
self, previous_transaction: transaction.transaction_id
|
651
645
|
retry
|
652
|
-
rescue Google::Cloud::InvalidArgumentError =>
|
646
|
+
rescue Google::Cloud::InvalidArgumentError => e
|
653
647
|
# Return if a previous call was retried but ultimately succeeded
|
654
|
-
return nil if backoff[:current]
|
648
|
+
return nil if backoff[:current].positive?
|
655
649
|
|
656
650
|
# Re-raise error.
|
657
|
-
raise
|
658
|
-
rescue StandardError =>
|
651
|
+
raise e
|
652
|
+
rescue StandardError => e
|
659
653
|
# Rollback transaction when handling unexpected error
|
660
654
|
transaction.rollback rescue nil
|
661
655
|
|
662
656
|
# Re-raise error.
|
663
|
-
raise
|
657
|
+
raise e
|
664
658
|
end
|
665
659
|
end
|
666
660
|
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# Copyright 2021 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/v1"
|
17
|
+
require "google/cloud/firestore/query"
|
18
|
+
require "google/cloud/firestore/query_partition"
|
19
|
+
|
20
|
+
module Google
|
21
|
+
module Cloud
|
22
|
+
module Firestore
|
23
|
+
##
|
24
|
+
# # CollectionGroup
|
25
|
+
#
|
26
|
+
# A collection group object is used for adding documents, getting
|
27
|
+
# document references, and querying for documents, including with partitions.
|
28
|
+
#
|
29
|
+
# See {Client#col_group} and {Query}.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# require "google/cloud/firestore"
|
33
|
+
#
|
34
|
+
# firestore = Google::Cloud::Firestore.new
|
35
|
+
#
|
36
|
+
# # Get a collection group
|
37
|
+
# col_group = firestore.col_group "cities"
|
38
|
+
#
|
39
|
+
# # Get and print all city documents
|
40
|
+
# col_group.get do |city|
|
41
|
+
# puts "#{city.document_id} has #{city[:population]} residents."
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
class CollectionGroup < Query
|
45
|
+
##
|
46
|
+
# Partitions a query by returning partition cursors that can be used to run the query in parallel. The returned
|
47
|
+
# partition cursors are split points that can be used as starting/end points for the query results.
|
48
|
+
#
|
49
|
+
# @param [Integer] partition_count The desired maximum number of partition points. The number must be strictly
|
50
|
+
# positive. The actual number of partitions returned may be fewer.
|
51
|
+
#
|
52
|
+
# @return [Array<QueryPartition>] An ordered array of query partitions.
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# require "google/cloud/firestore"
|
56
|
+
#
|
57
|
+
# firestore = Google::Cloud::Firestore.new
|
58
|
+
#
|
59
|
+
# col_group = firestore.col_group "cities"
|
60
|
+
#
|
61
|
+
# partitions = col_group.partitions 3
|
62
|
+
#
|
63
|
+
# queries = partitions.map(&:to_query)
|
64
|
+
#
|
65
|
+
def partitions partition_count
|
66
|
+
ensure_service!
|
67
|
+
|
68
|
+
raise ArgumentError, "partition_count must be > 0" unless partition_count.positive?
|
69
|
+
|
70
|
+
# Partition queries require explicit ordering by __name__.
|
71
|
+
query_with_default_order = order "__name__"
|
72
|
+
# Since we are always returning an extra partition (with en empty endBefore cursor), we reduce the desired
|
73
|
+
# partition count by one.
|
74
|
+
partition_count -= 1
|
75
|
+
|
76
|
+
grpc_partitions = if partition_count.positive?
|
77
|
+
# Retrieve all pages, since cursor order is not guaranteed and they must be sorted.
|
78
|
+
list_all partition_count, query_with_default_order
|
79
|
+
else
|
80
|
+
[] # Ensure that a single, empty QueryPartition is returned.
|
81
|
+
end
|
82
|
+
cursor_values = grpc_partitions.map do |cursor|
|
83
|
+
# Convert each cursor to a (single-element) array of Google::Cloud::Firestore::DocumentReference.
|
84
|
+
cursor.values.map do |value|
|
85
|
+
Convert.value_to_raw value, client
|
86
|
+
end
|
87
|
+
end
|
88
|
+
# Sort the values of the returned cursor, which right now should only contain a single reference value (which
|
89
|
+
# needs to be sorted one component at a time).
|
90
|
+
cursor_values.sort! do |a, b|
|
91
|
+
a.first <=> b.first
|
92
|
+
end
|
93
|
+
|
94
|
+
start_at = nil
|
95
|
+
results = cursor_values.map do |end_before|
|
96
|
+
partition = QueryPartition.new query_with_default_order, start_at, end_before
|
97
|
+
start_at = end_before
|
98
|
+
partition
|
99
|
+
end
|
100
|
+
# Always add a final QueryPartition with an empty end_before value.
|
101
|
+
results << QueryPartition.new(query_with_default_order, start_at, nil)
|
102
|
+
results
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# @private New Collection group object from a path.
|
107
|
+
def self.from_collection_id parent_path, collection_id, client
|
108
|
+
query = Google::Cloud::Firestore::V1::StructuredQuery.new(
|
109
|
+
from: [
|
110
|
+
Google::Cloud::Firestore::V1::StructuredQuery::CollectionSelector.new(
|
111
|
+
collection_id: collection_id,
|
112
|
+
all_descendants: true
|
113
|
+
)
|
114
|
+
]
|
115
|
+
)
|
116
|
+
CollectionGroup.new query, parent_path, client
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
|
121
|
+
def list_all partition_count, query_with_default_order
|
122
|
+
grpc_partitions = []
|
123
|
+
token = nil
|
124
|
+
loop do
|
125
|
+
grpc = service.partition_query parent_path, query_with_default_order.query, partition_count, token: token
|
126
|
+
grpc_partitions += Array(grpc.partitions)
|
127
|
+
token = grpc.next_page_token
|
128
|
+
token = nil if token == ""
|
129
|
+
break unless token
|
130
|
+
end
|
131
|
+
grpc_partitions
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -14,9 +14,9 @@
|
|
14
14
|
|
15
15
|
|
16
16
|
require "google/cloud/firestore/v1"
|
17
|
+
require "google/cloud/firestore/query"
|
17
18
|
require "google/cloud/firestore/document_reference"
|
18
19
|
require "google/cloud/firestore/document_snapshot"
|
19
|
-
require "google/cloud/firestore/query"
|
20
20
|
require "google/cloud/firestore/generate"
|
21
21
|
require "google/cloud/firestore/collection_reference_list"
|
22
22
|
|
@@ -47,6 +47,13 @@ module Google
|
|
47
47
|
# @private The firestore client object.
|
48
48
|
attr_accessor :client
|
49
49
|
|
50
|
+
##
|
51
|
+
# @private Creates a new CollectionReference.
|
52
|
+
def initialize query, path, client
|
53
|
+
super query, nil, client # Pass nil parent_path arg since this class implements #parent_path
|
54
|
+
@path = path
|
55
|
+
end
|
56
|
+
|
50
57
|
##
|
51
58
|
# The collection identifier for the collection resource.
|
52
59
|
#
|
@@ -257,11 +264,7 @@ module Google
|
|
257
264
|
]
|
258
265
|
)
|
259
266
|
|
260
|
-
new
|
261
|
-
c.client = client
|
262
|
-
c.instance_variable_set :@path, path
|
263
|
-
c.instance_variable_set :@query, query
|
264
|
-
end
|
267
|
+
CollectionReference.new query, path, client
|
265
268
|
end
|
266
269
|
|
267
270
|
protected
|
@@ -90,17 +90,17 @@ module Google
|
|
90
90
|
# collection_reference.collection_id
|
91
91
|
# end
|
92
92
|
#
|
93
|
-
def all request_limit: nil
|
93
|
+
def all request_limit: nil, &block
|
94
94
|
request_limit = request_limit.to_i if request_limit
|
95
95
|
unless block_given?
|
96
96
|
return enum_for :all, request_limit: request_limit
|
97
97
|
end
|
98
98
|
results = self
|
99
99
|
loop do
|
100
|
-
results.each
|
100
|
+
results.each(&block)
|
101
101
|
if request_limit
|
102
102
|
request_limit -= 1
|
103
|
-
break if request_limit
|
103
|
+
break if request_limit.negative?
|
104
104
|
end
|
105
105
|
break unless results.next?
|
106
106
|
results = results.next
|
@@ -24,7 +24,12 @@ module Google
|
|
24
24
|
##
|
25
25
|
# @private Helper module for converting Protobuf values.
|
26
26
|
module Convert
|
27
|
-
# rubocop:disable
|
27
|
+
# rubocop:disable Metrics/AbcSize
|
28
|
+
# rubocop:disable Metrics/BlockLength
|
29
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
30
|
+
# rubocop:disable Metrics/MethodLength
|
31
|
+
# rubocop:disable Metrics/ModuleLength
|
32
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
28
33
|
module ClassMethods
|
29
34
|
def time_to_timestamp time
|
30
35
|
return nil if time.nil?
|
@@ -32,9 +37,10 @@ module Google
|
|
32
37
|
# Force the object to be a Time object.
|
33
38
|
time = time.to_time
|
34
39
|
|
35
|
-
Google::Protobuf::Timestamp.new
|
40
|
+
Google::Protobuf::Timestamp.new(
|
36
41
|
seconds: time.to_i,
|
37
|
-
nanos:
|
42
|
+
nanos: time.nsec
|
43
|
+
)
|
38
44
|
end
|
39
45
|
|
40
46
|
def timestamp_to_time timestamp
|
@@ -100,9 +106,10 @@ module Google
|
|
100
106
|
elsif Google::Cloud::Firestore::DocumentReference === obj
|
101
107
|
Google::Cloud::Firestore::V1::Value.new reference_value: obj.path
|
102
108
|
elsif Array === obj
|
103
|
-
values = obj.map { |o| raw_to_value
|
104
|
-
Google::Cloud::Firestore::V1::Value.new(
|
105
|
-
Google::Cloud::Firestore::V1::ArrayValue.new(values: values)
|
109
|
+
values = obj.map { |o| raw_to_value o }
|
110
|
+
Google::Cloud::Firestore::V1::Value.new(
|
111
|
+
array_value: Google::Cloud::Firestore::V1::ArrayValue.new(values: values)
|
112
|
+
)
|
106
113
|
elsif Hash === obj
|
107
114
|
# keys have been changed to strings before the hash gets here
|
108
115
|
geo_pairs = hash_is_geo_point? obj
|
@@ -112,8 +119,9 @@ module Google
|
|
112
119
|
)
|
113
120
|
else
|
114
121
|
fields = hash_to_fields obj
|
115
|
-
Google::Cloud::Firestore::V1::Value.new(
|
116
|
-
Google::Cloud::Firestore::V1::MapValue.new(fields: fields)
|
122
|
+
Google::Cloud::Firestore::V1::Value.new(
|
123
|
+
map_value: Google::Cloud::Firestore::V1::MapValue.new(fields: fields)
|
124
|
+
)
|
117
125
|
end
|
118
126
|
elsif obj.respond_to?(:read) && obj.respond_to?(:rewind)
|
119
127
|
obj.rewind
|
@@ -129,9 +137,7 @@ module Google
|
|
129
137
|
return false unless hash.keys.count == 2
|
130
138
|
|
131
139
|
pairs = hash.map { |k, v| [String(k), v] }.sort
|
132
|
-
if pairs.map(&:first) == ["latitude", "longitude"]
|
133
|
-
pairs
|
134
|
-
end
|
140
|
+
pairs if pairs.map(&:first) == ["latitude", "longitude"]
|
135
141
|
end
|
136
142
|
|
137
143
|
def hash_to_geo_point hash, pairs = nil
|
@@ -140,47 +146,39 @@ module Google
|
|
140
146
|
raise ArgumentError, "value is not a geo point" unless pairs
|
141
147
|
|
142
148
|
Google::Type::LatLng.new(
|
143
|
-
latitude:
|
144
|
-
longitude: pairs.last.last
|
149
|
+
latitude: pairs.first.last,
|
150
|
+
longitude: pairs.last.last
|
145
151
|
)
|
146
152
|
end
|
147
153
|
|
148
|
-
def
|
149
|
-
|
150
|
-
|
151
|
-
if is_field_value_nested data, :delete
|
154
|
+
def write_for_create doc_path, data
|
155
|
+
if field_value_nested? data, :delete
|
152
156
|
raise ArgumentError, "DELETE not allowed on create"
|
153
157
|
end
|
154
158
|
raise ArgumentError, "data is required" unless data.is_a? Hash
|
155
159
|
|
156
160
|
data, field_paths_and_values = remove_field_value_from data
|
157
161
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
if field_paths_and_values.any?
|
170
|
-
transform_write = transform_write doc_path, field_paths_and_values
|
171
|
-
|
172
|
-
if data.empty?
|
173
|
-
transform_write.current_document = \
|
174
|
-
Google::Cloud::Firestore::V1::Precondition.new(exists: false)
|
175
|
-
end
|
176
|
-
|
177
|
-
writes << transform_write
|
178
|
-
end
|
162
|
+
doc = Google::Cloud::Firestore::V1::Document.new(
|
163
|
+
name: doc_path,
|
164
|
+
fields: hash_to_fields(data)
|
165
|
+
)
|
166
|
+
precondition = Google::Cloud::Firestore::V1::Precondition.new exists: false
|
167
|
+
Google::Cloud::Firestore::V1::Write.new(
|
168
|
+
update: doc,
|
169
|
+
current_document: precondition,
|
170
|
+
update_transforms: field_transforms(field_paths_and_values)
|
171
|
+
)
|
172
|
+
end
|
179
173
|
|
180
|
-
|
174
|
+
def field_transforms paths
|
175
|
+
return nil if paths.empty?
|
176
|
+
paths.map do |field_path, field_value|
|
177
|
+
to_field_transform field_path, field_value
|
178
|
+
end.to_a
|
181
179
|
end
|
182
180
|
|
183
|
-
def
|
181
|
+
def write_for_set doc_path, data, merge: nil
|
184
182
|
raise ArgumentError, "data is required" unless data.is_a? Hash
|
185
183
|
|
186
184
|
if merge
|
@@ -195,11 +193,9 @@ module Google
|
|
195
193
|
end
|
196
194
|
allow_empty = false
|
197
195
|
end
|
198
|
-
return
|
196
|
+
return write_for_set_merge doc_path, data, field_paths, allow_empty
|
199
197
|
end
|
200
198
|
|
201
|
-
writes = []
|
202
|
-
|
203
199
|
data, delete_paths = remove_field_value_from data, :delete
|
204
200
|
if delete_paths.any?
|
205
201
|
raise ArgumentError, "DELETE not allowed on set"
|
@@ -207,30 +203,25 @@ module Google
|
|
207
203
|
|
208
204
|
data, field_paths_and_values = remove_field_value_from data
|
209
205
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
206
|
+
doc = Google::Cloud::Firestore::V1::Document.new(
|
207
|
+
name: doc_path,
|
208
|
+
fields: hash_to_fields(data)
|
209
|
+
)
|
210
|
+
Google::Cloud::Firestore::V1::Write.new(
|
211
|
+
update: doc,
|
212
|
+
update_transforms: field_transforms(field_paths_and_values)
|
214
213
|
)
|
215
|
-
|
216
|
-
if field_paths_and_values.any?
|
217
|
-
writes << transform_write(doc_path, field_paths_and_values)
|
218
|
-
end
|
219
|
-
|
220
|
-
writes
|
221
214
|
end
|
222
215
|
|
223
|
-
def
|
216
|
+
def write_for_set_merge doc_path, data, field_paths, allow_empty
|
224
217
|
raise ArgumentError, "data is required" unless data.is_a? Hash
|
225
218
|
|
226
219
|
validate_field_paths! field_paths
|
227
220
|
|
228
|
-
writes = []
|
229
|
-
|
230
221
|
# Ensure provided field paths are valid.
|
231
222
|
all_valid = identify_leaf_nodes data
|
232
223
|
all_valid_check = field_paths.map do |verify_path|
|
233
|
-
if all_valid.include?
|
224
|
+
if all_valid.include? verify_path
|
234
225
|
true
|
235
226
|
else
|
236
227
|
found_in_all_valid = all_valid.select do |fp|
|
@@ -271,32 +262,25 @@ module Google
|
|
271
262
|
# Restore delete paths
|
272
263
|
field_paths += delete_field_paths_and_values.keys
|
273
264
|
|
274
|
-
if data.empty? && !allow_empty
|
275
|
-
|
276
|
-
raise ArgumentError, "data required for set with merge"
|
277
|
-
end
|
265
|
+
if data.empty? && !allow_empty && field_paths_and_values.empty? && delete_field_paths_and_values.empty?
|
266
|
+
raise ArgumentError, "data required for set with merge"
|
278
267
|
end
|
279
268
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
end
|
293
|
-
|
294
|
-
writes
|
269
|
+
doc = Google::Cloud::Firestore::V1::Document.new(
|
270
|
+
name: doc_path,
|
271
|
+
fields: hash_to_fields(data)
|
272
|
+
)
|
273
|
+
doc_mask = Google::Cloud::Firestore::V1::DocumentMask.new(
|
274
|
+
field_paths: field_paths.map(&:formatted_string).sort
|
275
|
+
)
|
276
|
+
Google::Cloud::Firestore::V1::Write.new(
|
277
|
+
update: doc,
|
278
|
+
update_mask: doc_mask,
|
279
|
+
update_transforms: field_transforms(field_paths_and_values)
|
280
|
+
)
|
295
281
|
end
|
296
282
|
|
297
|
-
def
|
298
|
-
writes = []
|
299
|
-
|
283
|
+
def write_for_update doc_path, data, update_time: nil
|
300
284
|
raise ArgumentError, "data is required" unless data.is_a? Hash
|
301
285
|
|
302
286
|
# Convert data to use FieldPath
|
@@ -308,11 +292,11 @@ module Google
|
|
308
292
|
# Duplicate field paths check
|
309
293
|
validate_field_paths! new_data_pairs.map(&:first)
|
310
294
|
|
311
|
-
delete_paths, new_data_pairs = new_data_pairs.partition do |
|
295
|
+
delete_paths, new_data_pairs = new_data_pairs.partition do |_field_path, value|
|
312
296
|
value.is_a?(FieldValue) && value.type == :delete
|
313
297
|
end
|
314
298
|
|
315
|
-
root_field_paths_and_values, new_data_pairs = new_data_pairs.partition do |
|
299
|
+
root_field_paths_and_values, new_data_pairs = new_data_pairs.partition do |_field_path, value|
|
316
300
|
value.is_a? FieldValue
|
317
301
|
end
|
318
302
|
|
@@ -325,7 +309,7 @@ module Google
|
|
325
309
|
data, nested_deletes = remove_field_value_from data, :delete
|
326
310
|
raise ArgumentError, "DELETE cannot be nested" if nested_deletes.any?
|
327
311
|
|
328
|
-
data, nested_field_paths_and_values
|
312
|
+
data, nested_field_paths_and_values = remove_field_value_from data
|
329
313
|
|
330
314
|
field_paths_and_values = root_field_paths_and_values.merge nested_field_paths_and_values
|
331
315
|
|
@@ -338,34 +322,31 @@ module Google
|
|
338
322
|
raise ArgumentError, "data is required"
|
339
323
|
end
|
340
324
|
|
325
|
+
write = Google::Cloud::Firestore::V1::Write.new(
|
326
|
+
update: Google::Cloud::Firestore::V1::Document.new(name: doc_path),
|
327
|
+
update_mask: Google::Cloud::Firestore::V1::DocumentMask.new,
|
328
|
+
current_document: Google::Cloud::Firestore::V1::Precondition.new(exists: true)
|
329
|
+
)
|
330
|
+
|
341
331
|
if data.any? || delete_paths.any?
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
current_document: Google::Cloud::Firestore::V1::Precondition.new(
|
349
|
-
exists: true)
|
350
|
-
)
|
332
|
+
htf = hash_to_fields data
|
333
|
+
htf.each_pair do |k, v|
|
334
|
+
write.update.fields[k] = v
|
335
|
+
end
|
336
|
+
write.update_mask.field_paths += field_paths.map(&:formatted_string).sort
|
337
|
+
|
351
338
|
if update_time
|
352
|
-
write.current_document =
|
353
|
-
|
354
|
-
|
339
|
+
write.current_document = Google::Cloud::Firestore::V1::Precondition.new(
|
340
|
+
update_time: time_to_timestamp(update_time)
|
341
|
+
)
|
355
342
|
end
|
356
|
-
writes << write
|
357
343
|
end
|
358
344
|
|
359
345
|
if field_paths_and_values.any?
|
360
|
-
|
361
|
-
if data.empty?
|
362
|
-
transform_write.current_document = \
|
363
|
-
Google::Cloud::Firestore::V1::Precondition.new(exists: true)
|
364
|
-
end
|
365
|
-
writes << transform_write
|
346
|
+
write.update_transforms += field_transforms field_paths_and_values
|
366
347
|
end
|
367
348
|
|
368
|
-
|
349
|
+
write
|
369
350
|
end
|
370
351
|
|
371
352
|
def write_for_delete doc_path, exists: nil, update_time: nil
|
@@ -387,13 +368,20 @@ module Google
|
|
387
368
|
write
|
388
369
|
end
|
389
370
|
|
390
|
-
def
|
371
|
+
def field_value_nested? obj, field_value_type = nil
|
391
372
|
return obj if obj.is_a?(FieldValue) && (field_value_type.nil? || obj.type == field_value_type)
|
392
373
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
374
|
+
case obj
|
375
|
+
when Array
|
376
|
+
obj.each do |o|
|
377
|
+
val = field_value_nested? o, field_value_type
|
378
|
+
return val if val
|
379
|
+
end
|
380
|
+
when Hash
|
381
|
+
obj.each do |_k, v|
|
382
|
+
val = field_value_nested? v, field_value_type
|
383
|
+
return val if val
|
384
|
+
end
|
397
385
|
end
|
398
386
|
nil
|
399
387
|
end
|
@@ -406,35 +394,33 @@ module Google
|
|
406
394
|
if value.is_a?(FieldValue) && (field_value_type.nil? || value.type == field_value_type)
|
407
395
|
paths << [FieldPath.new(*key), value]
|
408
396
|
nil # will be removed by calling compact
|
409
|
-
|
410
|
-
if value.
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
end
|
420
|
-
if nested_hash.empty?
|
421
|
-
nil # will be removed by calling compact
|
422
|
-
else
|
423
|
-
[String(key), nested_hash]
|
397
|
+
elsif value.is_a? Hash
|
398
|
+
if value.empty?
|
399
|
+
[String(key), value]
|
400
|
+
else
|
401
|
+
nested_hash, nested_paths = remove_field_value_from value, field_value_type
|
402
|
+
if nested_paths.any?
|
403
|
+
nested_paths.each do |nested_field_path, nested_field_value|
|
404
|
+
updated_field_paths = ([key] + nested_field_path.fields).flatten
|
405
|
+
updated_field_path = FieldPath.new(*updated_field_paths)
|
406
|
+
paths << [updated_field_path, nested_field_value]
|
424
407
|
end
|
408
|
+
end
|
409
|
+
if nested_hash.empty?
|
410
|
+
nil # will be removed by calling compact
|
425
411
|
else
|
426
|
-
[String(key),
|
412
|
+
[String(key), nested_hash]
|
427
413
|
end
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
414
|
+
end
|
415
|
+
else
|
416
|
+
if value.is_a? Array
|
417
|
+
nested_field_value = field_value_nested? value, field_value_type
|
418
|
+
if nested_field_value
|
419
|
+
raise ArgumentError, "cannot nest #{nested_field_value.type} under arrays"
|
434
420
|
end
|
435
|
-
|
436
|
-
[String(key), value]
|
437
421
|
end
|
422
|
+
|
423
|
+
[String(key), value]
|
438
424
|
end
|
439
425
|
end
|
440
426
|
|
@@ -449,14 +435,14 @@ module Google
|
|
449
435
|
if value.is_a? Hash
|
450
436
|
nested_paths = identify_leaf_nodes value
|
451
437
|
nested_paths.each do |nested_path|
|
452
|
-
paths << (
|
438
|
+
paths << ([key] + nested_path.fields).flatten
|
453
439
|
end
|
454
440
|
else
|
455
441
|
paths << [key]
|
456
442
|
end
|
457
443
|
end
|
458
444
|
|
459
|
-
paths.map { |path| FieldPath.new
|
445
|
+
paths.map { |path| FieldPath.new(*path) }
|
460
446
|
end
|
461
447
|
|
462
448
|
def identify_all_file_paths hash
|
@@ -465,15 +451,14 @@ module Google
|
|
465
451
|
hash.map do |key, value|
|
466
452
|
paths << [key]
|
467
453
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
end
|
454
|
+
next unless value.is_a? Hash
|
455
|
+
nested_paths = identify_all_file_paths value
|
456
|
+
nested_paths.each do |nested_path|
|
457
|
+
paths << ([key] + nested_path.fields).flatten
|
473
458
|
end
|
474
459
|
end
|
475
460
|
|
476
|
-
paths.map { |path| FieldPath.new
|
461
|
+
paths.map { |path| FieldPath.new(*path) }
|
477
462
|
end
|
478
463
|
|
479
464
|
def select_by_field_paths hash, field_paths
|
@@ -533,19 +518,19 @@ module Google
|
|
533
518
|
right_hash.each_pair do |key, right_value|
|
534
519
|
left_value = left_hash[key]
|
535
520
|
|
536
|
-
if left_value.is_a?(Hash) && right_value.is_a?(Hash)
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
521
|
+
left_hash[key] = if left_value.is_a?(Hash) && right_value.is_a?(Hash)
|
522
|
+
deep_merge_hashes left_value, right_value
|
523
|
+
else
|
524
|
+
right_value
|
525
|
+
end
|
541
526
|
end
|
542
527
|
|
543
528
|
left_hash
|
544
529
|
end
|
545
530
|
|
546
|
-
START_FIELD_PATH_CHARS = /\A[a-zA-Z_]
|
547
|
-
INVALID_FIELD_PATH_CHARS =
|
548
|
-
ESCAPED_FIELD_PATH = /\A
|
531
|
+
START_FIELD_PATH_CHARS = /\A[a-zA-Z_]/.freeze
|
532
|
+
INVALID_FIELD_PATH_CHARS = %r{[~*/\[\]]}.freeze
|
533
|
+
ESCAPED_FIELD_PATH = /\A`(.*)`\z/.freeze
|
549
534
|
|
550
535
|
def build_hash_from_field_paths_and_values pairs
|
551
536
|
pairs.each do |field_path, _value|
|
@@ -579,59 +564,52 @@ module Google
|
|
579
564
|
"`#{str}`"
|
580
565
|
end
|
581
566
|
|
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
|
585
|
-
end
|
586
|
-
|
587
|
-
Google::Cloud::Firestore::V1::Write.new(
|
588
|
-
transform: Google::Cloud::Firestore::V1::DocumentTransform.new(
|
589
|
-
document: doc_path,
|
590
|
-
field_transforms: field_transforms
|
591
|
-
)
|
592
|
-
)
|
593
|
-
end
|
594
|
-
|
595
567
|
def to_field_transform field_path, field_value
|
596
|
-
|
568
|
+
case field_value.type
|
569
|
+
when :server_time
|
597
570
|
Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
|
598
|
-
field_path:
|
571
|
+
field_path: field_path.formatted_string,
|
599
572
|
set_to_server_value: :REQUEST_TIME
|
600
573
|
)
|
601
|
-
|
574
|
+
when :array_union
|
602
575
|
Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
|
603
|
-
field_path:
|
576
|
+
field_path: field_path.formatted_string,
|
604
577
|
append_missing_elements: raw_to_value(Array(field_value.value)).array_value
|
605
578
|
)
|
606
|
-
|
579
|
+
when :array_delete
|
607
580
|
Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
|
608
|
-
field_path:
|
581
|
+
field_path: field_path.formatted_string,
|
609
582
|
remove_all_from_array: raw_to_value(Array(field_value.value)).array_value
|
610
583
|
)
|
611
|
-
|
584
|
+
when :increment
|
612
585
|
Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
|
613
586
|
field_path: field_path.formatted_string,
|
614
|
-
increment:
|
587
|
+
increment: raw_to_value(field_value.value)
|
615
588
|
)
|
616
|
-
|
589
|
+
when :maximum
|
617
590
|
Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
|
618
591
|
field_path: field_path.formatted_string,
|
619
|
-
maximum:
|
592
|
+
maximum: raw_to_value(field_value.value)
|
620
593
|
)
|
621
|
-
|
594
|
+
when :minimum
|
622
595
|
Google::Cloud::Firestore::V1::DocumentTransform::FieldTransform.new(
|
623
596
|
field_path: field_path.formatted_string,
|
624
|
-
minimum:
|
597
|
+
minimum: raw_to_value(field_value.value)
|
625
598
|
)
|
626
599
|
else
|
627
600
|
raise ArgumentError, "unknown field transform #{field_value.type}"
|
628
601
|
end
|
629
602
|
end
|
630
603
|
end
|
631
|
-
# rubocop:enable all
|
632
604
|
|
633
605
|
extend ClassMethods
|
634
606
|
end
|
607
|
+
# rubocop:enable Metrics/AbcSize
|
608
|
+
# rubocop:enable Metrics/BlockLength
|
609
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
610
|
+
# rubocop:enable Metrics/MethodLength
|
611
|
+
# rubocop:enable Metrics/ModuleLength
|
612
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
635
613
|
end
|
636
614
|
end
|
637
615
|
end
|