Skip to content

Commit 9719635

Browse files
author
Brian Chen
authored
Remove null and NaN checks in filters (#3960)
1 parent b247ffa commit 9719635

File tree

5 files changed

+65
-111
lines changed

5 files changed

+65
-111
lines changed

.changeset/wicked-cups-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/firestore': minor
3+
---
4+
5+
Removed excess validation of null and NaN values in query filters. This more closely aligns the SDK with the Firestore backend, which has always accepted null and NaN for all operators, even though this isn't necessarily useful.

packages/firestore/src/api/database.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,22 +1672,6 @@ function validateDisjunctiveFilterElements(
16721672
'maximum of 10 elements in the value array.'
16731673
);
16741674
}
1675-
if (operator === Operator.IN || operator === Operator.ARRAY_CONTAINS_ANY) {
1676-
if (value.indexOf(null) >= 0) {
1677-
throw new FirestoreError(
1678-
Code.INVALID_ARGUMENT,
1679-
`Invalid Query. '${operator.toString()}' filters cannot contain 'null' ` +
1680-
'in the value array.'
1681-
);
1682-
}
1683-
if (value.filter(element => Number.isNaN(element)).length > 0) {
1684-
throw new FirestoreError(
1685-
Code.INVALID_ARGUMENT,
1686-
`Invalid Query. '${operator.toString()}' filters cannot contain 'NaN' ` +
1687-
'in the value array.'
1688-
);
1689-
}
1690-
}
16911675
}
16921676

16931677
/**

packages/firestore/src/core/query.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,13 @@ import {
2323
arrayValueContains,
2424
canonicalId,
2525
isArray,
26-
isNanValue,
27-
isNullValue,
2826
isReferenceValue,
2927
typeOrder,
3028
valueCompare,
3129
valueEquals
3230
} from '../model/values';
3331
import { FieldPath, ResourcePath } from '../model/path';
3432
import { debugAssert, debugCast, fail } from '../util/assert';
35-
import { Code, FirestoreError } from '../util/error';
3633
import { isNullOrUndefined } from '../util/types';
3734
import {
3835
canonifyTarget,
@@ -603,22 +600,6 @@ export class FieldFilter extends Filter {
603600
);
604601
return new KeyFieldFilter(field, op, value);
605602
}
606-
} else if (isNullValue(value)) {
607-
if (op !== Operator.EQUAL && op !== Operator.NOT_EQUAL) {
608-
throw new FirestoreError(
609-
Code.INVALID_ARGUMENT,
610-
"Invalid query. Null only supports '==' and '!=' comparisons."
611-
);
612-
}
613-
return new FieldFilter(field, op, value);
614-
} else if (isNanValue(value)) {
615-
if (op !== Operator.EQUAL && op !== Operator.NOT_EQUAL) {
616-
throw new FirestoreError(
617-
Code.INVALID_ARGUMENT,
618-
"Invalid query. NaN only supports '==' and '!=' comparisons."
619-
);
620-
}
621-
return new FieldFilter(field, op, value);
622603
} else if (op === Operator.ARRAY_CONTAINS) {
623604
return new ArrayContainsFilter(field, value);
624605
} else if (op === Operator.IN) {

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

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,9 @@ apiDescribe('Queries', (persistence: boolean) => {
759759
a: { array: [42] },
760760
b: { array: ['a', 42, 'c'] },
761761
c: { array: [41.999, '42', { a: [42] }] },
762-
d: { array: [42], array2: ['bingo'] }
762+
d: { array: [42], array2: ['bingo'] },
763+
e: { array: [null] },
764+
f: { array: [Number.NaN] }
763765
};
764766

765767
await withTestCollection(persistence, testDocs, async coll => {
@@ -773,6 +775,15 @@ apiDescribe('Queries', (persistence: boolean) => {
773775

774776
// NOTE: The backend doesn't currently support null, NaN, objects, or
775777
// arrays, so there isn't much of anything else interesting to test.
778+
// With null.
779+
const snapshot3 = await coll.where('zip', 'array-contains', null).get();
780+
expect(toDataArray(snapshot3)).to.deep.equal([]);
781+
782+
// With NaN.
783+
const snapshot4 = await coll
784+
.where('zip', 'array-contains', Number.NaN)
785+
.get();
786+
expect(toDataArray(snapshot4)).to.deep.equal([]);
776787
});
777788
});
778789

@@ -784,7 +795,9 @@ apiDescribe('Queries', (persistence: boolean) => {
784795
d: { zip: [98101] },
785796
e: { zip: ['98101', { zip: 98101 }] },
786797
f: { zip: { code: 500 } },
787-
g: { zip: [98101, 98102] }
798+
g: { zip: [98101, 98102] },
799+
h: { zip: null },
800+
i: { zip: Number.NaN }
788801
};
789802

790803
await withTestCollection(persistence, testDocs, async coll => {
@@ -800,6 +813,24 @@ apiDescribe('Queries', (persistence: boolean) => {
800813
// With objects.
801814
const snapshot2 = await coll.where('zip', 'in', [{ code: 500 }]).get();
802815
expect(toDataArray(snapshot2)).to.deep.equal([{ zip: { code: 500 } }]);
816+
817+
// With null.
818+
const snapshot3 = await coll.where('zip', 'in', [null]).get();
819+
expect(toDataArray(snapshot3)).to.deep.equal([]);
820+
821+
// With null and a value.
822+
const snapshot4 = await coll.where('zip', 'in', [98101, null]).get();
823+
expect(toDataArray(snapshot4)).to.deep.equal([{ zip: 98101 }]);
824+
825+
// With NaN.
826+
const snapshot5 = await coll.where('zip', 'in', [Number.NaN]).get();
827+
expect(toDataArray(snapshot5)).to.deep.equal([]);
828+
829+
// With NaN and a value.
830+
const snapshot6 = await coll
831+
.where('zip', 'in', [98101, Number.NaN])
832+
.get();
833+
expect(toDataArray(snapshot6)).to.deep.equal([{ zip: 98101 }]);
803834
});
804835
});
805836

@@ -913,7 +944,9 @@ apiDescribe('Queries', (persistence: boolean) => {
913944
d: { array: [42], array2: ['bingo'] },
914945
e: { array: [43] },
915946
f: { array: [{ a: 42 }] },
916-
g: { array: 42 }
947+
g: { array: 42 },
948+
h: { array: [null] },
949+
i: { array: [Number.NaN] }
917950
};
918951

919952
await withTestCollection(persistence, testDocs, async coll => {
@@ -932,6 +965,30 @@ apiDescribe('Queries', (persistence: boolean) => {
932965
.where('array', 'array-contains-any', [{ a: 42 }])
933966
.get();
934967
expect(toDataArray(snapshot2)).to.deep.equal([{ array: [{ a: 42 }] }]);
968+
969+
// With null.
970+
const snapshot3 = await coll
971+
.where('array', 'array-contains-any', [null])
972+
.get();
973+
expect(toDataArray(snapshot3)).to.deep.equal([]);
974+
975+
// With null and a value.
976+
const snapshot4 = await coll
977+
.where('array', 'array-contains-any', [43, null])
978+
.get();
979+
expect(toDataArray(snapshot4)).to.deep.equal([{ array: [43] }]);
980+
981+
// With NaN.
982+
const snapshot5 = await coll
983+
.where('array', 'array-contains-any', [Number.NaN])
984+
.get();
985+
expect(toDataArray(snapshot5)).to.deep.equal([]);
986+
987+
// With NaN and a value.
988+
const snapshot6 = await coll
989+
.where('array', 'array-contains-any', [43, Number.NaN])
990+
.get();
991+
expect(toDataArray(snapshot6)).to.deep.equal([{ array: [43] }]);
935992
});
936993
});
937994

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

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -711,51 +711,6 @@ apiDescribe('Validation:', (persistence: boolean) => {
711711
);
712712
});
713713

714-
validationIt(
715-
persistence,
716-
'with null or NaN non-equality filters fail',
717-
db => {
718-
const collection = db.collection('test');
719-
expect(() => collection.where('a', '>', null)).to.throw(
720-
"Invalid query. Null only supports '==' and '!=' comparisons."
721-
);
722-
expect(() => collection.where('a', 'array-contains', null)).to.throw(
723-
"Invalid query. Null only supports '==' and '!=' comparisons."
724-
);
725-
expect(() => collection.where('a', 'in', null)).to.throw(
726-
"Invalid Query. A non-empty array is required for 'in' filters."
727-
);
728-
expect(() => collection.where('a', 'not-in', null)).to.throw(
729-
"Invalid Query. A non-empty array is required for 'not-in' filters."
730-
);
731-
expect(() =>
732-
collection.where('a', 'array-contains-any', null)
733-
).to.throw(
734-
"Invalid Query. A non-empty array is required for 'array-contains-any' filters."
735-
);
736-
737-
expect(() => collection.where('a', '>', Number.NaN)).to.throw(
738-
"Invalid query. NaN only supports '==' and '!=' comparisons."
739-
);
740-
expect(() =>
741-
collection.where('a', 'array-contains', Number.NaN)
742-
).to.throw(
743-
"Invalid query. NaN only supports '==' and '!=' comparisons."
744-
);
745-
expect(() => collection.where('a', 'in', Number.NaN)).to.throw(
746-
"Invalid Query. A non-empty array is required for 'in' filters."
747-
);
748-
expect(() => collection.where('a', 'not-in', Number.NaN)).to.throw(
749-
"Invalid Query. A non-empty array is required for 'not-in' filters."
750-
);
751-
expect(() =>
752-
collection.where('a', 'array-contains-any', Number.NaN)
753-
).to.throw(
754-
"Invalid Query. A non-empty array is required for 'array-contains-any' filters."
755-
);
756-
}
757-
);
758-
759714
it('cannot be created from documents missing sort values', () => {
760715
const testDocs = {
761716
f: { k: 'f', nosort: 1 } // should not show up
@@ -1246,34 +1201,6 @@ apiDescribe('Validation:', (persistence: boolean) => {
12461201
'Invalid Query. A non-empty array is required for ' +
12471202
"'array-contains-any' filters."
12481203
);
1249-
1250-
expect(() =>
1251-
db.collection('test').where('foo', 'in', [3, null])
1252-
).to.throw(
1253-
"Invalid Query. 'in' filters cannot contain 'null' in the value array."
1254-
);
1255-
1256-
expect(() =>
1257-
db.collection('test').where('foo', 'array-contains-any', [3, null])
1258-
).to.throw(
1259-
"Invalid Query. 'array-contains-any' filters cannot contain 'null' " +
1260-
'in the value array.'
1261-
);
1262-
1263-
expect(() =>
1264-
db.collection('test').where('foo', 'in', [2, Number.NaN])
1265-
).to.throw(
1266-
"Invalid Query. 'in' filters cannot contain 'NaN' in the value array."
1267-
);
1268-
1269-
expect(() =>
1270-
db
1271-
.collection('test')
1272-
.where('foo', 'array-contains-any', [2, Number.NaN])
1273-
).to.throw(
1274-
"Invalid Query. 'array-contains-any' filters cannot contain 'NaN' " +
1275-
'in the value array.'
1276-
);
12771204
}
12781205
);
12791206

0 commit comments

Comments
 (0)