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
@@ -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
|