Skip to content

Commit ed0803a

Browse files
authored
fix: remove null value inclusion from != and not-in filter results (#8915)
1 parent 4e0f630 commit ed0803a

File tree

4 files changed

+108
-3
lines changed

4 files changed

+108
-3
lines changed

.changeset/cyan-frogs-relate.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/firestore': patch
3+
'firebase': patch
4+
---
5+
6+
Fixed the `null` value handling in `!=` and `not-in` filters.

packages/firestore/src/core/filter.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export class FieldFilter extends Filter {
141141
if (this.op === Operator.NOT_EQUAL) {
142142
return (
143143
other !== null &&
144+
other.nullValue === undefined &&
144145
this.matchesComparison(valueCompare(other!, this.value))
145146
);
146147
}
@@ -495,7 +496,11 @@ export class NotInFilter extends FieldFilter {
495496
return false;
496497
}
497498
const other = doc.data.field(this.field);
498-
return other !== null && !arrayValueContains(this.value.arrayValue!, other);
499+
return (
500+
other !== null &&
501+
other.nullValue === undefined &&
502+
!arrayValueContains(this.value.arrayValue!, other)
503+
);
499504
}
500505
}
501506

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

+94
Original file line numberDiff line numberDiff line change
@@ -1751,6 +1751,100 @@ apiDescribe('Queries', persistence => {
17511751
);
17521752
});
17531753
});
1754+
1755+
it('sdk uses != filter same as backend', async () => {
1756+
const testDocs = {
1757+
a: { zip: Number.NaN },
1758+
b: { zip: 91102 },
1759+
c: { zip: 98101 },
1760+
d: { zip: '98101' },
1761+
e: { zip: [98101] },
1762+
f: { zip: [98101, 98102] },
1763+
g: { zip: ['98101', { zip: 98101 }] },
1764+
h: { zip: { code: 500 } },
1765+
i: { zip: null },
1766+
j: { code: 500 }
1767+
};
1768+
1769+
await withTestCollection(persistence, testDocs, async coll => {
1770+
// populate cache with all documents first to ensure getDocsFromCache() scans all docs
1771+
await getDocs(coll);
1772+
1773+
let testQuery = query(coll, where('zip', '!=', 98101));
1774+
await checkOnlineAndOfflineResultsMatch(
1775+
testQuery,
1776+
'a',
1777+
'b',
1778+
'd',
1779+
'e',
1780+
'f',
1781+
'g',
1782+
'h'
1783+
);
1784+
1785+
testQuery = query(coll, where('zip', '!=', Number.NaN));
1786+
await checkOnlineAndOfflineResultsMatch(
1787+
testQuery,
1788+
'b',
1789+
'c',
1790+
'd',
1791+
'e',
1792+
'f',
1793+
'g',
1794+
'h'
1795+
);
1796+
1797+
testQuery = query(coll, where('zip', '!=', null));
1798+
await checkOnlineAndOfflineResultsMatch(
1799+
testQuery,
1800+
'a',
1801+
'b',
1802+
'c',
1803+
'd',
1804+
'e',
1805+
'f',
1806+
'g',
1807+
'h'
1808+
);
1809+
});
1810+
});
1811+
1812+
it('sdk uses not-in filter same as backend', async () => {
1813+
const testDocs = {
1814+
a: { zip: Number.NaN },
1815+
b: { zip: 91102 },
1816+
c: { zip: 98101 },
1817+
d: { zip: '98101' },
1818+
e: { zip: [98101] },
1819+
f: { zip: [98101, 98102] },
1820+
g: { zip: ['98101', { zip: 98101 }] },
1821+
h: { zip: { code: 500 } },
1822+
i: { zip: null },
1823+
j: { code: 500 }
1824+
};
1825+
1826+
await withTestCollection(persistence, testDocs, async coll => {
1827+
// populate cache with all documents first to ensure getDocsFromCache() scans all docs
1828+
await getDocs(coll);
1829+
1830+
let testQuery = query(
1831+
coll,
1832+
where('zip', 'not-in', [98101, 98103, [98101, 98102]])
1833+
);
1834+
await checkOnlineAndOfflineResultsMatch(
1835+
testQuery,
1836+
'a',
1837+
'b',
1838+
'd',
1839+
'e',
1840+
'g',
1841+
'h'
1842+
);
1843+
1844+
testQuery = query(coll, where('zip', 'not-in', [null]));
1845+
await checkOnlineAndOfflineResultsMatch(testQuery);
1846+
});
1847+
});
17541848
});
17551849

17561850
// Reproduces https://github.com/firebase/firebase-js-sdk/issues/5873

packages/firestore/test/unit/core/query.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ describe('Query', () => {
256256
document = doc('collection/1', 0, {
257257
zip: null
258258
});
259-
expect(queryMatches(query1, document)).to.be.true;
259+
expect(queryMatches(query1, document)).to.be.false;
260260

261261
// NaN match.
262262
document = doc('collection/1', 0, {
@@ -354,7 +354,7 @@ describe('Query', () => {
354354
expect(queryMatches(query2, doc3)).to.equal(true);
355355
expect(queryMatches(query2, doc4)).to.equal(true);
356356
expect(queryMatches(query2, doc5)).to.equal(true);
357-
expect(queryMatches(query2, doc6)).to.equal(true);
357+
expect(queryMatches(query2, doc6)).to.equal(false);
358358
});
359359

360360
it('matches null for filters', () => {

0 commit comments

Comments
 (0)