google-cloud-firestore 2.11.0 → 2.13.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.
@@ -0,0 +1,326 @@
1
+ # Copyright 2023 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
+
18
+ module Google
19
+ module Cloud
20
+ module Firestore
21
+ ##
22
+ # Represents the filter for structured query.
23
+ #
24
+ class Filter
25
+ ##
26
+ # @private Object of type
27
+ # Google::Cloud::Firestore::V1::StructuredQuery::Filter
28
+ attr_accessor :filter
29
+
30
+ ##
31
+ # Create a Filter object.
32
+ #
33
+ # @param field [FieldPath, String, Symbol] A field path to filter
34
+ # results with.
35
+ # If a {FieldPath} object is not provided then the field will be
36
+ # treated as a dotted string, meaning the string represents individual
37
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
38
+ # `.` cannot be in a dotted string, and should provided using a
39
+ # {FieldPath} object instead.
40
+ #
41
+ # @param operator [String, Symbol] The operation to compare the field
42
+ # to. Acceptable values include:
43
+ # * less than: `<`, `lt`
44
+ # * less than or equal: `<=`, `lte`
45
+ # * greater than: `>`, `gt`
46
+ # * greater than or equal: `>=`, `gte`
47
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
48
+ # * not equal: `!=`
49
+ # * in: `in`
50
+ # * not in: `not-in`, `not_in`
51
+ # * array contains: `array-contains`, `array_contains`
52
+ #
53
+ # @param value [Object] The value to compare the property to. Defaults to nil.
54
+ # Possible values are:
55
+ # * Integer
56
+ # * Float/BigDecimal
57
+ # * String
58
+ # * Boolean
59
+ # * Array
60
+ # * Date/Time
61
+ # * StringIO
62
+ # * Google::Cloud::Datastore::Key
63
+ # * Google::Cloud::Datastore::Entity
64
+ # * nil
65
+ #
66
+ # @return [Google::Cloud::Firestore::Filter] New filter for the given condition
67
+ #
68
+ # @example
69
+ # require "google/cloud/firestore"
70
+ #
71
+ # firestore = Google::Cloud::Firestore.new
72
+ #
73
+ # # Create a Filter
74
+ # Google::Cloud::Firestore::Filter.new(:population, :>=, 1000000)
75
+ #
76
+ def initialize field, operator, value
77
+ @filter = create_filter field, operator, value
78
+ end
79
+
80
+ ##
81
+ # Joins filter using AND operator.
82
+ #
83
+ # @overload where(filter)
84
+ # Pass Firestore::Filter to `where` via field_or_filter argument.
85
+ #
86
+ # @param filter [::Google::Cloud::Firestore::Filter]
87
+ #
88
+ # @overload where(field, operator, value)
89
+ # Pass arguments to `where` via positional arguments.
90
+ #
91
+ # @param field [FieldPath, String, Symbol] A field path to filter
92
+ # results with.
93
+ # If a {FieldPath} object is not provided then the field will be
94
+ # treated as a dotted string, meaning the string represents individual
95
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
96
+ # `.` cannot be in a dotted string, and should provided using a
97
+ # {FieldPath} object instead.
98
+ #
99
+ # @param operator [String, Symbol] The operation to compare the field
100
+ # to. Acceptable values include:
101
+ # * less than: `<`, `lt`
102
+ # * less than or equal: `<=`, `lte`
103
+ # * greater than: `>`, `gt`
104
+ # * greater than or equal: `>=`, `gte`
105
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
106
+ # * not equal: `!=`
107
+ # * in: `in`
108
+ # * not in: `not-in`, `not_in`
109
+ # * array contains: `array-contains`, `array_contains`
110
+ #
111
+ # @param value [Object] The value to compare the property to. Defaults to nil.
112
+ # Possible values are:
113
+ # * Integer
114
+ # * Float/BigDecimal
115
+ # * String
116
+ # * Boolean
117
+ # * Array
118
+ # * Date/Time
119
+ # * StringIO
120
+ # * Google::Cloud::Datastore::Key
121
+ # * Google::Cloud::Datastore::Entity
122
+ # * nil
123
+ #
124
+ # @return [Filter] New Filter object.
125
+ #
126
+ # @example Pass a Filter type object in argument
127
+ # require "google/cloud/firestore"
128
+ #
129
+ # filter_1 = Google::Cloud::Firestore.Firestore.new(:population, :>=, 1000000)
130
+ # filter_2 = Google::Cloud::Firestore.Firestore.new("done", "=", "false")
131
+ #
132
+ # filter = filter_1.and(filter_2)
133
+ #
134
+ # @example Pass filter conditions in the argument
135
+ # require "google/cloud/firestore"
136
+ #
137
+ # filter_1 = Google::Cloud::Firestore.Firestore.new(:population, :>=, 1000000)
138
+ #
139
+ # filter = filter_1.and("done", "=", "false")
140
+ #
141
+ def and filter_or_field = nil, operator = nil, value = nil
142
+ combine_filters composite_filter_and, filter_or_field, operator, value
143
+ end
144
+
145
+ ##
146
+ # Joins filter using OR operator.
147
+ #
148
+ # @overload where(filter)
149
+ # Pass Firestore::Filter to `where` via field_or_filter argument.
150
+ #
151
+ # @param filter [::Google::Cloud::Firestore::Filter]
152
+ #
153
+ # @overload where(field, operator, value)
154
+ # Pass arguments to `where` via positional arguments.
155
+ #
156
+ # @param field [FieldPath, String, Symbol] A field path to filter
157
+ # results with.
158
+ # If a {FieldPath} object is not provided then the field will be
159
+ # treated as a dotted string, meaning the string represents individual
160
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
161
+ # `.` cannot be in a dotted string, and should provided using a
162
+ # {FieldPath} object instead.
163
+ #
164
+ # @param operator [String, Symbol] The operation to compare the field
165
+ # to. Acceptable values include:
166
+ # * less than: `<`, `lt`
167
+ # * less than or equal: `<=`, `lte`
168
+ # * greater than: `>`, `gt`
169
+ # * greater than or equal: `>=`, `gte`
170
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
171
+ # * not equal: `!=`
172
+ # * in: `in`
173
+ # * not in: `not-in`, `not_in`
174
+ # * array contains: `array-contains`, `array_contains`
175
+ #
176
+ # @param value [Object] The value to compare the property to. Defaults to nil.
177
+ # Possible values are:
178
+ # * Integer
179
+ # * Float/BigDecimal
180
+ # * String
181
+ # * Boolean
182
+ # * Array
183
+ # * Date/Time
184
+ # * StringIO
185
+ # * Google::Cloud::Datastore::Key
186
+ # * Google::Cloud::Datastore::Entity
187
+ # * nil
188
+ #
189
+ # @return [Filter] New Filter object.
190
+ #
191
+ # @example Pass a Filter type object in argument
192
+ # require "google/cloud/firestore"
193
+ #
194
+ # filter_1 = Google::Cloud::Firestore.Firestore.new(:population, :>=, 1000000)
195
+ # filter_2 = Google::Cloud::Firestore.Firestore.new("done", "=", "false")
196
+ #
197
+ # filter = filter_1.or(filter_2)
198
+ #
199
+ # @example Pass filter conditions in the argument
200
+ # require "google/cloud/firestore"
201
+ #
202
+ # filter_1 = Google::Cloud::Firestore.Firestore.new(:population, :>=, 1000000)
203
+ #
204
+ # filter = filter_1.or("done", "=", "false")
205
+ #
206
+ def or filter_or_field = nil, operator = nil, value = nil
207
+ combine_filters composite_filter_or, filter_or_field, operator, value
208
+ end
209
+
210
+ private
211
+
212
+ ##
213
+ # @private
214
+ StructuredQuery = Google::Cloud::Firestore::V1::StructuredQuery
215
+
216
+ ##
217
+ # @private
218
+ FILTER_OPS = {
219
+ "<" => :LESS_THAN,
220
+ "lt" => :LESS_THAN,
221
+ "<=" => :LESS_THAN_OR_EQUAL,
222
+ "lte" => :LESS_THAN_OR_EQUAL,
223
+ ">" => :GREATER_THAN,
224
+ "gt" => :GREATER_THAN,
225
+ ">=" => :GREATER_THAN_OR_EQUAL,
226
+ "gte" => :GREATER_THAN_OR_EQUAL,
227
+ "=" => :EQUAL,
228
+ "==" => :EQUAL,
229
+ "eq" => :EQUAL,
230
+ "eql" => :EQUAL,
231
+ "is" => :EQUAL,
232
+ "!=" => :NOT_EQUAL,
233
+ "array_contains" => :ARRAY_CONTAINS,
234
+ "array-contains" => :ARRAY_CONTAINS,
235
+ "include" => :ARRAY_CONTAINS,
236
+ "include?" => :ARRAY_CONTAINS,
237
+ "has" => :ARRAY_CONTAINS,
238
+ "in" => :IN,
239
+ "not_in" => :NOT_IN,
240
+ "not-in" => :NOT_IN,
241
+ "array_contains_any" => :ARRAY_CONTAINS_ANY,
242
+ "array-contains-any" => :ARRAY_CONTAINS_ANY
243
+ }.freeze
244
+
245
+ ##
246
+ # @private
247
+ INEQUALITY_FILTERS = [
248
+ :LESS_THAN,
249
+ :LESS_THAN_OR_EQUAL,
250
+ :GREATER_THAN,
251
+ :GREATER_THAN_OR_EQUAL
252
+ ].freeze
253
+
254
+ def composite_filter_and
255
+ StructuredQuery::Filter.new(
256
+ composite_filter: StructuredQuery::CompositeFilter.new(op: :AND)
257
+ )
258
+ end
259
+
260
+ def composite_filter_or
261
+ StructuredQuery::Filter.new(
262
+ composite_filter: StructuredQuery::CompositeFilter.new(op: :OR)
263
+ )
264
+ end
265
+
266
+ def combine_filters new_filter, filter_or_field, operator, value
267
+ new_filter.composite_filter.filters << @filter
268
+ new_filter.composite_filter.filters << if filter_or_field.is_a? Google::Cloud::Firestore::Filter
269
+ filter_or_field.filter
270
+ else
271
+ create_filter filter_or_field, operator, value
272
+ end
273
+ dup.tap do |f|
274
+ f.filter = new_filter
275
+ end
276
+ end
277
+
278
+ def value_nil? value
279
+ [nil, :null, :nil].include? value
280
+ end
281
+
282
+ def value_nan? value
283
+ # Comparing NaN values raises, so check for #nan? first.
284
+ return true if value.respond_to?(:nan?) && value.nan?
285
+ [:nan].include? value
286
+ end
287
+
288
+ def value_unary? value
289
+ value_nil?(value) || value_nan?(value)
290
+ end
291
+
292
+ def create_filter field, op_key, value
293
+ return if field.nil? && op_key.nil? && value.nil?
294
+ field = FieldPath.parse field unless field.is_a? FieldPath
295
+ field = StructuredQuery::FieldReference.new field_path: field.formatted_string.to_s
296
+ operator = FILTER_OPS[op_key.to_s.downcase]
297
+ raise ArgumentError, "unknown operator #{op_key}" if operator.nil?
298
+
299
+ if value_unary? value
300
+ operator = case operator
301
+ when :EQUAL
302
+ value_nan?(value) ? :IS_NAN : :IS_NULL
303
+ when :NOT_EQUAL
304
+ value_nan?(value) ? :IS_NOT_NAN : :IS_NOT_NULL
305
+ else
306
+ raise ArgumentError, "can only perform '==' and '!=' comparisons on #{value} values"
307
+ end
308
+
309
+ return StructuredQuery::Filter.new(
310
+ unary_filter: StructuredQuery::UnaryFilter.new(
311
+ field: field, op: operator
312
+ )
313
+ )
314
+ end
315
+
316
+ value = Convert.raw_to_value value
317
+ StructuredQuery::Filter.new(
318
+ field_filter: StructuredQuery::FieldFilter.new(
319
+ field: field, op: operator, value: value
320
+ )
321
+ )
322
+ end
323
+ end
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,97 @@
1
+ # Copyright 2023 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
+ module Google
16
+ module Cloud
17
+ module Firestore
18
+ module Promise
19
+ ##
20
+ # # Future
21
+ #
22
+ # A Future object represents a value which will become available in future.
23
+ # May reject with a reason instead, e.g. when the tasks raises an exception.
24
+ #
25
+ class Future
26
+ ##
27
+ # Initialize the future object
28
+ #
29
+ def initialize future
30
+ @future = future
31
+ end
32
+
33
+ # Is it in fulfilled state?
34
+ #
35
+ # @return [Boolean]
36
+ def fulfilled?
37
+ @future.fulfilled?
38
+ end
39
+
40
+ # Is it in rejected state?
41
+ #
42
+ # @return [Boolean]
43
+ def rejected?
44
+ @future.rejected?
45
+ end
46
+
47
+ ##
48
+ # Method waits for the timeout duration and return the value of the future if
49
+ # fulfilled, timeout value in case of timeout and nil in case of rejection.
50
+ #
51
+ # @param [Integer] timeout the maximum time in seconds to wait
52
+ # @param [Object] timeout_value a value returned by the method when it times out
53
+ # @return [Object, nil, timeout_value] the value of the Future when fulfilled,
54
+ # timeout_value on timeout, nil on rejection.
55
+ def value timeout = nil, timeout_value = nil
56
+ @future.value timeout, timeout_value
57
+ end
58
+
59
+ # Returns reason of future's rejection.
60
+ #
61
+ # @return [Object, timeout_value] the reason, or timeout_value on timeout, or nil on fulfillment.
62
+ def reason timeout = nil, timeout_value = nil
63
+ @future.reason timeout, timeout_value
64
+ end
65
+
66
+ ##
67
+ # Method waits for the timeout duration and raise exception on rejection
68
+ #
69
+ # @param [Integer] timeout the maximum time in seconds to wait
70
+ def wait! timeout = nil
71
+ @future.wait! timeout
72
+ end
73
+
74
+ ##
75
+ # Chains the task to be executed synchronously after it fulfills. Does not run
76
+ # the task if it rejects. It will resolve though, triggering any dependent futures.
77
+ #
78
+ # @return [Future]
79
+ # @yield [reason, *args] to the task.
80
+ def then(*args, &task)
81
+ Future.new @future.then(*args, &task)
82
+ end
83
+
84
+ # Chains the task to be executed synchronously on executor after it rejects. Does
85
+ # not run the task if it fulfills. It will resolve though, triggering any
86
+ # dependent futures.
87
+ #
88
+ # @return [Future]
89
+ # @yield [reason, *args] to the task.
90
+ def rescue(*args, &task)
91
+ Future.new @future.rescue(*args, &task)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -18,6 +18,7 @@ require "google/cloud/firestore/document_snapshot"
18
18
  require "google/cloud/firestore/query_listener"
19
19
  require "google/cloud/firestore/convert"
20
20
  require "google/cloud/firestore/aggregate_query"
21
+ require "google/cloud/firestore/filter"
21
22
  require "json"
22
23
 
23
24
  module Google
@@ -210,29 +211,48 @@ module Google
210
211
  end
211
212
 
212
213
  ##
213
- # Filters the query on a field.
214
- #
215
- # @param [FieldPath, String, Symbol] field A field path to filter
216
- # results with.
217
- #
218
- # If a {FieldPath} object is not provided then the field will be
219
- # treated as a dotted string, meaning the string represents individual
220
- # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
221
- # `.` cannot be in a dotted string, and should provided using a
222
- # {FieldPath} object instead.
223
- # @param [String, Symbol] operator The operation to compare the field
224
- # to. Acceptable values include:
225
- #
226
- # * less than: `<`, `lt`
227
- # * less than or equal: `<=`, `lte`
228
- # * greater than: `>`, `gt`
229
- # * greater than or equal: `>=`, `gte`
230
- # * equal: `=`, `==`, `eq`, `eql`, `is`
231
- # * not equal: `!=`
232
- # * in: `in`
233
- # * not in: `not-in`, `not_in`
234
- # * array contains: `array-contains`, `array_contains`
235
- # @param [Object] value A value the field is compared to.
214
+ # Adds filter to the where clause
215
+ #
216
+ # @overload where(filter)
217
+ # Pass Firestore::Filter to `where` via field_or_filter argument.
218
+ #
219
+ # @param filter [::Google::Cloud::Firestore::Filter]
220
+ #
221
+ # @overload where(field, operator, value)
222
+ # Pass arguments to `where` via positional arguments.
223
+ #
224
+ # @param field [FieldPath, String, Symbol] A field path to filter
225
+ # results with.
226
+ # If a {FieldPath} object is not provided then the field will be
227
+ # treated as a dotted string, meaning the string represents individual
228
+ # fields joined by ".". Fields containing `~`, `*`, `/`, `[`, `]`, and
229
+ # `.` cannot be in a dotted string, and should provided using a
230
+ # {FieldPath} object instead.
231
+ #
232
+ # @param operator [String, Symbol] The operation to compare the field
233
+ # to. Acceptable values include:
234
+ # * less than: `<`, `lt`
235
+ # * less than or equal: `<=`, `lte`
236
+ # * greater than: `>`, `gt`
237
+ # * greater than or equal: `>=`, `gte`
238
+ # * equal: `=`, `==`, `eq`, `eql`, `is`
239
+ # * not equal: `!=`
240
+ # * in: `in`
241
+ # * not in: `not-in`, `not_in`
242
+ # * array contains: `array-contains`, `array_contains`
243
+ #
244
+ # @param value [Object] The value to compare the property to. Defaults to nil.
245
+ # Possible values are:
246
+ # * Integer
247
+ # * Float/BigDecimal
248
+ # * String
249
+ # * Boolean
250
+ # * Array
251
+ # * Date/Time
252
+ # * StringIO
253
+ # * Google::Cloud::Datastore::Key
254
+ # * Google::Cloud::Datastore::Entity
255
+ # * nil
236
256
  #
237
257
  # @return [Query] New query with `where` called on it.
238
258
  #
@@ -251,7 +271,25 @@ module Google
251
271
  # puts "#{city.document_id} has #{city[:population]} residents."
252
272
  # end
253
273
  #
254
- def where field, operator, value
274
+ # @example
275
+ # require "google/cloud/firestore"
276
+ #
277
+ # firestore = Google::Cloud::Firestore.new
278
+ #
279
+ # # Get a collection reference
280
+ # cities_col = firestore.col "cities"
281
+ #
282
+ # # Create a filter
283
+ # filter = Filter.create(:population, :>=, 1000000)
284
+ #
285
+ # # Add filter to where clause
286
+ # query = query.where filter
287
+ #
288
+ # query.get do |city|
289
+ # puts "#{city.document_id} has #{city[:population]} residents."
290
+ # end
291
+ #
292
+ def where filter_or_field = nil, operator = nil, value = nil
255
293
  if query_has_cursors?
256
294
  raise "cannot call where after calling " \
257
295
  "start_at, start_after, end_before, or end_at"
@@ -260,10 +298,12 @@ module Google
260
298
  new_query = @query.dup
261
299
  new_query ||= StructuredQuery.new
262
300
 
263
- field = FieldPath.parse field unless field.is_a? FieldPath
264
-
265
- new_filter = filter field.formatted_string, operator, value
266
- add_filters_to_query new_query, new_filter
301
+ if filter_or_field.is_a? Google::Cloud::Firestore::Filter
302
+ new_query.where = filter_or_field.filter
303
+ else
304
+ new_filter = Google::Cloud::Firestore::Filter.new filter_or_field, operator, value
305
+ add_filters_to_query new_query, new_filter.filter
306
+ end
267
307
 
268
308
  Query.start new_query, parent_path, client, limit_type: limit_type
269
309
  end
@@ -1087,34 +1127,6 @@ module Google
1087
1127
  # @private
1088
1128
  StructuredQuery = Google::Cloud::Firestore::V1::StructuredQuery
1089
1129
 
1090
- ##
1091
- # @private
1092
- FILTER_OPS = {
1093
- "<" => :LESS_THAN,
1094
- "lt" => :LESS_THAN,
1095
- "<=" => :LESS_THAN_OR_EQUAL,
1096
- "lte" => :LESS_THAN_OR_EQUAL,
1097
- ">" => :GREATER_THAN,
1098
- "gt" => :GREATER_THAN,
1099
- ">=" => :GREATER_THAN_OR_EQUAL,
1100
- "gte" => :GREATER_THAN_OR_EQUAL,
1101
- "=" => :EQUAL,
1102
- "==" => :EQUAL,
1103
- "eq" => :EQUAL,
1104
- "eql" => :EQUAL,
1105
- "is" => :EQUAL,
1106
- "!=" => :NOT_EQUAL,
1107
- "array_contains" => :ARRAY_CONTAINS,
1108
- "array-contains" => :ARRAY_CONTAINS,
1109
- "include" => :ARRAY_CONTAINS,
1110
- "include?" => :ARRAY_CONTAINS,
1111
- "has" => :ARRAY_CONTAINS,
1112
- "in" => :IN,
1113
- "not_in" => :NOT_IN,
1114
- "not-in" => :NOT_IN,
1115
- "array_contains_any" => :ARRAY_CONTAINS_ANY,
1116
- "array-contains-any" => :ARRAY_CONTAINS_ANY
1117
- }.freeze
1118
1130
  ##
1119
1131
  # @private
1120
1132
  INEQUALITY_FILTERS = [
@@ -1138,36 +1150,6 @@ module Google
1138
1150
  value_nil?(value) || value_nan?(value)
1139
1151
  end
1140
1152
 
1141
- def filter name, op_key, value
1142
- field = StructuredQuery::FieldReference.new field_path: name.to_s
1143
- operator = FILTER_OPS[op_key.to_s.downcase]
1144
- raise ArgumentError, "unknown operator #{op_key}" if operator.nil?
1145
-
1146
- if value_unary? value
1147
- operator = case operator
1148
- when :EQUAL
1149
- value_nan?(value) ? :IS_NAN : :IS_NULL
1150
- when :NOT_EQUAL
1151
- value_nan?(value) ? :IS_NOT_NAN : :IS_NOT_NULL
1152
- else
1153
- raise ArgumentError, "can only perform '==' and '!=' comparisons on #{value} values"
1154
- end
1155
-
1156
- return StructuredQuery::Filter.new(
1157
- unary_filter: StructuredQuery::UnaryFilter.new(
1158
- field: field, op: operator
1159
- )
1160
- )
1161
- end
1162
-
1163
- value = Convert.raw_to_value value
1164
- StructuredQuery::Filter.new(
1165
- field_filter: StructuredQuery::FieldFilter.new(
1166
- field: field, op: operator, value: value
1167
- )
1168
- )
1169
- end
1170
-
1171
1153
  def composite_filter
1172
1154
  StructuredQuery::Filter.new(
1173
1155
  composite_filter: StructuredQuery::CompositeFilter.new(op: :AND)