google-cloud-firestore 2.3.0 → 2.6.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.
- 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
@@ -18,6 +18,7 @@ require "google/cloud/firestore/document_snapshot"
|
|
18
18
|
require "google/cloud/firestore/collection_reference"
|
19
19
|
require "google/cloud/firestore/document_listener"
|
20
20
|
require "google/cloud/firestore/document_reference/list"
|
21
|
+
require "google/cloud/firestore/resource_path"
|
21
22
|
|
22
23
|
module Google
|
23
24
|
module Cloud
|
@@ -90,11 +91,11 @@ module Google
|
|
90
91
|
# puts col.collection_id
|
91
92
|
# end
|
92
93
|
#
|
93
|
-
def cols
|
94
|
+
def cols &block
|
94
95
|
ensure_service!
|
95
96
|
grpc = service.list_collections path
|
96
97
|
cols_enum = CollectionReferenceList.from_grpc(grpc, client, path).all
|
97
|
-
cols_enum.each
|
98
|
+
cols_enum.each(&block) if block_given?
|
98
99
|
cols_enum
|
99
100
|
end
|
100
101
|
alias collections cols
|
@@ -459,6 +460,12 @@ module Google
|
|
459
460
|
|
460
461
|
# @!endgroup
|
461
462
|
|
463
|
+
# @private
|
464
|
+
def <=> other
|
465
|
+
return nil unless other.is_a? self.class
|
466
|
+
ResourcePath.from_path(path) <=> ResourcePath.from_path(other.path)
|
467
|
+
end
|
468
|
+
|
462
469
|
##
|
463
470
|
# @private New DocumentReference object from a path.
|
464
471
|
def self.from_path path, client
|
@@ -86,8 +86,7 @@ module Google
|
|
86
86
|
def next
|
87
87
|
return nil unless next?
|
88
88
|
ensure_client!
|
89
|
-
|
90
|
-
grpc = @client.service.list_documents @parent, @collection_id, options
|
89
|
+
grpc = @client.service.list_documents @parent, @collection_id, token: token, max: @max
|
91
90
|
self.class.from_grpc grpc, @client, @parent, @collection_id, @max
|
92
91
|
end
|
93
92
|
|
@@ -110,7 +109,7 @@ module Google
|
|
110
109
|
#
|
111
110
|
# @return [Enumerator]
|
112
111
|
#
|
113
|
-
# @example Iterating each document reference by passing a block:
|
112
|
+
# @example Iterating each document reference by passing a block or proc:
|
114
113
|
# require "google/cloud/firestore"
|
115
114
|
#
|
116
115
|
# firestore = Google::Cloud::Firestore.new
|
@@ -143,17 +142,17 @@ module Google
|
|
143
142
|
# puts doc_ref.document_id
|
144
143
|
# end
|
145
144
|
#
|
146
|
-
def all request_limit: nil
|
145
|
+
def all request_limit: nil, &block
|
147
146
|
request_limit = request_limit.to_i if request_limit
|
148
147
|
unless block_given?
|
149
148
|
return enum_for :all, request_limit: request_limit
|
150
149
|
end
|
151
150
|
results = self
|
152
151
|
loop do
|
153
|
-
results.each
|
152
|
+
results.each(&block)
|
154
153
|
if request_limit
|
155
154
|
request_limit -= 1
|
156
|
-
break if request_limit
|
155
|
+
break if request_limit.negative?
|
157
156
|
end
|
158
157
|
break unless results.next?
|
159
158
|
results = results.next
|
@@ -210,14 +210,14 @@ module Google
|
|
210
210
|
protected
|
211
211
|
|
212
212
|
START_FIELD_PATH_CHARS = /\A[a-zA-Z_]/.freeze
|
213
|
-
INVALID_FIELD_PATH_CHARS = %r{[
|
213
|
+
INVALID_FIELD_PATH_CHARS = %r{[~*/\[\]]}.freeze
|
214
214
|
|
215
215
|
def escape_field_for_path field
|
216
216
|
field = String field
|
217
217
|
|
218
218
|
if INVALID_FIELD_PATH_CHARS.match(field) ||
|
219
219
|
field["."] || field["`"] || field["\\"]
|
220
|
-
escaped_field = field.gsub(/[
|
220
|
+
escaped_field = field.gsub(/[`\\]/, "`" => "\\\`", "\\" => "\\\\")
|
221
221
|
return "`#{escaped_field}`"
|
222
222
|
end
|
223
223
|
|
@@ -27,9 +27,14 @@ module Google
|
|
27
27
|
#
|
28
28
|
# firestore = Google::Cloud::Firestore.new
|
29
29
|
#
|
30
|
-
#
|
30
|
+
# # Get a document reference
|
31
|
+
# nyc_ref = firestore.doc "cities/NYC"
|
31
32
|
#
|
32
|
-
# #
|
33
|
+
# # Set the population to increment by 1.
|
34
|
+
# increment_value = Google::Cloud::Firestore::FieldValue.increment 1
|
35
|
+
#
|
36
|
+
# nyc_ref.update({ name: "New York City",
|
37
|
+
# population: increment_value })
|
33
38
|
#
|
34
39
|
class FieldValue
|
35
40
|
##
|
@@ -17,6 +17,7 @@ require "google/cloud/firestore/v1"
|
|
17
17
|
require "google/cloud/firestore/document_snapshot"
|
18
18
|
require "google/cloud/firestore/query_listener"
|
19
19
|
require "google/cloud/firestore/convert"
|
20
|
+
require "json"
|
20
21
|
|
21
22
|
module Google
|
22
23
|
module Cloud
|
@@ -74,6 +75,16 @@ module Google
|
|
74
75
|
# @private The firestore client object.
|
75
76
|
attr_accessor :client
|
76
77
|
|
78
|
+
##
|
79
|
+
# @private Creates a new Query.
|
80
|
+
def initialize query, parent_path, client, limit_type: nil
|
81
|
+
query ||= StructuredQuery.new
|
82
|
+
@query = query
|
83
|
+
@parent_path = parent_path
|
84
|
+
@limit_type = limit_type
|
85
|
+
@client = client
|
86
|
+
end
|
87
|
+
|
77
88
|
##
|
78
89
|
# Restricts documents matching the query to return only data for the
|
79
90
|
# provided fields.
|
@@ -216,6 +227,9 @@ module Google
|
|
216
227
|
# * greater than: `>`, `gt`
|
217
228
|
# * greater than or equal: `>=`, `gte`
|
218
229
|
# * equal: `=`, `==`, `eq`, `eql`, `is`
|
230
|
+
# * not equal: `!=`
|
231
|
+
# * in: `in`
|
232
|
+
# * not in: `not-in`, `not_in`
|
219
233
|
# * array contains: `array-contains`, `array_contains`
|
220
234
|
# @param [Object] value A value the field is compared to.
|
221
235
|
#
|
@@ -959,16 +973,71 @@ module Google
|
|
959
973
|
end
|
960
974
|
alias on_snapshot listen
|
961
975
|
|
976
|
+
##
|
977
|
+
# Serializes the instance to a JSON text string. See also {Query.from_json}.
|
978
|
+
#
|
979
|
+
# @return [String] A JSON text string.
|
980
|
+
#
|
981
|
+
# @example
|
982
|
+
# require "google/cloud/firestore"
|
983
|
+
#
|
984
|
+
# firestore = Google::Cloud::Firestore.new
|
985
|
+
# query = firestore.col(:cities).select(:population)
|
986
|
+
#
|
987
|
+
# json = query.to_json
|
988
|
+
#
|
989
|
+
# new_query = Google::Cloud::Firestore::Query.from_json json, firestore
|
990
|
+
#
|
991
|
+
# new_query.get do |city|
|
992
|
+
# puts "#{city.document_id} has #{city[:population]} residents."
|
993
|
+
# end
|
994
|
+
#
|
995
|
+
def to_json options = nil
|
996
|
+
query_json = Google::Cloud::Firestore::V1::StructuredQuery.encode_json query
|
997
|
+
{
|
998
|
+
"query" => JSON.parse(query_json),
|
999
|
+
"parent_path" => parent_path,
|
1000
|
+
"limit_type" => limit_type
|
1001
|
+
}.to_json options
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
##
|
1005
|
+
# Deserializes a JSON text string serialized from this class and returns it as a new instance. See also
|
1006
|
+
# {#to_json}.
|
1007
|
+
#
|
1008
|
+
# @param [String] json A JSON text string serialized using {#to_json}.
|
1009
|
+
# @param [Google::Cloud::Firestore::Client] client A connected client instance.
|
1010
|
+
#
|
1011
|
+
# @return [Query] A new query equal to the original query used to create the JSON text string.
|
1012
|
+
#
|
1013
|
+
# @example
|
1014
|
+
# require "google/cloud/firestore"
|
1015
|
+
#
|
1016
|
+
# firestore = Google::Cloud::Firestore.new
|
1017
|
+
# query = firestore.col(:cities).select(:population)
|
1018
|
+
#
|
1019
|
+
# json = query.to_json
|
1020
|
+
#
|
1021
|
+
# new_query = Google::Cloud::Firestore::Query.from_json json, firestore
|
1022
|
+
#
|
1023
|
+
# new_query.get do |city|
|
1024
|
+
# puts "#{city.document_id} has #{city[:population]} residents."
|
1025
|
+
# end
|
1026
|
+
#
|
1027
|
+
def self.from_json json, client
|
1028
|
+
raise ArgumentError, "client is required" unless client
|
1029
|
+
|
1030
|
+
json = JSON.parse json
|
1031
|
+
query_json = json["query"]
|
1032
|
+
raise ArgumentError, "Field 'query' is required" unless query_json
|
1033
|
+
query = Google::Cloud::Firestore::V1::StructuredQuery.decode_json query_json.to_json
|
1034
|
+
start query, json["parent_path"], client, limit_type: json["limit_type"]&.to_sym
|
1035
|
+
end
|
1036
|
+
|
962
1037
|
##
|
963
1038
|
# @private Start a new Query.
|
964
1039
|
def self.start query, parent_path, client, limit_type: nil
|
965
|
-
query
|
966
|
-
Query.new.tap do |q|
|
967
|
-
q.instance_variable_set :@query, query
|
968
|
-
q.instance_variable_set :@parent_path, parent_path
|
969
|
-
q.instance_variable_set :@limit_type, limit_type
|
970
|
-
q.instance_variable_set :@client, client
|
971
|
-
end
|
1040
|
+
new query, parent_path, client, limit_type: limit_type
|
972
1041
|
end
|
973
1042
|
|
974
1043
|
protected
|
@@ -993,28 +1062,25 @@ module Google
|
|
993
1062
|
"eq" => :EQUAL,
|
994
1063
|
"eql" => :EQUAL,
|
995
1064
|
"is" => :EQUAL,
|
1065
|
+
"!=" => :NOT_EQUAL,
|
996
1066
|
"array_contains" => :ARRAY_CONTAINS,
|
997
1067
|
"array-contains" => :ARRAY_CONTAINS,
|
998
1068
|
"include" => :ARRAY_CONTAINS,
|
999
1069
|
"include?" => :ARRAY_CONTAINS,
|
1000
1070
|
"has" => :ARRAY_CONTAINS,
|
1001
1071
|
"in" => :IN,
|
1072
|
+
"not_in" => :NOT_IN,
|
1073
|
+
"not-in" => :NOT_IN,
|
1002
1074
|
"array_contains_any" => :ARRAY_CONTAINS_ANY,
|
1003
1075
|
"array-contains-any" => :ARRAY_CONTAINS_ANY
|
1004
1076
|
}.freeze
|
1005
1077
|
##
|
1006
1078
|
# @private
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
# @private
|
1013
|
-
INEQUALITY_FILTERS = %i[
|
1014
|
-
LESS_THAN
|
1015
|
-
LESS_THAN_OR_EQUAL
|
1016
|
-
GREATER_THAN
|
1017
|
-
GREATER_THAN_OR_EQUAL
|
1079
|
+
INEQUALITY_FILTERS = [
|
1080
|
+
:LESS_THAN,
|
1081
|
+
:LESS_THAN_OR_EQUAL,
|
1082
|
+
:GREATER_THAN,
|
1083
|
+
:GREATER_THAN_OR_EQUAL
|
1018
1084
|
].freeze
|
1019
1085
|
|
1020
1086
|
def value_nil? value
|
@@ -1031,18 +1097,20 @@ module Google
|
|
1031
1097
|
value_nil?(value) || value_nan?(value)
|
1032
1098
|
end
|
1033
1099
|
|
1034
|
-
def filter name,
|
1100
|
+
def filter name, op_key, value
|
1035
1101
|
field = StructuredQuery::FieldReference.new field_path: name.to_s
|
1036
|
-
operator = FILTER_OPS[
|
1037
|
-
raise ArgumentError, "unknown operator #{
|
1102
|
+
operator = FILTER_OPS[op_key.to_s.downcase]
|
1103
|
+
raise ArgumentError, "unknown operator #{op_key}" if operator.nil?
|
1038
1104
|
|
1039
1105
|
if value_unary? value
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1106
|
+
operator = case operator
|
1107
|
+
when :EQUAL
|
1108
|
+
value_nan?(value) ? :IS_NAN : :IS_NULL
|
1109
|
+
when :NOT_EQUAL
|
1110
|
+
value_nan?(value) ? :IS_NOT_NAN : :IS_NOT_NULL
|
1111
|
+
else
|
1112
|
+
raise ArgumentError, "can only perform '==' and '!=' comparisons on #{value} values"
|
1113
|
+
end
|
1046
1114
|
|
1047
1115
|
return StructuredQuery::Filter.new(
|
1048
1116
|
unary_filter: StructuredQuery::UnaryFilter.new(
|
@@ -1092,11 +1160,14 @@ module Google
|
|
1092
1160
|
return snapshot_to_cursor values.first, query
|
1093
1161
|
end
|
1094
1162
|
|
1163
|
+
# The *values param in start_at, start_after, etc. will wrap an array argument in an array, so unwrap it here.
|
1164
|
+
values = values.first if values.count == 1 && values.first.is_a?(Array)
|
1165
|
+
|
1095
1166
|
# pair values with their field_paths to ensure correct formatting
|
1096
1167
|
order_field_paths = order_by_field_paths query
|
1097
1168
|
if values.count > order_field_paths.count
|
1098
1169
|
# raise if too many values provided for the cursor
|
1099
|
-
raise ArgumentError, "
|
1170
|
+
raise ArgumentError, "There cannot be more cursor values than order by fields"
|
1100
1171
|
end
|
1101
1172
|
|
1102
1173
|
values = values.zip(order_field_paths).map do |value, field_path|
|
@@ -1128,7 +1199,6 @@ module Google
|
|
1128
1199
|
snapshot[field_path]
|
1129
1200
|
end
|
1130
1201
|
end
|
1131
|
-
|
1132
1202
|
values_to_cursor values, query
|
1133
1203
|
end
|
1134
1204
|
|
@@ -0,0 +1,80 @@
|
|
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
|
+
module Google
|
17
|
+
module Cloud
|
18
|
+
module Firestore
|
19
|
+
##
|
20
|
+
# # QueryPartition
|
21
|
+
#
|
22
|
+
# Represents a split point that can be used in a query as a starting and/or end point for the query results.
|
23
|
+
#
|
24
|
+
# The cursors returned by {#start_at} and {#end_before} can only be used in a query that matches the constraint of
|
25
|
+
# the query that produced this partition.
|
26
|
+
#
|
27
|
+
# See {CollectionGroup#partitions} and {Query}.
|
28
|
+
#
|
29
|
+
# @!attribute [r] start_at
|
30
|
+
# The cursor values that define the first result for this partition, or `nil` if this is the first partition.
|
31
|
+
# Returns an array of values that represent a position, in the order they appear in the order by clause of the
|
32
|
+
# query. Can contain fewer values than specified in the order by clause. Will be used in the query returned by
|
33
|
+
# {#to_query}.
|
34
|
+
# @return [Array<Object>, nil] Typically, the values are {DocumentReference} objects.
|
35
|
+
# @!attribute [r] end_before
|
36
|
+
# The cursor values that define the first result after this partition, or `nil` if this is the last partition.
|
37
|
+
# Returns an array of values that represent a position, in the order they appear in the order by clause of the
|
38
|
+
# query. Can contain fewer values than specified in the order by clause. Will be used in the query returned by
|
39
|
+
# {#to_query}.
|
40
|
+
# @return [Array<Object>, nil] Typically, the values are {DocumentReference} objects.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# require "google/cloud/firestore"
|
44
|
+
#
|
45
|
+
# firestore = Google::Cloud::Firestore.new
|
46
|
+
#
|
47
|
+
# col_group = firestore.col_group "cities"
|
48
|
+
#
|
49
|
+
# partitions = col_group.partitions 3
|
50
|
+
#
|
51
|
+
# queries = partitions.map(&:to_query)
|
52
|
+
#
|
53
|
+
class QueryPartition
|
54
|
+
attr_reader :start_at
|
55
|
+
attr_reader :end_before
|
56
|
+
|
57
|
+
##
|
58
|
+
# @private New QueryPartition from query and Cursor
|
59
|
+
def initialize query, start_at, end_before
|
60
|
+
@query = query
|
61
|
+
@start_at = start_at
|
62
|
+
@end_before = end_before
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Creates a new query that only returns the documents for this partition, using the cursor values from
|
67
|
+
# {#start_at} and {#end_before}.
|
68
|
+
#
|
69
|
+
# @return [Query] The query for the partition.
|
70
|
+
#
|
71
|
+
def to_query
|
72
|
+
base_query = @query
|
73
|
+
base_query = base_query.start_at start_at if start_at
|
74
|
+
base_query = base_query.end_before end_before if end_before
|
75
|
+
base_query
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,58 @@
|
|
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
|
+
module Google
|
17
|
+
module Cloud
|
18
|
+
module Firestore
|
19
|
+
##
|
20
|
+
# @private
|
21
|
+
#
|
22
|
+
# Represents a resource path to the Firestore API.
|
23
|
+
#
|
24
|
+
class ResourcePath
|
25
|
+
include Comparable
|
26
|
+
|
27
|
+
RESOURCE_PATH_RE = %r{^projects/([^/]*)/databases/([^/]*)(?:/documents/)?([\s\S]*)$}.freeze
|
28
|
+
|
29
|
+
attr_reader :project_id
|
30
|
+
attr_reader :database_id
|
31
|
+
attr_reader :segments
|
32
|
+
|
33
|
+
##
|
34
|
+
# Creates a resource path object.
|
35
|
+
#
|
36
|
+
# @param [Array<String>] segments One or more strings representing the resource path.
|
37
|
+
#
|
38
|
+
# @return [ResourcePath] The resource path object.
|
39
|
+
#
|
40
|
+
def initialize project_id, database_id, segments
|
41
|
+
@project_id = project_id
|
42
|
+
@database_id = database_id
|
43
|
+
@segments = segments.split "/"
|
44
|
+
end
|
45
|
+
|
46
|
+
def <=> other
|
47
|
+
return nil unless other.is_a? ResourcePath
|
48
|
+
[project_id, database_id, segments] <=> [other.project_id, other.database_id, other.segments]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.from_path path
|
52
|
+
data = RESOURCE_PATH_RE.match path
|
53
|
+
new data[1], data[2], data[3]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|