Skip to content

Commit 8978ba5

Browse files
Googlera-maurice
authored andcommitted
[c++] port ARRAY_CONTAINS_ANY and IN queries
waiting on cl/292025113 to go in first to allow tests to pass TESTED= linux: http://sponge2/ce3c91a9-a3f9-42d5-b76f-daff37935288 iOS: http://sponge2/45798482-0370-41a4-8470-655dd07eb8fe Android: http://sponge2/aed789cf-a62b-403e-9b50-65201d69719b PiperOrigin-RevId: 292227077
1 parent 3e93baa commit 8978ba5

File tree

8 files changed

+201
-9
lines changed

8 files changed

+201
-9
lines changed

firestore/src/android/query_android.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,36 @@ Query QueryInternal::Where(const FieldPath& field, query::Method method,
106106
return Query{internal};
107107
}
108108

109+
Query QueryInternal::Where(const FieldPath& field, query::Method method,
110+
const std::vector<FieldValue>& values) {
111+
JNIEnv* env = firestore_->app()->GetJNIEnv();
112+
113+
// Convert std::vector into java.util.List object.
114+
// TODO(chenbrian): Refactor this into a helper function.
115+
jobject converted_values = env->NewObject(
116+
util::array_list::GetClass(),
117+
util::array_list::GetMethodId(util::array_list::kConstructor));
118+
jmethodID add_method = util::array_list::GetMethodId(util::array_list::kAdd);
119+
jsize size = static_cast<jsize>(values.size());
120+
for (jsize i = 0; i < size; ++i) {
121+
// ArrayList.Add() always returns true, which we have no use for.
122+
env->CallBooleanMethod(converted_values, add_method,
123+
values[i].internal_->java_object());
124+
CheckAndClearJniExceptions(env);
125+
}
126+
127+
jobject path = FieldPathConverter::ToJavaObject(env, field);
128+
jobject query = env->CallObjectMethod(obj_, query::GetMethodId(method), path,
129+
converted_values);
130+
CheckAndClearJniExceptions(env);
131+
QueryInternal* internal = new QueryInternal{firestore_, query};
132+
env->DeleteLocalRef(path);
133+
env->DeleteLocalRef(query);
134+
env->DeleteLocalRef(converted_values);
135+
136+
return Query{internal};
137+
}
138+
109139
Query QueryInternal::WithBound(query::Method method,
110140
const DocumentSnapshot& snapshot) {
111141
JNIEnv* env = firestore_->app()->GetJNIEnv();

firestore/src/android/query_android.h

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,17 @@ enum class QueryFn {
5050
X(ArrayContains, "whereArrayContains", \
5151
"(Lcom/google/firebase/firestore/FieldPath;Ljava/lang/Object;)" \
5252
"Lcom/google/firebase/firestore/Query;"), \
53+
X(ArrayContainsAny, "whereArrayContainsAny", \
54+
"(Lcom/google/firebase/firestore/FieldPath;Ljava/util/List;)" \
55+
"Lcom/google/firebase/firestore/Query;"), \
56+
X(In, "whereIn", \
57+
"(Lcom/google/firebase/firestore/FieldPath;Ljava/util/List;)" \
58+
"Lcom/google/firebase/firestore/Query;"), \
5359
X(OrderBy, "orderBy", \
5460
"(Lcom/google/firebase/firestore/FieldPath;" \
5561
"Lcom/google/firebase/firestore/Query$Direction;)" \
5662
"Lcom/google/firebase/firestore/Query;"), \
57-
X(Limit, "limit", "(J)Lcom/google/firebase/firestore/Query;"), \
63+
X(Limit, "limit", "(J)Lcom/google/firebase/firestore/Query;"), \
5864
X(StartAtSnapshot, "startAt", \
5965
"(Lcom/google/firebase/firestore/DocumentSnapshot;)" \
6066
"Lcom/google/firebase/firestore/Query;"), \
@@ -171,7 +177,7 @@ class QueryInternal : public WrapperFuture<QueryFn, QueryFn::kCount> {
171177
* documents must contain the specified field, the value must be an array, and
172178
* that the array must contain the provided value.
173179
*
174-
* A Query can have only one WhereArrayContains() filter.
180+
* A Query can have only one `WhereArrayContains()` filter.
175181
*
176182
* @param[in] field The path of the field containing an array to search
177183
* @param[in] value The value that must be contained in the array
@@ -182,6 +188,41 @@ class QueryInternal : public WrapperFuture<QueryFn, QueryFn::kCount> {
182188
return Where(field, query::kArrayContains, value);
183189
}
184190

191+
/**
192+
* @brief Creates and returns a new Query with the additional filter that
193+
* documents must contain the specified field, the value must be an array, and
194+
* that the array must contain at least one value from the provided list.
195+
*
196+
* A Query can have only one `WhereArrayContainsAny()` filter and it cannot be
197+
* combined with `WhereArrayContains()` or `WhereIn()`.
198+
*
199+
* @param[in] field The path of the field containing an array to search.
200+
* @param[in] values The list that contains the values to match.
201+
*
202+
* @return The created Query.
203+
*/
204+
Query WhereArrayContainsAny(const FieldPath& field,
205+
const std::vector<FieldValue>& values) {
206+
return Where(field, query::kArrayContainsAny, values);
207+
}
208+
209+
/**
210+
* @brief Creates and returns a new Query with the additional filter that
211+
* documents must contain the specified field and the value must equal one of
212+
* the values from the provided list.
213+
*
214+
* A Query can have only one `WhereIn()` filter and it cannot be
215+
* combined with `WhereArrayContainsAny()`.
216+
*
217+
* @param[in] field The path of the field containing an array to search.
218+
* @param[in] values The list that contains the values to match.
219+
*
220+
* @return The created Query.
221+
*/
222+
Query WhereIn(const FieldPath& field, const std::vector<FieldValue>& values) {
223+
return Where(field, query::kIn, values);
224+
}
225+
185226
/**
186227
* @brief Creates and returns a new Query that's additionally sorted by the
187228
* specified field.
@@ -387,6 +428,8 @@ class QueryInternal : public WrapperFuture<QueryFn, QueryFn::kCount> {
387428
// A generalized function for all WhereFoo calls.
388429
Query Where(const FieldPath& field, query::Method method,
389430
const FieldValue& value);
431+
Query Where(const FieldPath& field, query::Method method,
432+
const std::vector<FieldValue>& values);
390433

391434
// A generalized function for all {Start|End}{Before|After|At} calls.
392435
Query WithBound(query::Method method, const DocumentSnapshot& snapshot);

firestore/src/common/query.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,29 @@ Query Query::WhereArrayContains(const FieldPath& field,
155155
return internal_->WhereArrayContains(field, value);
156156
}
157157

158+
Query Query::WhereArrayContainsAny(const std::string& field,
159+
const std::vector<FieldValue>& values) {
160+
return WhereArrayContainsAny(FieldPath::FromDotSeparatedString(field),
161+
values);
162+
}
163+
164+
Query Query::WhereArrayContainsAny(const FieldPath& field,
165+
const std::vector<FieldValue>& values) {
166+
if (!internal_) return {};
167+
return internal_->WhereArrayContainsAny(field, values);
168+
}
169+
170+
Query Query::WhereIn(const std::string& field,
171+
const std::vector<FieldValue>& values) {
172+
return WhereIn(FieldPath::FromDotSeparatedString(field), values);
173+
}
174+
175+
Query Query::WhereIn(const FieldPath& field,
176+
const std::vector<FieldValue>& values) {
177+
if (!internal_) return {};
178+
return internal_->WhereIn(field, values);
179+
}
180+
158181
Query Query::OrderBy(const std::string& field, Direction direction) {
159182
return OrderBy(FieldPath::FromDotSeparatedString(field), direction);
160183
}

firestore/src/include/firebase/firestore/query.h

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ class Query {
275275
* documents must contain the specified field, the value must be an array, and
276276
* that the array must contain the provided value.
277277
*
278-
* A Query can have only one WhereArrayContains() filter.
278+
* A Query can have only one `WhereArrayContains()` filter and it cannot be
279+
* combined with `WhereArrayContainsAny()` or `WhereIn()`.
279280
*
280281
* @param[in] field The name of the field containing an array to search.
281282
* @param[in] value The value that must be contained in the array.
@@ -290,7 +291,8 @@ class Query {
290291
* documents must contain the specified field, the value must be an array, and
291292
* that the array must contain the provided value.
292293
*
293-
* A Query can have only one WhereArrayContains() filter.
294+
* A Query can have only one `WhereArrayContains()` filter and it cannot be
295+
* combined with `WhereArrayContainsAny()` or `WhereIn()`.
294296
*
295297
* @param[in] field The path of the field containing an array to search.
296298
* @param[in] value The value that must be contained in the array.
@@ -300,6 +302,70 @@ class Query {
300302
virtual Query WhereArrayContains(const FieldPath& field,
301303
const FieldValue& value);
302304

305+
/**
306+
* @brief Creates and returns a new Query with the additional filter that
307+
* documents must contain the specified field, the value must be an array, and
308+
* that the array must contain at least one value from the provided list.
309+
*
310+
* A Query can have only one `WhereArrayContainsAny()` filter and it cannot be
311+
* combined with `WhereArrayContains()` or `WhereIn()`.
312+
*
313+
* @param[in] field The name of the field containing an array to search.
314+
* @param[in] values The list that contains the values to match.
315+
*
316+
* @return The created Query.
317+
*/
318+
virtual Query WhereArrayContainsAny(const std::string& field,
319+
const std::vector<FieldValue>& values);
320+
321+
/**
322+
* @brief Creates and returns a new Query with the additional filter that
323+
* documents must contain the specified field, the value must be an array, and
324+
* that the array must contain at least one value from the provided list.
325+
*
326+
* A Query can have only one `WhereArrayContainsAny()` filter and it cannot be
327+
* combined with` WhereArrayContains()` or `WhereIn()`.
328+
*
329+
* @param[in] field The path of the field containing an array to search.
330+
* @param[in] values The list that contains the values to match.
331+
*
332+
* @return The created Query.
333+
*/
334+
virtual Query WhereArrayContainsAny(const FieldPath& field,
335+
const std::vector<FieldValue>& values);
336+
337+
/**
338+
* @brief Creates and returns a new Query with the additional filter that
339+
* documents must contain the specified field and the value must equal one of
340+
* the values from the provided list.
341+
*
342+
* A Query can have only one `WhereIn()` filter and it cannot be
343+
* combined with `WhereArrayContainsAny()`.
344+
*
345+
* @param[in] field The name of the field containing an array to search.
346+
* @param[in] values The list that contains the values to match.
347+
*
348+
* @return The created Query.
349+
*/
350+
virtual Query WhereIn(const std::string& field,
351+
const std::vector<FieldValue>& values);
352+
353+
/**
354+
* @brief Creates and returns a new Query with the additional filter that
355+
* documents must contain the specified field and the value must equal one of
356+
* the values from the provided list.
357+
*
358+
* A Query can have only one `WhereIn()` filter and it cannot be
359+
* combined with `WhereArrayContainsAny()`.
360+
*
361+
* @param[in] field The path of the field containing an array to search.
362+
* @param[in] values The list that contains the values to match.
363+
*
364+
* @return The created Query.
365+
*/
366+
virtual Query WhereIn(const FieldPath& field,
367+
const std::vector<FieldValue>& values);
368+
303369
/**
304370
* @brief Creates and returns a new Query that's additionally sorted by the
305371
* specified field.

firestore/src/ios/query_ios.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ Query QueryInternal::Where(const FieldPath& field_path, Operator op,
7474
return MakePublic(std::move(decorated));
7575
}
7676

77+
Query QueryInternal::Where(const FieldPath& field_path, Operator op,
78+
const std::vector<FieldValue>& values) const {
79+
const model::FieldPath& path = GetInternal(field_path);
80+
auto array_value = FieldValue::FromArray(values);
81+
model::FieldValue parsed =
82+
user_data_converter_.ParseQueryValue(array_value, true);
83+
auto describer = [&array_value] { return Describe(array_value.type()); };
84+
85+
api::Query decorated = query_.Filter(path, op, std::move(parsed), describer);
86+
return MakePublic(std::move(decorated));
87+
}
88+
7789
Query QueryInternal::WithBound(BoundPosition bound_pos,
7890
const DocumentSnapshot& snapshot) const {
7991
core::Bound bound = ToBound(bound_pos, snapshot);

firestore/src/ios/query_ios.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ class QueryInternal {
6969
return Where(field, Operator::ArrayContains, value);
7070
}
7171

72+
Query WhereArrayContainsAny(const FieldPath& field,
73+
const std::vector<FieldValue>& values) {
74+
return Where(field, Operator::ArrayContainsAny, values);
75+
}
76+
77+
Query WhereIn(const FieldPath& field, const std::vector<FieldValue>& values) {
78+
return Where(field, Operator::In, values);
79+
}
80+
7281
Query StartAt(const DocumentSnapshot& snapshot) {
7382
return WithBound(BoundPosition::kStartAt, snapshot);
7483
}
@@ -140,6 +149,8 @@ class QueryInternal {
140149

141150
Query Where(const FieldPath& field, Operator op,
142151
const FieldValue& value) const;
152+
Query Where(const FieldPath& field, Operator op,
153+
const std::vector<FieldValue>& values) const;
143154

144155
Query WithBound(BoundPosition bound_pos,
145156
const DocumentSnapshot& snapshot) const;

firestore/src/ios/user_data_converter_ios.cc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,10 @@ ParsedSetData UserDataConverter::ParseMergeData(
220220
CreateFieldMask(accumulator, maybe_field_mask.value()));
221221
}
222222

223-
model::FieldValue UserDataConverter::ParseQueryValue(
224-
const FieldValue& input) const {
225-
ParseAccumulator accumulator{UserDataSource::Argument};
223+
model::FieldValue UserDataConverter::ParseQueryValue(const FieldValue& input,
224+
bool allow_arrays) const {
225+
ParseAccumulator accumulator{allow_arrays ? UserDataSource::ArrayArgument
226+
: UserDataSource::Argument};
226227

227228
absl::optional<model::FieldValue> parsed =
228229
ParseData(input, accumulator.RootContext());
@@ -266,7 +267,12 @@ absl::optional<model::FieldValue> UserDataConverter::ParseData(
266267

267268
model::FieldValue::Array UserDataConverter::ParseArray(
268269
const std::vector<FieldValue>& input, ParseContext&& context) const {
269-
if (context.array_element()) {
270+
// In the case of IN queries, the parsed data is an array (representing the
271+
// set of values to be included for the IN query) that may directly contain
272+
// additional arrays (each representing an individual field value), so we
273+
// disable this validation.
274+
if (context.array_element() &&
275+
context.data_source() != core::UserDataSource::ArrayArgument) {
270276
ThrowInvalidArgument("Nested arrays are not supported");
271277
}
272278

firestore/src/ios/user_data_converter_ios.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ class UserDataConverter {
4747
* Parse a "query value" (e.g. value in a where filter or a value in a cursor
4848
* bound).
4949
*/
50-
model::FieldValue ParseQueryValue(const FieldValue& input) const;
50+
model::FieldValue ParseQueryValue(const FieldValue& input,
51+
bool allow_arrays = false) const;
5152

5253
private:
5354
using UpdateDataInput =

0 commit comments

Comments
 (0)