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.
@@ -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 { |c| yield c } if block_given?
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
- options = { token: token, max: @max }
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 { |r| yield r }
152
+ results.each(&block)
154
153
  if request_limit
155
154
  request_limit -= 1
156
- break if request_limit < 0
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{[\~\*\/\[\]]}.freeze
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
- # user_snap = firestore.doc("users/frank").get
30
+ # # Get a document reference
31
+ # nyc_ref = firestore.doc "cities/NYC"
31
32
  #
32
- # # TODO
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 ||= StructuredQuery.new
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
- EQUALITY_FILTERS = %i[
1008
- EQUAL
1009
- ARRAY_CONTAINS
1010
- ].freeze
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, op, value
1100
+ def filter name, op_key, value
1035
1101
  field = StructuredQuery::FieldReference.new field_path: name.to_s
1036
- operator = FILTER_OPS[op.to_s.downcase]
1037
- raise ArgumentError, "unknown operator #{op}" if operator.nil?
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
- if operator != :EQUAL
1041
- raise ArgumentError,
1042
- "can only check equality for #{value} values"
1043
- end
1044
-
1045
- operator = value_nan?(value) ? :IS_NAN : :IS_NULL
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, "too many values"
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