Skip to content

Commit 14b2878

Browse files
author
Brian Chen
authored
Allow nested arrays for IN queries (#2346)
1 parent ed4b147 commit 14b2878

File tree

3 files changed

+52
-31
lines changed

3 files changed

+52
-31
lines changed

packages/firestore/src/api/database.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1489,7 +1489,9 @@ export class Query implements firestore.Query {
14891489
}
14901490
fieldValue = this.firestore._dataConverter.parseQueryValue(
14911491
'Query.where',
1492-
value
1492+
value,
1493+
// We only allow nested arrays for IN queries.
1494+
/** allowArrays = */ operator === Operator.IN ? true : false
14931495
);
14941496
}
14951497
const filter = FieldFilter.create(fieldPath, operator, fieldValue);

packages/firestore/src/api/user_data_converter.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,12 @@ enum UserDataSource {
134134
* Indicates the source is a where clause, cursor bound, arrayUnion()
135135
* element, etc. Of note, isWrite(source) will return false.
136136
*/
137-
Argument
137+
Argument,
138+
/**
139+
* Indicates that the source is an Argument that may directly contain nested
140+
* arrays (e.g. the operand of an `in` query).
141+
*/
142+
ArrayArgument
138143
}
139144

140145
function isWrite(dataSource: UserDataSource): boolean {
@@ -144,6 +149,7 @@ function isWrite(dataSource: UserDataSource): boolean {
144149
case UserDataSource.Update:
145150
return true;
146151
case UserDataSource.Argument:
152+
case UserDataSource.ArrayArgument:
147153
return false;
148154
default:
149155
throw fail(`Unexpected case for UserDataSource: ${dataSource}`);
@@ -478,10 +484,17 @@ export class UserDataConverter {
478484
/**
479485
* Parse a "query value" (e.g. value in a where filter or a value in a cursor
480486
* bound).
487+
*
488+
* @param allowArrays Whether the query value is an array that may directly
489+
* contain additional arrays (e.g. the operand of an `in` query).
481490
*/
482-
parseQueryValue(methodName: string, input: unknown): FieldValue {
491+
parseQueryValue(
492+
methodName: string,
493+
input: unknown,
494+
allowArrays = false
495+
): FieldValue {
483496
const context = new ParseContext(
484-
UserDataSource.Argument,
497+
allowArrays ? UserDataSource.ArrayArgument : UserDataSource.Argument,
485498
methodName,
486499
FieldPath.EMPTY_PATH
487500
);
@@ -536,7 +549,14 @@ export class UserDataConverter {
536549
if (input instanceof Array) {
537550
// TODO(b/34871131): Include the path containing the array in the error
538551
// message.
539-
if (context.arrayElement) {
552+
// In the case of IN queries, the parsed data is an array (representing
553+
// the set of values to be included for the IN query) that may directly
554+
// contain additional arrays (each representing an individual field
555+
// value), so we disable this validation.
556+
if (
557+
context.arrayElement &&
558+
context.dataSource !== UserDataSource.ArrayArgument
559+
) {
540560
throw context.createError('Nested arrays are not supported');
541561
}
542562
return this.parseArray(input as unknown[], context);

packages/firestore/test/integration/api/query.test.ts

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import { EventsAccumulator } from '../util/events_accumulator';
2626
import firebase from '../util/firebase_export';
2727
import {
2828
apiDescribe,
29-
isRunningAgainstEmulator,
3029
toChangesArray,
3130
toDataArray,
3231
withTestCollection,
@@ -799,14 +798,18 @@ apiDescribe('Queries', (persistence: boolean) => {
799798
c: { zip: 98103 },
800799
d: { zip: [98101] },
801800
e: { zip: ['98101', { zip: 98101 }] },
802-
f: { zip: { code: 500 } }
801+
f: { zip: { code: 500 } },
802+
g: { zip: [98101, 98102] }
803803
};
804804

805805
await withTestCollection(persistence, testDocs, async coll => {
806-
const snapshot = await coll.where('zip', 'in', [98101, 98103]).get();
806+
const snapshot = await coll
807+
.where('zip', 'in', [98101, 98103, [98101, 98102]])
808+
.get();
807809
expect(toDataArray(snapshot)).to.deep.equal([
808810
{ zip: 98101 },
809-
{ zip: 98103 }
811+
{ zip: 98103 },
812+
{ zip: [98101, 98102] }
810813
]);
811814

812815
// With objects.
@@ -815,28 +818,24 @@ apiDescribe('Queries', (persistence: boolean) => {
815818
});
816819
});
817820

818-
// eslint-disable-next-line no-restricted-properties,
819-
(isRunningAgainstEmulator() ? it : it.skip)(
820-
'can use IN filters by document ID',
821-
async () => {
822-
const testDocs = {
823-
aa: { key: 'aa' },
824-
ab: { key: 'ab' },
825-
ba: { key: 'ba' },
826-
bb: { key: 'bb' }
827-
};
828-
await withTestCollection(persistence, testDocs, async coll => {
829-
const snapshot = await coll
830-
.where(FieldPath.documentId(), 'in', ['aa', 'ab'])
831-
.get();
832-
833-
expect(toDataArray(snapshot)).to.deep.equal([
834-
{ key: 'aa' },
835-
{ key: 'ab' }
836-
]);
837-
});
838-
}
839-
);
821+
it('can use IN filters by document ID', async () => {
822+
const testDocs = {
823+
aa: { key: 'aa' },
824+
ab: { key: 'ab' },
825+
ba: { key: 'ba' },
826+
bb: { key: 'bb' }
827+
};
828+
await withTestCollection(persistence, testDocs, async coll => {
829+
const snapshot = await coll
830+
.where(FieldPath.documentId(), 'in', ['aa', 'ab'])
831+
.get();
832+
833+
expect(toDataArray(snapshot)).to.deep.equal([
834+
{ key: 'aa' },
835+
{ key: 'ab' }
836+
]);
837+
});
838+
});
840839

841840
it('can use array-contains-any filters', async () => {
842841
const testDocs = {

0 commit comments

Comments
 (0)