Skip to content

Commit

Permalink
fix(firestore): allow 30 conjunctive & disjunctive queries for "in" &…
Browse files Browse the repository at this point in the history
… "array-contains-any" via where() API (#11265)
  • Loading branch information
russellwheatley committed Jul 12, 2023
1 parent 81156d1 commit f5477b1
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 202 deletions.
Expand Up @@ -2268,202 +2268,128 @@ void runQueryTests() {
skip: kIsWeb,
);

// TODO(russellwheatley): Firestore allows up to 30 disjunctive queries but testing on android & iOS reveals it only allows 10 when using where() with "arrayContainsAny", "whereIn" & "whereNotIn"
// testWidgets(
// 'allow multiple disjunctive queries for "arrayContainsAny" using ".where() API"',
// (_) async {
// CollectionReference<Map<String, dynamic>> collection =
// await initializeTest('multiple-disjunctive-where');
//
// await Future.wait([
// collection.doc('doc1').set({
// 'genre': ['Not', 'Here'],
// 'number': 1
// }),
// collection.doc('doc2').set({
// 'genre': ['Animation', 'Another'],
// 'number': 2
// }),
// collection.doc('doc3').set({
// 'genre': ['Adventure', 'Another'],
// 'number': 3
// }),
// ]);
// final genres = [
// 'Action',
// 'Adventure',
// 'Animation',
// 'Biography',
// 'Comedy',
// 'Crime',
// 'Drama',
// 'Documentary',
// 'Family',
// 'Fantasy',
// 'Film-Noir',
// // 'History',
// // 'Horror',
// // 'Music',
// // 'Musical',
// // 'Mystery',
// // 'Romance',
// // 'Sci-Fi',
// // 'Sport',
// // 'Thriller',
// // 'War',
// // 'Western',
// // 'Epic',
// // 'Tragedy',
// // 'Satire',
// // 'Romantic Comedy',
// // 'Black Comedy',
// // 'Paranormal',
// // 'Non-fiction',
// // 'Realism',
// ];
//
// final results = await collection
// .where(
// 'genre',
// arrayContainsAny: genres,
// )
// .orderBy('number')
// .get();
//
// expect(results.docs.length, equals(2));
// expect(results.docs[0].id, equals('doc2'));
// expect(results.docs[1].id, equals('doc3'));
// });
//
// testWidgets(
// 'allow multiple disjunctive queries for "whereIn" using ".where() API"',
// (_) async {
// CollectionReference<Map<String, dynamic>> collection =
// await initializeTest('multiple-disjunctive-where');
//
// await Future.wait([
// collection.doc('doc1').set({
// 'genre': 'Not this',
// 'number': 1
// }),
// collection.doc('doc2').set({
// 'genre': 'Animation',
// 'number': 2
// }),
// collection.doc('doc3').set({
// 'genre': 'Adventure',
// 'number': 3
// }),
// ]);
// final genres = [
// 'Action',
// 'Adventure',
// 'Animation',
// 'Biography',
// 'Comedy',
// 'Crime',
// 'Drama',
// 'Documentary',
// 'Family',
// 'Fantasy',
// 'Film-Noir',
// 'History',
// 'Horror',
// 'Music',
// 'Musical',
// 'Mystery',
// 'Romance',
// 'Sci-Fi',
// 'Sport',
// 'Thriller',
// 'War',
// 'Western',
// 'Epic',
// 'Tragedy',
// 'Satire',
// 'Romantic Comedy',
// 'Black Comedy',
// 'Paranormal',
// 'Non-fiction',
// 'Realism',
// ];
//
// final results = await collection
// .where(
// 'genre',
// whereIn: genres,
// )
// .orderBy('number')
// .get();
//
// expect(results.docs.length, equals(2));
// expect(results.docs[0].id, equals('doc2'));
// expect(results.docs[1].id, equals('doc3'));
// });
//
// testWidgets(
// 'allow multiple disjunctive queries for "whereNotIn" using ".where() API"',
// (_) async {
// CollectionReference<Map<String, dynamic>> collection =
// await initializeTest('multiple-disjunctive-where-not-in');
//
// await Future.wait([
// collection.doc('doc1').set({
// 'genre': 'Action',
// 'number': 1
// }),
// collection.doc('doc2').set({
// 'genre': 'Animation',
// 'number': 2
// }),
// collection.doc('doc3').set({
// 'genre': 'Adventure',
// 'number': 3
// }),
// ]);
// final genres = [
// 'Action',
// 'Biography',
// 'Comedy',
// 'Crime',
// 'Drama',
// 'Documentary',
// 'Family',
// 'Fantasy',
// 'Film-Noir',
// 'History',
// 'Horror',
// 'Music',
// 'Musical',
// 'Mystery',
// 'Romance',
// 'Sci-Fi',
// 'Sport',
// 'Thriller',
// 'War',
// 'Western',
// 'Epic',
// 'Tragedy',
// 'Satire',
// 'Romantic Comedy',
// 'Black Comedy',
// 'Paranormal',
// 'Non-fiction',
// 'Realism',
// ];
//
// final results = await collection
// .where(
// 'genre',
// whereNotIn: genres,
// )
// .orderBy('genre')
// .get();
//
// expect(results.docs.length, equals(2));
// expect(results.docs[0].id, equals('doc3'));
// expect(results.docs[1].id, equals('doc2'));
// });
testWidgets(
'allow multiple disjunctive queries for "arrayContainsAny" using ".where() API"',
(_) async {
CollectionReference<Map<String, dynamic>> collection =
await initializeTest('multiple-disjunctive-where');

await Future.wait([
collection.doc('doc1').set({
'genre': ['Not', 'Here'],
'number': 1
}),
collection.doc('doc2').set({
'genre': ['Animation', 'Another'],
'number': 2
}),
collection.doc('doc3').set({
'genre': ['Adventure', 'Another'],
'number': 3
}),
]);
final genres = [
'Action',
'Adventure',
'Animation',
'Biography',
'Comedy',
'Crime',
'Drama',
'Documentary',
'Family',
'Fantasy',
'Film-Noir',
'History',
'Horror',
'Music',
'Musical',
'Mystery',
'Romance',
'Sci-Fi',
'Sport',
'Thriller',
'War',
'Western',
'Epic',
'Tragedy',
'Satire',
'Romantic Comedy',
'Black Comedy',
'Paranormal',
'Non-fiction',
'Realism',
];

final results = await collection
.where(
'genre',
arrayContainsAny: genres,
)
.orderBy('number')
.get();

expect(results.docs.length, equals(2));
expect(results.docs[0].id, equals('doc2'));
expect(results.docs[1].id, equals('doc3'));
});

testWidgets(
'allow multiple disjunctive queries for "whereIn" using ".where() API"',
(_) async {
CollectionReference<Map<String, dynamic>> collection =
await initializeTest('multiple-disjunctive-where');

await Future.wait([
collection.doc('doc1').set({'genre': 'Not this', 'number': 1}),
collection.doc('doc2').set({'genre': 'Animation', 'number': 2}),
collection.doc('doc3').set({'genre': 'Adventure', 'number': 3}),
]);
final genres = [
'Action',
'Adventure',
'Animation',
'Biography',
'Comedy',
'Crime',
'Drama',
'Documentary',
'Family',
'Fantasy',
'Film-Noir',
'History',
'Horror',
'Music',
'Musical',
'Mystery',
'Romance',
'Sci-Fi',
'Sport',
'Thriller',
'War',
'Western',
'Epic',
'Tragedy',
'Satire',
'Romantic Comedy',
'Black Comedy',
'Paranormal',
'Non-fiction',
'Realism',
];

final results = await collection
.where(
'genre',
whereIn: genres,
)
.orderBy('number')
.get();

expect(results.docs.length, equals(2));
expect(results.docs[0].id, equals('doc2'));
expect(results.docs[1].id, equals('doc3'));
});

testWidgets('isEqualTo filter', (_) async {
CollectionReference<Map<String, dynamic>> collection =
Expand Down
10 changes: 9 additions & 1 deletion packages/cloud_firestore/cloud_firestore/lib/src/query.dart
Expand Up @@ -730,10 +730,18 @@ class _JsonQuery implements Query<Map<String, dynamic>> {
value is Iterable,
"A non-empty [Iterable] is required for '$operator' filters.",
);
// This assert checks every operator other than "in" or "array-contains-any" have 10 or less filters
assert(
(value as Iterable).length <= 10,
(operator == 'in' || operator == 'array-contains-any') ||
(value as Iterable).length <= 10,
"'$operator' filters support a maximum of 10 elements in the value [Iterable].",
);
// This assert checks whether "in" or "array-contains-any" have 30 or less filters
assert(
(operator != 'in' && operator != 'array-contains-any') ||
(value as Iterable).length <= 30,
"'$operator' filters support a maximum of 30 elements in the value [Iterable].",
);
assert(
(value as Iterable).isNotEmpty,
"'$operator' filters require a non-empty [Iterable].",
Expand Down
21 changes: 16 additions & 5 deletions packages/cloud_firestore/cloud_firestore/test/query_test.dart
Expand Up @@ -116,19 +116,30 @@ void main() {
);
});

test('throws if whereIn query length is greater than 10', () {
test('throws if whereIn query length is greater than 30', () {
List<int> numbers = List.generate(31, (i) => i + 1);
expect(
() => query!
.where('foo.bar', whereIn: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]),
() => query!.where('foo.bar', whereIn: numbers),
throwsAssertionError,
);
});

test('throws if arrayContainsAny query length is greater than 30', () {
List<int> numbers = List.generate(31, (i) => i + 1);
expect(
() => query!.where(
'foo',
arrayContainsAny: numbers,
),
throwsAssertionError,
);
});

test('throws if arrayContainsAny query length is greater than 10', () {
test('throws if whereNotIn query length is greater than 10', () {
expect(
() => query!.where(
'foo',
arrayContainsAny: [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9],
whereNotIn: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
),
throwsAssertionError,
);
Expand Down

0 comments on commit f5477b1

Please sign in to comment.