From 89a67bee2e59768b61bdfa46964d95368c6536a7 Mon Sep 17 00:00:00 2001 From: Mark Duckworth Date: Fri, 4 Nov 2022 17:14:56 -0600 Subject: [PATCH 01/12] Relaxing in restrictions. --- packages/firestore/src/lite-api/query.ts | 10 +- .../test/integration/api/query.test.ts | 235 ++++++++++++++++++ .../test/integration/api/validation.test.ts | 52 ---- 3 files changed, 237 insertions(+), 60 deletions(-) diff --git a/packages/firestore/src/lite-api/query.ts b/packages/firestore/src/lite-api/query.ts index 0b3de2268d0..4276dd01353 100644 --- a/packages/firestore/src/lite-api/query.ts +++ b/packages/firestore/src/lite-api/query.ts @@ -1076,20 +1076,14 @@ function conflictingOps(op: Operator): Operator[] { case Operator.NOT_EQUAL: return [Operator.NOT_EQUAL, Operator.NOT_IN]; case Operator.ARRAY_CONTAINS: - return [ - Operator.ARRAY_CONTAINS, - Operator.ARRAY_CONTAINS_ANY, - Operator.NOT_IN - ]; - case Operator.IN: - return [Operator.ARRAY_CONTAINS_ANY, Operator.IN, Operator.NOT_IN]; case Operator.ARRAY_CONTAINS_ANY: return [ Operator.ARRAY_CONTAINS, Operator.ARRAY_CONTAINS_ANY, - Operator.IN, Operator.NOT_IN ]; + case Operator.IN: + return [Operator.NOT_IN]; case Operator.NOT_IN: return [ Operator.ARRAY_CONTAINS, diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index f76baf19b9f..308f923a2b2 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1614,6 +1614,241 @@ apiDescribe('Queries', (persistence: boolean) => { }); }); }); + + // TODO(orquery): Enable this test when prod supports OR queries. + // eslint-disable-next-line no-restricted-properties + it.skip('supports multiple in ops', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // Two IN operations on different fields with disjunction. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), + orderBy('a') + ), + 'doc1', + 'doc6', + 'doc3' + ); + + // Two IN operations on different fields with conjunction. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), + orderBy('a') + ), + 'doc3' + ); + + // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an + // empty set. + await checkOnlineAndOfflineResultsMatch( + query(coll, and(where('a', 'in', [2, 3]), where('a', 'in', [0, 1, 4]))) + ); + + // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]). + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', 'in', [0, 3]), where('a', 'in', [0, 2]))), + 'doc3', + 'doc6' + ); + + // Nested composite filter on the same field. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [1, 3]), + or( + where('a', 'in', [0, 2]), + and(where('b', '>=', 1), where('a', 'in', [1, 3])) + ) + ) + ), + 'doc3', + 'doc4' + ); + + // Nested composite filter on the different fields. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('b', 'in', [0, 3]), + or( + where('b', 'in', [1]), + and(where('b', 'in', [2, 3]), where('a', 'in', [1, 3])) + ) + ) + ), + 'doc4' + ); + }); + }); + + // TODO(orquery): Enable this test when prod supports OR queries. + // eslint-disable-next-line no-restricted-properties + it.skip('supports using in with array contains any', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'array-contains-any', [0, 7])) + ), + 'doc1', + 'doc3', + 'doc4', + 'doc6' + ); + }); + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc3' + ); + }); + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + where('a', 'in', [2, 3]), + where('c', '==', 10), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc1', + 'doc3', + 'doc4' + ); + }); + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + or(where('b', 'array-contains-any', [0, 7]), where('c', '==', 10)) + ) + ), + 'doc3', + 'doc6' + ); + }); + }); + + // TODO(orquery): Enable this test when prod supports OR queries. + // eslint-disable-next-line no-restricted-properties + it.skip('supports using in with array contains', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7] }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'array-contains', 3)) + ), + 'doc3', + 'doc4', + 'doc6' + ); + }); + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('b', 'array-contains', 7)) + ), + 'doc3' + ); + }); + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + where('a', 'in', [2, 3]), + and(where('b', 'array-contains', 3), where('b', '==', 1)) + ) + ), + 'doc3', + 'doc4', + 'doc6' + ); + }); + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + or(where('b', 'array-contains', 7), where('a', '==', 1)) + ) + ), + 'doc3' + ); + }); + }); + + // TODO(orquery): Enable this test when prod supports OR queries. + // eslint-disable-next-line no-restricted-properties + it.skip('supports order by equality', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), orderBy('a')), + 'doc1', + 'doc4', + 'doc5' + ); + }); + }); }); function verifyDocumentChange( diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index c118e485310..c4f4c0a60f0 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -962,14 +962,6 @@ apiDescribe('Validation:', (persistence: boolean) => { }); validationIt(persistence, 'with multiple disjunctive filters fail', db => { - expect(() => - query( - collection(db, 'test'), - where('foo', 'in', [1, 2]), - where('foo', 'in', [2, 3]) - ) - ).to.throw("Invalid query. You cannot use more than one 'in' filter."); - expect(() => query( collection(db, 'test'), @@ -991,28 +983,6 @@ apiDescribe('Validation:', (persistence: boolean) => { ' filter.' ); - expect(() => - query( - collection(db, 'test'), - where('foo', 'array-contains-any', [2, 3]), - where('foo', 'in', [2, 3]) - ) - ).to.throw( - "Invalid query. You cannot use 'in' filters with " + - "'array-contains-any' filters." - ); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'in', [2, 3]), - where('foo', 'array-contains-any', [2, 3]) - ) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with " + - "'in' filters." - ); - expect(() => query( collection(db, 'test'), @@ -1055,19 +1025,6 @@ apiDescribe('Validation:', (persistence: boolean) => { "Invalid query. You cannot use 'not-in' filters with 'in' filters." ); - // This is redundant with the above tests, but makes sure our validation - // doesn't get confused. - expect(() => - query( - collection(db, 'test'), - where('foo', 'in', [2, 3]), - where('foo', 'array-contains', 1), - where('foo', 'array-contains-any', [2]) - ) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with 'in' filters." - ); - expect(() => query( collection(db, 'test'), @@ -1135,15 +1092,6 @@ apiDescribe('Validation:', (persistence: boolean) => { ).to.throw( "Invalid query. You cannot use more than one 'array-contains' filter." ); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'array-contains', 1), - where('foo', 'in', [2, 3]), - where('foo', 'in', [2, 3]) - ) - ).to.throw("Invalid query. You cannot use more than one 'in' filter."); } ); From 8c0f205be8e51a0843d5834ca4a78fe27a763ac3 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Thu, 9 Feb 2023 14:15:34 -0700 Subject: [PATCH 02/12] Additional testing. --- .../test/integration/api/query.test.ts | 477 +++++++++--------- .../test/integration/api/validation.test.ts | 13 + 2 files changed, 255 insertions(+), 235 deletions(-) diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index 308f923a2b2..a92eef8443c 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1574,6 +1574,248 @@ apiDescribe('Queries', (persistence: boolean) => { ); }); }); + + // eslint-disable-next-line no-restricted-properties + it('supports multiple in ops', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // Two IN operations on different fields with disjunction. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), + orderBy('a') + ), + 'doc1', + 'doc6', + 'doc3' + ); + + // Two IN operations on different fields with conjunction. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), + orderBy('a') + ), + 'doc3' + ); + + // Two IN operations on the same field. + // a IN [1,2,3] && a IN [0,1,4] should result in "a==1". + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [1, 2, 3]), where('a', 'in', [0, 1, 4])) + ), + 'doc1', + 'doc4', + 'doc5' + ); + + // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an + // empty set. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('a', 'in', [0, 1, 4])) + ) + ); + + // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]). + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', 'in', [0, 3]), where('a', 'in', [0, 2]))), + 'doc3', + 'doc6' + ); + + // Nested composite filter on the same field. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [1, 3]), + or( + where('a', 'in', [0, 2]), + and(where('b', '>=', 1), where('a', 'in', [1, 3])) + ) + ) + ), + 'doc3', + 'doc4' + ); + + // Nested composite filter on the different fields. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('b', 'in', [0, 3]), + or( + where('b', 'in', [1]), + and(where('b', 'in', [2, 3]), where('a', 'in', [1, 3])) + ) + ) + ), + 'doc4' + ); + }); + }); + + // eslint-disable-next-line no-restricted-properties + it('supports using in with array contains any', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + where('a', 'in', [2, 3]), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc1', + 'doc3', + 'doc4', + 'doc6' + ); + + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc3' + ); + + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + and(where('a', 'in', [2, 3]), where('c', '==', 10)), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc1', + 'doc3', + 'doc4' + ); + + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + or(where('b', 'array-contains-any', [0, 7]), where('c', '==', 20)) + ) + ), + 'doc3', + 'doc6' + ); + }); + }); + + // eslint-disable-next-line no-restricted-properties + it('supports using in with array contains', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7] }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'array-contains', 3)) + ), + 'doc3', + 'doc4', + 'doc6' + ); + + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('b', 'array-contains', 7)) + ), + 'doc3' + ); + + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + where('a', 'in', [2, 3]), + and(where('b', 'array-contains', 3), where('a', '==', 1)) + ) + ), + 'doc3', + 'doc4', + 'doc6' + ); + + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + or(where('b', 'array-contains', 7), where('a', '==', 1)) + ) + ), + 'doc3' + ); + }); + }); + + // eslint-disable-next-line no-restricted-properties + it('supports order by equality', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), orderBy('a')), + 'doc1', + 'doc4', + 'doc5' + ); + + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', 'in', [2, 3]), orderBy('a')), + 'doc6', + 'doc3' + ); + }); + }); }); // Reproduces https://github.com/firebase/firebase-js-sdk/issues/5873 @@ -1614,241 +1856,6 @@ apiDescribe('Queries', (persistence: boolean) => { }); }); }); - - // TODO(orquery): Enable this test when prod supports OR queries. - // eslint-disable-next-line no-restricted-properties - it.skip('supports multiple in ops', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1 }, - doc6: { a: 2 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - // Two IN operations on different fields with disjunction. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), - orderBy('a') - ), - 'doc1', - 'doc6', - 'doc3' - ); - - // Two IN operations on different fields with conjunction. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), - orderBy('a') - ), - 'doc3' - ); - - // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an - // empty set. - await checkOnlineAndOfflineResultsMatch( - query(coll, and(where('a', 'in', [2, 3]), where('a', 'in', [0, 1, 4]))) - ); - - // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]). - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', 'in', [0, 3]), where('a', 'in', [0, 2]))), - 'doc3', - 'doc6' - ); - - // Nested composite filter on the same field. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [1, 3]), - or( - where('a', 'in', [0, 2]), - and(where('b', '>=', 1), where('a', 'in', [1, 3])) - ) - ) - ), - 'doc3', - 'doc4' - ); - - // Nested composite filter on the different fields. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('b', 'in', [0, 3]), - or( - where('b', 'in', [1]), - and(where('b', 'in', [2, 3]), where('a', 'in', [1, 3])) - ) - ) - ), - 'doc4' - ); - }); - }); - - // TODO(orquery): Enable this test when prod supports OR queries. - // eslint-disable-next-line no-restricted-properties - it.skip('supports using in with array contains any', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7], c: 10 }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2, c: 20 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', 'in', [2, 3]), where('b', 'array-contains-any', [0, 7])) - ), - 'doc1', - 'doc3', - 'doc4', - 'doc6' - ); - }); - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc3' - ); - }); - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or( - where('a', 'in', [2, 3]), - where('c', '==', 10), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc1', - 'doc3', - 'doc4' - ); - }); - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - or(where('b', 'array-contains-any', [0, 7]), where('c', '==', 10)) - ) - ), - 'doc3', - 'doc6' - ); - }); - }); - - // TODO(orquery): Enable this test when prod supports OR queries. - // eslint-disable-next-line no-restricted-properties - it.skip('supports using in with array contains', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7] }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', 'in', [2, 3]), where('b', 'array-contains', 3)) - ), - 'doc3', - 'doc4', - 'doc6' - ); - }); - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('b', 'array-contains', 7)) - ), - 'doc3' - ); - }); - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or( - where('a', 'in', [2, 3]), - and(where('b', 'array-contains', 3), where('b', '==', 1)) - ) - ), - 'doc3', - 'doc4', - 'doc6' - ); - }); - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - or(where('b', 'array-contains', 7), where('a', '==', 1)) - ) - ), - 'doc3' - ); - }); - }); - - // TODO(orquery): Enable this test when prod supports OR queries. - // eslint-disable-next-line no-restricted-properties - it.skip('supports order by equality', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7], c: 10 }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2, c: 20 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', '==', 1), orderBy('a')), - 'doc1', - 'doc4', - 'doc5' - ); - }); - }); }); function verifyDocumentChange( diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index c4f4c0a60f0..05b4c4fe773 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -1025,6 +1025,19 @@ apiDescribe('Validation:', (persistence: boolean) => { "Invalid query. You cannot use 'not-in' filters with 'in' filters." ); + // This is redundant with the above tests, but makes sure our validation + // doesn't get confused. + expect(() => + query( + collection(db, 'test'), + where('foo', 'in', [2, 3]), + where('foo', 'array-contains', 1), + where('foo', 'array-contains-any', [2]) + ) + ).to.throw( + "Invalid query. You cannot use 'array-contains-any' filters with 'array-contains' filters." + ); + expect(() => query( collection(db, 'test'), From 4e7c5afd867ba341cb2a896c4332c8acad655267 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:00:11 -0700 Subject: [PATCH 03/12] Relaxing query restrictions. --- packages/firestore/src/core/query.ts | 7 - packages/firestore/src/lite-api/query.ts | 26 +--- .../test/integration/api/validation.test.ts | 141 ------------------ 3 files changed, 5 insertions(+), 169 deletions(-) diff --git a/packages/firestore/src/core/query.ts b/packages/firestore/src/core/query.ts index 310f7f6ac4b..cb76205bb98 100644 --- a/packages/firestore/src/core/query.ts +++ b/packages/firestore/src/core/query.ts @@ -165,13 +165,6 @@ export function queryMatchesAllDocuments(query: Query): boolean { ); } -export function queryContainsCompositeFilters(query: Query): boolean { - return ( - query.filters.find(filter => filter instanceof CompositeFilter) !== - undefined - ); -} - export function getFirstOrderByField(query: Query): FieldPath | null { return query.explicitOrderBy.length > 0 ? query.explicitOrderBy[0].field diff --git a/packages/firestore/src/lite-api/query.ts b/packages/firestore/src/lite-api/query.ts index 4276dd01353..30e88927742 100644 --- a/packages/firestore/src/lite-api/query.ts +++ b/packages/firestore/src/lite-api/query.ts @@ -1050,43 +1050,27 @@ function validateDisjunctiveFilterElements( `'${operator.toString()}' filters.` ); } - if (value.length > 10) { - throw new FirestoreError( - Code.INVALID_ARGUMENT, - `Invalid Query. '${operator.toString()}' filters support a ` + - 'maximum of 10 elements in the value array.' - ); - } } /** * Given an operator, returns the set of operators that cannot be used with it. * - * Operators in a query must adhere to the following set of rules: - * 1. Only one array operator is allowed. - * 2. Only one disjunctive operator is allowed. - * 3. `NOT_EQUAL` cannot be used with another `NOT_EQUAL` operator. - * 4. `NOT_IN` cannot be used with array, disjunctive, or `NOT_EQUAL` operators. + * This is not a comprehensive check, and this function should be removed in the + * long term. Validations should occur in the Firestore backend. * - * Array operators: `ARRAY_CONTAINS`, `ARRAY_CONTAINS_ANY` - * Disjunctive operators: `IN`, `ARRAY_CONTAINS_ANY`, `NOT_IN` + * Operators in a query must adhere to the following set of rules: + * 1. Only one inequality per query. + * 2. `NOT_IN` cannot be used with array, disjunctive, or `NOT_EQUAL` operators. */ function conflictingOps(op: Operator): Operator[] { switch (op) { case Operator.NOT_EQUAL: return [Operator.NOT_EQUAL, Operator.NOT_IN]; - case Operator.ARRAY_CONTAINS: case Operator.ARRAY_CONTAINS_ANY: - return [ - Operator.ARRAY_CONTAINS, - Operator.ARRAY_CONTAINS_ANY, - Operator.NOT_IN - ]; case Operator.IN: return [Operator.NOT_IN]; case Operator.NOT_IN: return [ - Operator.ARRAY_CONTAINS, Operator.ARRAY_CONTAINS_ANY, Operator.IN, Operator.NOT_IN, diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index 05b4c4fe773..8ec14d1ae3e 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -894,51 +894,6 @@ apiDescribe('Validation:', (persistence: boolean) => { } ); - validationIt(persistence, 'with multiple array filters fail', db => { - expect(() => - query( - collection(db, 'test'), - where('foo', 'array-contains', 1), - where('foo', 'array-contains', 2) - ) - ).to.throw( - "Invalid query. You cannot use more than one 'array-contains' filter." - ); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'array-contains', 1), - where('foo', 'array-contains-any', [2, 3]) - ) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with " + - "'array-contains' filters." - ); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'array-contains-any', [2, 3]), - where('foo', 'array-contains', 1) - ) - ).to.throw( - "Invalid query. You cannot use 'array-contains' filters with " + - "'array-contains-any' filters." - ); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'not-in', [2, 3]), - where('foo', 'array-contains', 1) - ) - ).to.throw( - "Invalid query. You cannot use 'array-contains' filters with " + - "'not-in' filters." - ); - }); - validationIt(persistence, 'with != and not-in filters fail', db => { expect(() => query( @@ -972,17 +927,6 @@ apiDescribe('Validation:', (persistence: boolean) => { "Invalid query. You cannot use more than one 'not-in' filter." ); - expect(() => - query( - collection(db, 'test'), - where('foo', 'array-contains-any', [1, 2]), - where('foo', 'array-contains-any', [2, 3]) - ) - ).to.throw( - "Invalid query. You cannot use more than one 'array-contains-any'" + - ' filter.' - ); - expect(() => query( collection(db, 'test'), @@ -1024,55 +968,6 @@ apiDescribe('Validation:', (persistence: boolean) => { ).to.throw( "Invalid query. You cannot use 'not-in' filters with 'in' filters." ); - - // This is redundant with the above tests, but makes sure our validation - // doesn't get confused. - expect(() => - query( - collection(db, 'test'), - where('foo', 'in', [2, 3]), - where('foo', 'array-contains', 1), - where('foo', 'array-contains-any', [2]) - ) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with 'array-contains' filters." - ); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'array-contains', 1), - where('foo', 'in', [2, 3]), - where('foo', 'array-contains-any', [2]) - ) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with " + - "'array-contains' filters." - ); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'not-in', [2, 3]), - where('foo', 'array-contains', 2), - where('foo', 'array-contains-any', [2]) - ) - ).to.throw( - "Invalid query. You cannot use 'array-contains' filters with " + - "'not-in' filters." - ); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'array-contains', 2), - where('foo', 'in', [2]), - where('foo', 'not-in', [2, 3]) - ) - ).to.throw( - "Invalid query. You cannot use 'not-in' filters with " + - "'array-contains' filters." - ); }); validationIt( @@ -1094,17 +989,6 @@ apiDescribe('Validation:', (persistence: boolean) => { where('foo', 'array-contains', 1) ) ).not.to.throw(); - - expect(() => - query( - collection(db, 'test'), - where('foo', 'in', [2, 3]), - where('foo', 'array-contains', 1), - where('foo', 'array-contains', 2) - ) - ).to.throw( - "Invalid query. You cannot use more than one 'array-contains' filter." - ); } ); @@ -1125,31 +1009,6 @@ apiDescribe('Validation:', (persistence: boolean) => { "'array-contains-any' filters." ); - expect(() => - query( - collection(db, 'test'), - // The 10 element max includes duplicates. - where('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9]) - ) - ).to.throw( - "Invalid Query. 'in' filters support a maximum of 10 elements in " + - 'the value array.' - ); - - expect(() => - query( - collection(db, 'test'), - where( - 'foo', - 'array-contains-any', - [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9] - ) - ) - ).to.throw( - "Invalid Query. 'array-contains-any' filters support a maximum of " + - '10 elements in the value array.' - ); - expect(() => query(collection(db, 'test'), where('foo', 'in', [])) ).to.throw( From 88a32a8bda9f22d722a9a4572024e3f549aa2c1e Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:19:54 -0700 Subject: [PATCH 04/12] Code cleanup. --- packages/firestore/src/core/query.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firestore/src/core/query.ts b/packages/firestore/src/core/query.ts index cb76205bb98..9045a470e2c 100644 --- a/packages/firestore/src/core/query.ts +++ b/packages/firestore/src/core/query.ts @@ -25,7 +25,7 @@ import { boundSortsAfterDocument, boundSortsBeforeDocument } from './bound'; -import { CompositeFilter, Filter } from './filter'; +import { Filter } from './filter'; import { Direction, OrderBy } from './order_by'; import { canonifyTarget, From 045c66480d154a8e0cd2145fd41eb71944b2d620 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:35:34 -0700 Subject: [PATCH 05/12] Removing compat tests that perform validation on the old query restrictions. --- .../firestore-compat/test/validation.test.ts | 164 ------------------ 1 file changed, 164 deletions(-) diff --git a/packages/firestore-compat/test/validation.test.ts b/packages/firestore-compat/test/validation.test.ts index 26d45888193..0b94df7950e 100644 --- a/packages/firestore-compat/test/validation.test.ts +++ b/packages/firestore-compat/test/validation.test.ts @@ -901,47 +901,6 @@ apiDescribe('Validation:', (persistence: boolean) => { } ); - validationIt(persistence, 'with multiple array filters fail', db => { - expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'array-contains', 2) - ).to.throw( - "Invalid query. You cannot use more than one 'array-contains' filter." - ); - - expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'array-contains-any', [2, 3]) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with " + - "'array-contains' filters." - ); - - expect(() => - db - .collection('test') - .where('foo', 'array-contains-any', [2, 3]) - .where('foo', 'array-contains', 1) - ).to.throw( - "Invalid query. You cannot use 'array-contains' filters with " + - "'array-contains-any' filters." - ); - - expect(() => - db - .collection('test') - .where('foo', 'not-in', [2, 3]) - .where('foo', 'array-contains', 1) - ).to.throw( - "Invalid query. You cannot use 'array-contains' filters with " + - "'not-in' filters." - ); - }); - validationIt(persistence, 'with != and not-in filters fail', db => { expect(() => db @@ -963,13 +922,6 @@ apiDescribe('Validation:', (persistence: boolean) => { }); validationIt(persistence, 'with multiple disjunctive filters fail', db => { - expect(() => - db - .collection('test') - .where('foo', 'in', [1, 2]) - .where('foo', 'in', [2, 3]) - ).to.throw("Invalid query. You cannot use more than one 'in' filter."); - expect(() => db .collection('test') @@ -979,36 +931,6 @@ apiDescribe('Validation:', (persistence: boolean) => { "Invalid query. You cannot use more than one 'not-in' filter." ); - expect(() => - db - .collection('test') - .where('foo', 'array-contains-any', [1, 2]) - .where('foo', 'array-contains-any', [2, 3]) - ).to.throw( - "Invalid query. You cannot use more than one 'array-contains-any'" + - ' filter.' - ); - - expect(() => - db - .collection('test') - .where('foo', 'array-contains-any', [2, 3]) - .where('foo', 'in', [2, 3]) - ).to.throw( - "Invalid query. You cannot use 'in' filters with " + - "'array-contains-any' filters." - ); - - expect(() => - db - .collection('test') - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains-any', [2, 3]) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with " + - "'in' filters." - ); - expect(() => db .collection('test') @@ -1046,51 +968,6 @@ apiDescribe('Validation:', (persistence: boolean) => { ).to.throw( "Invalid query. You cannot use 'not-in' filters with 'in' filters." ); - - // This is redundant with the above tests, but makes sure our validation - // doesn't get confused. - expect(() => - db - .collection('test') - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains', 1) - .where('foo', 'array-contains-any', [2]) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with 'in' filters." - ); - - expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains-any', [2]) - ).to.throw( - "Invalid query. You cannot use 'array-contains-any' filters with " + - "'array-contains' filters." - ); - - expect(() => - db - .collection('test') - .where('foo', 'not-in', [2, 3]) - .where('foo', 'array-contains', 2) - .where('foo', 'array-contains-any', [2]) - ).to.throw( - "Invalid query. You cannot use 'array-contains' filters with " + - "'not-in' filters." - ); - - expect(() => - db - .collection('test') - .where('foo', 'array-contains', 2) - .where('foo', 'in', [2]) - .where('foo', 'not-in', [2, 3]) - ).to.throw( - "Invalid query. You cannot use 'not-in' filters with " + - "'array-contains' filters." - ); }); validationIt( @@ -1110,24 +987,6 @@ apiDescribe('Validation:', (persistence: boolean) => { .where('foo', 'in', [2, 3]) .where('foo', 'array-contains', 1) ).not.to.throw(); - - expect(() => - db - .collection('test') - .where('foo', 'in', [2, 3]) - .where('foo', 'array-contains', 1) - .where('foo', 'array-contains', 2) - ).to.throw( - "Invalid query. You cannot use more than one 'array-contains' filter." - ); - - expect(() => - db - .collection('test') - .where('foo', 'array-contains', 1) - .where('foo', 'in', [2, 3]) - .where('foo', 'in', [2, 3]) - ).to.throw("Invalid query. You cannot use more than one 'in' filter."); } ); @@ -1146,29 +1005,6 @@ apiDescribe('Validation:', (persistence: boolean) => { "'array-contains-any' filters." ); - expect(() => - db - .collection('test') - // The 10 element max includes duplicates. - .where('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9]) - ).to.throw( - "Invalid Query. 'in' filters support a maximum of 10 elements in " + - 'the value array.' - ); - - expect(() => - db - .collection('test') - .where( - 'foo', - 'array-contains-any', - [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9] - ) - ).to.throw( - "Invalid Query. 'array-contains-any' filters support a maximum of " + - '10 elements in the value array.' - ); - expect(() => db.collection('test').where('foo', 'in', [])).to.throw( "Invalid Query. A non-empty array is required for 'in' filters." ); From d2086a28265eeb9f7116dae54e9aa6fb1718e46b Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:49:50 -0700 Subject: [PATCH 06/12] Making OR Queries public. --- common/api-review/firestore-lite.api.md | 17 +++++++++++++++++ common/api-review/firestore.api.md | 17 +++++++++++++++++ packages/firestore/src/lite-api/query.ts | 5 ----- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 331b9dd2f36..10f3c3723d1 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -46,6 +46,9 @@ export type AggregateSpecData = { [P in keyof T]: T[P] extends AggregateField ? U : never; }; +// @public +export function and(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; + // @public export function arrayRemove(...elements: unknown[]): FieldValue; @@ -234,6 +237,9 @@ export type NestedUpdateFields> = UnionToInter [K in keyof T & string]: ChildUpdateFields; }[keyof T & string]>; +// @public +export function or(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; + // @public export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryOrderByConstraint; @@ -258,9 +264,17 @@ export class Query { withConverter(converter: FirestoreDataConverter): Query; } +// @public +export function query(query: Query, compositeFilter: QueryCompositeFilterConstraint, ...queryConstraints: QueryNonFilterConstraint[]): Query; + // @public export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; +// @public +export class QueryCompositeFilterConstraint { + readonly type: 'or' | 'and'; +} + // @public export abstract class QueryConstraint { abstract readonly type: QueryConstraintType; @@ -288,6 +302,9 @@ export class QueryFieldFilterConstraint extends QueryConstraint { readonly type = "where"; } +// @public +export type QueryFilterConstraint = QueryFieldFilterConstraint | QueryCompositeFilterConstraint; + // @public export class QueryLimitConstraint extends QueryConstraint { readonly type: 'limit' | 'limitToLast'; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 5dd5742b318..4d24f4376b3 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -46,6 +46,9 @@ export type AggregateSpecData = { [P in keyof T]: T[P] extends AggregateField ? U : never; }; +// @public +export function and(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; + // @public export function arrayRemove(...elements: unknown[]): FieldValue; @@ -382,6 +385,9 @@ export function onSnapshotsInSync(firestore: Firestore, observer: { // @public export function onSnapshotsInSync(firestore: Firestore, onSync: () => void): Unsubscribe; +// @public +export function or(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; + // @public export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryOrderByConstraint; @@ -411,9 +417,17 @@ export class Query { withConverter(converter: FirestoreDataConverter): Query; } +// @public +export function query(query: Query, compositeFilter: QueryCompositeFilterConstraint, ...queryConstraints: QueryNonFilterConstraint[]): Query; + // @public export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; +// @public +export class QueryCompositeFilterConstraint { + readonly type: 'or' | 'and'; +} + // @public export abstract class QueryConstraint { abstract readonly type: QueryConstraintType; @@ -441,6 +455,9 @@ export class QueryFieldFilterConstraint extends QueryConstraint { readonly type = "where"; } +// @public +export type QueryFilterConstraint = QueryFieldFilterConstraint | QueryCompositeFilterConstraint; + // @public export class QueryLimitConstraint extends QueryConstraint { readonly type: 'limit' | 'limitToLast'; diff --git a/packages/firestore/src/lite-api/query.ts b/packages/firestore/src/lite-api/query.ts index 30e88927742..078b3a6e91b 100644 --- a/packages/firestore/src/lite-api/query.ts +++ b/packages/firestore/src/lite-api/query.ts @@ -130,7 +130,6 @@ export abstract class QueryConstraint extends AppliableConstraint { * apply (e.g. {@link orderBy}, {@link limit}). * @throws if any of the provided query constraints cannot be combined with the * existing or new constraints. - * @internal TODO remove this internal tag with OR Query support in the server */ export function query( query: Query, @@ -276,7 +275,6 @@ export function where( * `QueryCompositeFilterConstraint`s are created by invoking {@link or} or * {@link and} and can then be passed to {@link query} to create a new query * instance that also contains the `QueryCompositeFilterConstraint`. - * @internal TODO remove this internal tag with OR Query support in the server */ export class QueryCompositeFilterConstraint extends AppliableConstraint { /** @@ -357,7 +355,6 @@ export type QueryNonFilterConstraint = * `QueryFilterConstraint`s are created by invoking {@link or} or {@link and} * and can then be passed to {@link query} to create a new query instance that * also contains the `QueryConstraint`. - * @internal TODO remove this internal tag with OR Query support in the server */ export type QueryFilterConstraint = | QueryFieldFilterConstraint @@ -372,7 +369,6 @@ export type QueryFilterConstraint = * {@link QueryFilterConstraint}s to perform a disjunction for. These must be * created with calls to {@link where}, {@link or}, or {@link and}. * @returns The newly created {@link QueryCompositeFilterConstraint}. - * @internal TODO remove this internal tag with OR Query support in the server */ export function or( ...queryConstraints: QueryFilterConstraint[] @@ -397,7 +393,6 @@ export function or( * {@link QueryFilterConstraint}s to perform a conjunction for. These must be * created with calls to {@link where}, {@link or}, or {@link and}. * @returns The newly created {@link QueryCompositeFilterConstraint}. - * @internal TODO remove this internal tag with OR Query support in the server */ export function and( ...queryConstraints: QueryFilterConstraint[] From 933869a43598a262421df96747ab46bbd50e4172 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:24:14 -0700 Subject: [PATCH 07/12] Enabling tests for or queries and configuring tests that require composite indexes to only run against the emulator. --- .../test/integration/api/query.test.ts | 201 +++++++++++------- 1 file changed, 119 insertions(+), 82 deletions(-) diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index a92eef8443c..afe4ca53e6e 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1329,9 +1329,10 @@ apiDescribe('Queries', (persistence: boolean) => { }); }); - // TODO(orquery): Enable these tests when prod supports OR queries. + // OR Query tests only run when the SDK is configured for persistence + // because they validate that the result from server and cache match. // eslint-disable-next-line no-restricted-properties - (false && persistence ? describe : describe.skip)('OR Queries', () => { + (persistence ? describe : describe.skip)('OR Queries', () => { it('can use query overloads', () => { const testDocs = { doc1: { a: 1, b: 0 }, @@ -1369,13 +1370,6 @@ apiDescribe('Queries', (persistence: boolean) => { 'doc4' ); - // a == 1, limit 2, b - desc - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', '==', 1), limit(2), orderBy('b', 'desc')), - 'doc4', - 'doc5' - ); - // explicit OR: a == 1 || b == 1 with limit 2 await checkOnlineAndOfflineResultsMatch( query(coll, or(where('a', '==', 1), where('b', '==', 1)), limit(2)), @@ -1418,14 +1412,6 @@ apiDescribe('Queries', (persistence: boolean) => { 'doc5' ); - // with one inequality: a>2 || b==1. - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '>', 2), where('b', '==', 1))), - 'doc5', - 'doc2', - 'doc3' - ); - // (a==1 && b==0) || (a==3 && b==2) await checkOnlineAndOfflineResultsMatch( query( @@ -1464,48 +1450,6 @@ apiDescribe('Queries', (persistence: boolean) => { 'doc3' ); - // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '==', 1), where('b', '>', 0)), limit(2)), - 'doc1', - 'doc2' - ); - - // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 - // Note: The public query API does not allow implicit ordering when limitToLast is used. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 1), where('b', '>', 0)), - limitToLast(2), - orderBy('b') - ), - 'doc3', - 'doc4' - ); - - // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 2), where('b', '==', 1)), - limit(1), - orderBy('a') - ), - 'doc5' - ); - - // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1 - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 2), where('b', '==', 1)), - limitToLast(1), - orderBy('a') - ), - 'doc2' - ); - // Test with limits without orderBy (the __name__ ordering is the tie breaker). await checkOnlineAndOfflineResultsMatch( query(coll, or(where('a', '==', 2), where('b', '==', 1)), limit(1)), @@ -1532,14 +1476,6 @@ apiDescribe('Queries', (persistence: boolean) => { 'doc4', 'doc6' ); - - // a==2 || b not-in [2,3] - // Has implicit orderBy b. - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '==', 2), where('b', 'not-in', [2, 3]))), - 'doc1', - 'doc2' - ); }); }); @@ -1589,23 +1525,15 @@ apiDescribe('Queries', (persistence: boolean) => { return withTestCollection(persistence, testDocs, async coll => { // Two IN operations on different fields with disjunction. await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), - orderBy('a') - ), + query(coll, or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2]))), 'doc1', - 'doc6', - 'doc3' + 'doc3', + 'doc6' ); // Two IN operations on different fields with conjunction. await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), - orderBy('a') - ), + query(coll, and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2]))), 'doc3' ); @@ -1645,12 +1573,11 @@ apiDescribe('Queries', (persistence: boolean) => { where('a', 'in', [1, 3]), or( where('a', 'in', [0, 2]), - and(where('b', '>=', 1), where('a', 'in', [1, 3])) + and(where('b', '==', 2), where('a', 'in', [1, 3])) ) ) ), - 'doc3', - 'doc4' + 'doc3' ); // Nested composite filter on the different fields. @@ -1789,6 +1716,116 @@ apiDescribe('Queries', (persistence: boolean) => { ); }); }); + }); + + // OR Query tests only run when the SDK is configured for persistence + // because they validate that the result from server and cache match + // Additionally these tests must be skipped if running against production + // because it results in a 'missing index' error. The Firestore Emulator, + // however, does serve these queries. + // eslint-disable-next-line no-restricted-properties + (persistence && USE_EMULATOR ? describe : describe.skip)('OR Queries', () => { + it('can use query overloads', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { a: 2, b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1, b: 1 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // a == 1, limit 2, b - desc + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), limit(2), orderBy('b', 'desc')), + 'doc4', + 'doc5' + ); + }); + }); + + it('can use or queries', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { a: 2, b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1, b: 1 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // with one inequality: a>2 || b==1. + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '>', 2), where('b', '==', 1))), + 'doc5', + 'doc2', + 'doc3' + ); + + // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '==', 1), where('b', '>', 0)), limit(2)), + 'doc1', + 'doc2' + ); + + // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 + // Note: The public query API does not allow implicit ordering when limitToLast is used. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 1), where('b', '>', 0)), + limitToLast(2), + orderBy('b') + ), + 'doc3', + 'doc4' + ); + + // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 2), where('b', '==', 1)), + limit(1), + orderBy('a') + ), + 'doc5' + ); + + // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1 + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 2), where('b', '==', 1)), + limitToLast(1), + orderBy('a') + ), + 'doc2' + ); + }); + }); + + it('can use or queries with in and not-in', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // a==2 || b not-in [2,3] + // Has implicit orderBy b. + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '==', 2), where('b', 'not-in', [2, 3]))), + 'doc1', + 'doc2' + ); + }); + }); // eslint-disable-next-line no-restricted-properties it('supports order by equality', () => { From 0aa527305de3e4846b5f44bd57697e695b004013 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:40:13 -0700 Subject: [PATCH 08/12] Create sweet-rats-compete.md --- .changeset/sweet-rats-compete.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sweet-rats-compete.md diff --git a/.changeset/sweet-rats-compete.md b/.changeset/sweet-rats-compete.md new file mode 100644 index 00000000000..a48062ea92e --- /dev/null +++ b/.changeset/sweet-rats-compete.md @@ -0,0 +1,5 @@ +--- +"@firebase/firestore": feat +--- + +OR Query public API From f101d95bb0ae609845eeceb66f048a479fb2a38c Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 22 Feb 2023 16:00:53 -0700 Subject: [PATCH 09/12] Fixing documentation errors revealed by the doc change check. --- docs-devsite/firestore_.md | 92 ++++++++++++++++++- ...restore_.querycompositefilterconstraint.md | 35 +++++++ docs-devsite/firestore_.queryconstraint.md | 2 +- docs-devsite/firestore_lite.md | 92 ++++++++++++++++++- ...ore_lite.querycompositefilterconstraint.md | 35 +++++++ .../firestore_lite.queryconstraint.md | 2 +- packages/firestore/src/lite-api/query.ts | 25 +++-- packages/firestore/src/lite-api/reference.ts | 2 +- 8 files changed, 262 insertions(+), 23 deletions(-) create mode 100644 docs-devsite/firestore_.querycompositefilterconstraint.md create mode 100644 docs-devsite/firestore_lite.querycompositefilterconstraint.md diff --git a/docs-devsite/firestore_.md b/docs-devsite/firestore_.md index 058cdd7b281..9351f162119 100644 --- a/docs-devsite/firestore_.md +++ b/docs-devsite/firestore_.md @@ -75,7 +75,11 @@ https://github.com/firebase/firebase-js-sdk | [onSnapshot(query, options, observer)](./firestore_.md#onsnapshot) | Attaches a listener for QuerySnapshot events. You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | | [onSnapshot(query, onNext, onError, onCompletion)](./firestore_.md#onsnapshot) | Attaches a listener for QuerySnapshot events. You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | | [onSnapshot(query, options, onNext, onError, onCompletion)](./firestore_.md#onsnapshot) | Attaches a listener for QuerySnapshot events. You may either pass individual onNext and onError callbacks or pass a single observer object with next and error callbacks. The listener can be cancelled by calling the function that is returned when onSnapshot is called.NOTE: Although an onCompletion callback can be provided, it will never be called because the snapshot stream is never-ending. | +| [query(query, compositeFilter, queryConstraints)](./firestore_.md#query) | Creates a new immutable instance of [Query](./firestore_.query.md#query_class) that is extended to also include additional query constraints. | | [query(query, queryConstraints)](./firestore_.md#query) | Creates a new immutable instance of [Query](./firestore_.query.md#query_class) that is extended to also include additional query constraints. | +| function(queryConstraints...) | +| [and(queryConstraints)](./firestore_.md#and) | Creates a new [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) that is a conjunction of the given filter constraints. A conjunction filter includes a document if it satisfies all of the given filters. | +| [or(queryConstraints)](./firestore_.md#or) | Creates a new [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) that is a disjunction of the given filter constraints. A disjunction filter includes a document if it satisfies any of the given filters. | | function(reference...) | | [addDoc(reference, data)](./firestore_.md#adddoc) | Add a new document to specified CollectionReference with the given data, assigning it a document ID automatically. | | [collection(reference, path, pathSegments)](./firestore_.md#collection) | Gets a CollectionReference instance that refers to a subcollection of reference at the the specified relative path. | @@ -117,7 +121,8 @@ https://github.com/firebase/firebase-js-sdk | [GeoPoint](./firestore_.geopoint.md#geopoint_class) | An immutable object representing a geographic location in Firestore. The location is represented as latitude/longitude pair.Latitude values are in the range of \[-90, 90\]. Longitude values are in the range of \[-180, 180\]. | | [LoadBundleTask](./firestore_.loadbundletask.md#loadbundletask_class) | Represents the task of loading a Firestore bundle. It provides progress of bundle loading, as well as task completion and error events.The API is compatible with Promise<LoadBundleTaskProgress>. | | [Query](./firestore_.query.md#query_class) | A Query refers to a query which you can read or listen to. You can also construct refined Query objects by adding filters and ordering. | -| [QueryConstraint](./firestore_.queryconstraint.md#queryconstraint_class) | A QueryConstraint is used to narrow the set of documents returned by a Firestore query. QueryConstraints are created by invoking [where()](./firestore_.md#where), [orderBy()](./firestore_.md#orderby), , , , , [limit()](./firestore_.md#limit), [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this QueryConstraint. | +| [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) | A QueryCompositeFilterConstraint is used to narrow the set of documents returned by a Firestore query by performing the logical OR or AND of multiple [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class)s or [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class)s. QueryCompositeFilterConstraints are created by invoking [or()](./firestore_.md#or) or [and()](./firestore_.md#and) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the QueryCompositeFilterConstraint. | +| [QueryConstraint](./firestore_.queryconstraint.md#queryconstraint_class) | A QueryConstraint is used to narrow the set of documents returned by a Firestore query. QueryConstraints are created by invoking [where()](./firestore_.md#where), [orderBy()](./firestore_.md#orderby), [startAt()](./firestore_.md#startat), [startAfter()](./firestore_.md#startafter), [endBefore()](./firestore_.md#endbefore), [endAt()](./firestore_.md#endat), [limit()](./firestore_.md#limit), [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this QueryConstraint. | | [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md#querydocumentsnapshot_class) | A QueryDocumentSnapshot contains data read from a document in your Firestore database as part of a query. The document is guaranteed to exist and its data can be extracted with .data() or .get(<field>) to get a specific field.A QueryDocumentSnapshot offers the same API surface as a DocumentSnapshot. Since query results contain only existing documents, the exists property will always be true and data() will never return 'undefined'. | | [QueryEndAtConstraint](./firestore_.queryendatconstraint.md#queryendatconstraint_class) | A QueryEndAtConstraint is used to exclude documents from the end of a result set returned by a Firestore query. QueryEndAtConstraints are created by invoking [endAt()](./firestore_.md#endat) or [endBefore()](./firestore_.md#endbefore) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this QueryEndAtConstraint. | | [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class) | A QueryFieldFilterConstraint is used to narrow the set of documents returned by a Firestore query by filtering on one or more document fields. QueryFieldFilterConstraints are created by invoking [where()](./firestore_.md#where) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this QueryFieldFilterConstraint. | @@ -170,7 +175,8 @@ https://github.com/firebase/firebase-js-sdk | [PartialWithFieldValue](./firestore_.md#partialwithfieldvalue) | Similar to Typescript's Partial<T>, but allows nested fields to be omitted and FieldValues to be passed in as property values. | | [Primitive](./firestore_.md#primitive) | Primitive types. | | [QueryConstraintType](./firestore_.md#queryconstrainttype) | Describes the different query constraints available in this SDK. | -| [QueryNonFilterConstraint](./firestore_.md#querynonfilterconstraint) | QueryNonFilterConstraint is a helper union type that represents QueryConstraints which are used to narrow or order the set of documents, but that do not explicitly filter on a document field. QueryNonFilterConstraints are created by invoking [orderBy()](./firestore_.md#orderby), , , , , [limit()](./firestore_.md#limit) or [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the QueryConstraint. | +| [QueryFilterConstraint](./firestore_.md#queryfilterconstraint) | QueryFilterConstraint is a helper union type that represents [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class) and [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class). | +| [QueryNonFilterConstraint](./firestore_.md#querynonfilterconstraint) | QueryNonFilterConstraint is a helper union type that represents QueryConstraints which are used to narrow or order the set of documents, but that do not explicitly filter on a document field. QueryNonFilterConstraints are created by invoking [orderBy()](./firestore_.md#orderby), [startAt()](./firestore_.md#startat), [startAfter()](./firestore_.md#startafter), [endBefore()](./firestore_.md#endbefore), [endAt()](./firestore_.md#endat), [limit()](./firestore_.md#limit) or [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the QueryConstraint. | | [SetOptions](./firestore_.md#setoptions) | An options object that configures the behavior of [setDoc()](./firestore_lite.md#setdoc), and calls. These calls can be configured to perform granular merges instead of overwriting the target documents in their entirety by providing a SetOptions with merge: true. | | [TaskState](./firestore_.md#taskstate) | Represents the state of bundle loading tasks.Both 'Error' and 'Success' are sinking state: task will abort or complete and there will be no more updates after they are reported. | | [UnionToIntersection](./firestore_.md#uniontointersection) | Given a union type U = T1 | T2 | ..., returns an intersected type (T1 & T2 & ...).Uses distributive conditional types and inference from conditional types. This works because multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred. https://www.typescriptlang.org/docs/handbook/advanced-types.html\#type-inference-in-conditional-types https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type | @@ -1374,6 +1380,32 @@ Creates a new immutable instance of [Query](./firestore_.query.md#query_class) t Signature: +```typescript +export declare function query(query: Query, compositeFilter: QueryCompositeFilterConstraint, ...queryConstraints: QueryNonFilterConstraint[]): Query; +``` + +### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| query | [Query](./firestore_.query.md#query_class)<T> | The [Query](./firestore_.query.md#query_class) instance to use as a base for the new constraints. | +| compositeFilter | [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) | The [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) to apply. Create [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) using [and()](./firestore_.md#and) or [or()](./firestore_.md#or). | +| queryConstraints | [QueryNonFilterConstraint](./firestore_.md#querynonfilterconstraint)\[\] | Additional [QueryNonFilterConstraint](./firestore_.md#querynonfilterconstraint)s to apply (e.g. [orderBy()](./firestore_.md#orderby), [limit()](./firestore_.md#limit)). | + +Returns: + +[Query](./firestore_.query.md#query_class)<T> + +## Exceptions + +if any of the provided query constraints cannot be combined with the existing or new constraints. + +## query() + +Creates a new immutable instance of [Query](./firestore_.query.md#query_class) that is extended to also include additional query constraints. + +Signature: + ```typescript export declare function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; ``` @@ -1393,6 +1425,50 @@ export declare function query(query: Query, ...queryConstraints: QueryCons if any of the provided query constraints cannot be combined with the existing or new constraints. +## and() + +Creates a new [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) that is a conjunction of the given filter constraints. A conjunction filter includes a document if it satisfies all of the given filters. + +Signature: + +```typescript +export declare function and(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; +``` + +### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryConstraints | [QueryFilterConstraint](./firestore_.md#queryfilterconstraint)\[\] | Optional. The list of [QueryFilterConstraint](./firestore_.md#queryfilterconstraint)s to perform a conjunction for. These must be created with calls to [where()](./firestore_.md#where), [or()](./firestore_.md#or), or [and()](./firestore_.md#and). | + +Returns: + +[QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) + +The newly created [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class). + +## or() + +Creates a new [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) that is a disjunction of the given filter constraints. A disjunction filter includes a document if it satisfies any of the given filters. + +Signature: + +```typescript +export declare function or(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; +``` + +### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryConstraints | [QueryFilterConstraint](./firestore_.md#queryfilterconstraint)\[\] | Optional. The list of [QueryFilterConstraint](./firestore_.md#queryfilterconstraint)s to perform a disjunction for. These must be created with calls to [where()](./firestore_.md#where), [or()](./firestore_.md#or), or [and()](./firestore_.md#and). | + +Returns: + +[QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) + +The newly created [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class). + ## addDoc() Add a new document to specified `CollectionReference` with the given data, assigning it a document ID automatically. @@ -2051,9 +2127,19 @@ Describes the different query constraints available in this SDK. export declare type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; ``` +## QueryFilterConstraint + +`QueryFilterConstraint` is a helper union type that represents [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class) and [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class). + +Signature: + +```typescript +export declare type QueryFilterConstraint = QueryFieldFilterConstraint | QueryCompositeFilterConstraint; +``` + ## QueryNonFilterConstraint -`QueryNonFilterConstraint` is a helper union type that represents QueryConstraints which are used to narrow or order the set of documents, but that do not explicitly filter on a document field. `QueryNonFilterConstraint`s are created by invoking [orderBy()](./firestore_.md#orderby), , , , , [limit()](./firestore_.md#limit) or [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the `QueryConstraint`. +`QueryNonFilterConstraint` is a helper union type that represents QueryConstraints which are used to narrow or order the set of documents, but that do not explicitly filter on a document field. `QueryNonFilterConstraint`s are created by invoking [orderBy()](./firestore_.md#orderby), [startAt()](./firestore_.md#startat), [startAfter()](./firestore_.md#startafter), [endBefore()](./firestore_.md#endbefore), [endAt()](./firestore_.md#endat), [limit()](./firestore_.md#limit) or [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the `QueryConstraint`. Signature: diff --git a/docs-devsite/firestore_.querycompositefilterconstraint.md b/docs-devsite/firestore_.querycompositefilterconstraint.md new file mode 100644 index 00000000000..7c82f21e25f --- /dev/null +++ b/docs-devsite/firestore_.querycompositefilterconstraint.md @@ -0,0 +1,35 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# QueryCompositeFilterConstraint class +A `QueryCompositeFilterConstraint` is used to narrow the set of documents returned by a Firestore query by performing the logical OR or AND of multiple [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class)s or [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class)s. `QueryCompositeFilterConstraint`s are created by invoking [or()](./firestore_.md#or) or [and()](./firestore_.md#and) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the `QueryCompositeFilterConstraint`. + +Signature: + +```typescript +export declare class QueryCompositeFilterConstraint +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [type](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstrainttype) | | 'or' \| 'and' | The type of this query constraint | + +## QueryCompositeFilterConstraint.type + +The type of this query constraint + +Signature: + +```typescript +readonly type: 'or' | 'and'; +``` diff --git a/docs-devsite/firestore_.queryconstraint.md b/docs-devsite/firestore_.queryconstraint.md index 77ebddf1c65..c51850d8dea 100644 --- a/docs-devsite/firestore_.queryconstraint.md +++ b/docs-devsite/firestore_.queryconstraint.md @@ -10,7 +10,7 @@ https://github.com/firebase/firebase-js-sdk {% endcomment %} # QueryConstraint class -A `QueryConstraint` is used to narrow the set of documents returned by a Firestore query. `QueryConstraint`s are created by invoking [where()](./firestore_.md#where), [orderBy()](./firestore_.md#orderby), , , , , [limit()](./firestore_.md#limit), [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this `QueryConstraint`. +A `QueryConstraint` is used to narrow the set of documents returned by a Firestore query. `QueryConstraint`s are created by invoking [where()](./firestore_.md#where), [orderBy()](./firestore_.md#orderby), [startAt()](./firestore_.md#startat), [startAfter()](./firestore_.md#startafter), [endBefore()](./firestore_.md#endbefore), [endAt()](./firestore_.md#endat), [limit()](./firestore_.md#limit), [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this `QueryConstraint`. Signature: diff --git a/docs-devsite/firestore_lite.md b/docs-devsite/firestore_lite.md index 3dc3dfe2388..b0ec302e722 100644 --- a/docs-devsite/firestore_lite.md +++ b/docs-devsite/firestore_lite.md @@ -57,7 +57,11 @@ https://github.com/firebase/firebase-js-sdk | function(query...) | | [getCount(query)](./firestore_lite.md#getcount) | Calculates the number of documents in the result set of the given query, without actually downloading the documents.Using this function to count the documents is efficient because only the final count, not the documents' data, is downloaded. This function can even count the documents if the result set would be prohibitively large to download entirely (e.g. thousands of documents). | | [getDocs(query)](./firestore_lite.md#getdocs) | Executes the query and returns the results as a [QuerySnapshot](./firestore_.querysnapshot.md#querysnapshot_class).All queries are executed directly by the server, even if the the query was previously executed. Recent modifications are only reflected in the retrieved results if they have already been applied by the backend. If the client is offline, the operation fails. To see previously cached result and local modifications, use the full Firestore SDK. | +| [query(query, compositeFilter, queryConstraints)](./firestore_lite.md#query) | Creates a new immutable instance of [Query](./firestore_.query.md#query_class) that is extended to also include additional query constraints. | | [query(query, queryConstraints)](./firestore_lite.md#query) | Creates a new immutable instance of [Query](./firestore_.query.md#query_class) that is extended to also include additional query constraints. | +| function(queryConstraints...) | +| [and(queryConstraints)](./firestore_lite.md#and) | Creates a new [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) that is a conjunction of the given filter constraints. A conjunction filter includes a document if it satisfies all of the given filters. | +| [or(queryConstraints)](./firestore_lite.md#or) | Creates a new [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) that is a disjunction of the given filter constraints. A disjunction filter includes a document if it satisfies any of the given filters. | | function(reference...) | | [addDoc(reference, data)](./firestore_lite.md#adddoc) | Add a new document to specified CollectionReference with the given data, assigning it a document ID automatically.The result of this write will only be reflected in document reads that occur after the returned promise resolves. If the client is offline, the write fails. If you would like to see local modifications or buffer writes until the client is online, use the full Firestore SDK. | | [collection(reference, path, pathSegments)](./firestore_lite.md#collection) | Gets a CollectionReference instance that refers to a subcollection of reference at the the specified relative path. | @@ -92,7 +96,8 @@ https://github.com/firebase/firebase-js-sdk | [FirestoreError](./firestore_lite.firestoreerror.md#firestoreerror_class) | An error returned by a Firestore operation. | | [GeoPoint](./firestore_lite.geopoint.md#geopoint_class) | An immutable object representing a geographic location in Firestore. The location is represented as latitude/longitude pair.Latitude values are in the range of \[-90, 90\]. Longitude values are in the range of \[-180, 180\]. | | [Query](./firestore_lite.query.md#query_class) | A Query refers to a query which you can read or listen to. You can also construct refined Query objects by adding filters and ordering. | -| [QueryConstraint](./firestore_lite.queryconstraint.md#queryconstraint_class) | A QueryConstraint is used to narrow the set of documents returned by a Firestore query. QueryConstraints are created by invoking [where()](./firestore_.md#where), [orderBy()](./firestore_.md#orderby), , , , , [limit()](./firestore_.md#limit), [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this QueryConstraint. | +| [QueryCompositeFilterConstraint](./firestore_lite.querycompositefilterconstraint.md#querycompositefilterconstraint_class) | A QueryCompositeFilterConstraint is used to narrow the set of documents returned by a Firestore query by performing the logical OR or AND of multiple [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class)s or [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class)s. QueryCompositeFilterConstraints are created by invoking [or()](./firestore_.md#or) or [and()](./firestore_.md#and) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the QueryCompositeFilterConstraint. | +| [QueryConstraint](./firestore_lite.queryconstraint.md#queryconstraint_class) | A QueryConstraint is used to narrow the set of documents returned by a Firestore query. QueryConstraints are created by invoking [where()](./firestore_.md#where), [orderBy()](./firestore_.md#orderby), [startAt()](./firestore_.md#startat), [startAfter()](./firestore_.md#startafter), [endBefore()](./firestore_.md#endbefore), [endAt()](./firestore_.md#endat), [limit()](./firestore_.md#limit), [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this QueryConstraint. | | [QueryDocumentSnapshot](./firestore_lite.querydocumentsnapshot.md#querydocumentsnapshot_class) | A QueryDocumentSnapshot contains data read from a document in your Firestore database as part of a query. The document is guaranteed to exist and its data can be extracted with .data() or .get(<field>) to get a specific field.A QueryDocumentSnapshot offers the same API surface as a DocumentSnapshot. Since query results contain only existing documents, the exists property will always be true and data() will never return 'undefined'. | | [QueryEndAtConstraint](./firestore_lite.queryendatconstraint.md#queryendatconstraint_class) | A QueryEndAtConstraint is used to exclude documents from the end of a result set returned by a Firestore query. QueryEndAtConstraints are created by invoking [endAt()](./firestore_.md#endat) or [endBefore()](./firestore_.md#endbefore) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this QueryEndAtConstraint. | | [QueryFieldFilterConstraint](./firestore_lite.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class) | A QueryFieldFilterConstraint is used to narrow the set of documents returned by a Firestore query by filtering on one or more document fields. QueryFieldFilterConstraints are created by invoking [where()](./firestore_.md#where) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this QueryFieldFilterConstraint. | @@ -128,7 +133,8 @@ https://github.com/firebase/firebase-js-sdk | [PartialWithFieldValue](./firestore_lite.md#partialwithfieldvalue) | Similar to Typescript's Partial<T>, but allows nested fields to be omitted and FieldValues to be passed in as property values. | | [Primitive](./firestore_lite.md#primitive) | Primitive types. | | [QueryConstraintType](./firestore_lite.md#queryconstrainttype) | Describes the different query constraints available in this SDK. | -| [QueryNonFilterConstraint](./firestore_lite.md#querynonfilterconstraint) | QueryNonFilterConstraint is a helper union type that represents QueryConstraints which are used to narrow or order the set of documents, but that do not explicitly filter on a document field. QueryNonFilterConstraints are created by invoking [orderBy()](./firestore_.md#orderby), , , , , [limit()](./firestore_.md#limit) or [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the QueryConstraint. | +| [QueryFilterConstraint](./firestore_lite.md#queryfilterconstraint) | QueryFilterConstraint is a helper union type that represents [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class) and [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class). | +| [QueryNonFilterConstraint](./firestore_lite.md#querynonfilterconstraint) | QueryNonFilterConstraint is a helper union type that represents QueryConstraints which are used to narrow or order the set of documents, but that do not explicitly filter on a document field. QueryNonFilterConstraints are created by invoking [orderBy()](./firestore_.md#orderby), [startAt()](./firestore_.md#startat), [startAfter()](./firestore_.md#startafter), [endBefore()](./firestore_.md#endbefore), [endAt()](./firestore_.md#endat), [limit()](./firestore_.md#limit) or [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the QueryConstraint. | | [SetOptions](./firestore_lite.md#setoptions) | An options object that configures the behavior of [setDoc()](./firestore_lite.md#setdoc), and calls. These calls can be configured to perform granular merges instead of overwriting the target documents in their entirety by providing a SetOptions with merge: true. | | [UnionToIntersection](./firestore_lite.md#uniontointersection) | Given a union type U = T1 | T2 | ..., returns an intersected type (T1 & T2 & ...).Uses distributive conditional types and inference from conditional types. This works because multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred. https://www.typescriptlang.org/docs/handbook/advanced-types.html\#type-inference-in-conditional-types https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type | | [UpdateData](./firestore_lite.md#updatedata) | Update data (for use with [updateDoc()](./firestore_.md#updatedoc)) that consists of field paths (e.g. 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference nested fields within the document. FieldValues can be passed in as property values. | @@ -841,6 +847,32 @@ Creates a new immutable instance of [Query](./firestore_.query.md#query_class) t Signature: +```typescript +export declare function query(query: Query, compositeFilter: QueryCompositeFilterConstraint, ...queryConstraints: QueryNonFilterConstraint[]): Query; +``` + +### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| query | [Query](./firestore_lite.query.md#query_class)<T> | The [Query](./firestore_.query.md#query_class) instance to use as a base for the new constraints. | +| compositeFilter | [QueryCompositeFilterConstraint](./firestore_lite.querycompositefilterconstraint.md#querycompositefilterconstraint_class) | The [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) to apply. Create [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) using [and()](./firestore_.md#and) or [or()](./firestore_.md#or). | +| queryConstraints | [QueryNonFilterConstraint](./firestore_lite.md#querynonfilterconstraint)\[\] | Additional [QueryNonFilterConstraint](./firestore_.md#querynonfilterconstraint)s to apply (e.g. [orderBy()](./firestore_.md#orderby), [limit()](./firestore_.md#limit)). | + +Returns: + +[Query](./firestore_lite.query.md#query_class)<T> + +## Exceptions + +if any of the provided query constraints cannot be combined with the existing or new constraints. + +## query() + +Creates a new immutable instance of [Query](./firestore_.query.md#query_class) that is extended to also include additional query constraints. + +Signature: + ```typescript export declare function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; ``` @@ -860,6 +892,50 @@ export declare function query(query: Query, ...queryConstraints: QueryCons if any of the provided query constraints cannot be combined with the existing or new constraints. +## and() + +Creates a new [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) that is a conjunction of the given filter constraints. A conjunction filter includes a document if it satisfies all of the given filters. + +Signature: + +```typescript +export declare function and(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; +``` + +### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryConstraints | [QueryFilterConstraint](./firestore_lite.md#queryfilterconstraint)\[\] | Optional. The list of [QueryFilterConstraint](./firestore_.md#queryfilterconstraint)s to perform a conjunction for. These must be created with calls to [where()](./firestore_.md#where), [or()](./firestore_.md#or), or [and()](./firestore_.md#and). | + +Returns: + +[QueryCompositeFilterConstraint](./firestore_lite.querycompositefilterconstraint.md#querycompositefilterconstraint_class) + +The newly created [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class). + +## or() + +Creates a new [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class) that is a disjunction of the given filter constraints. A disjunction filter includes a document if it satisfies any of the given filters. + +Signature: + +```typescript +export declare function or(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; +``` + +### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryConstraints | [QueryFilterConstraint](./firestore_lite.md#queryfilterconstraint)\[\] | Optional. The list of [QueryFilterConstraint](./firestore_.md#queryfilterconstraint)s to perform a disjunction for. These must be created with calls to [where()](./firestore_.md#where), [or()](./firestore_.md#or), or [and()](./firestore_.md#and). | + +Returns: + +[QueryCompositeFilterConstraint](./firestore_lite.querycompositefilterconstraint.md#querycompositefilterconstraint_class) + +The newly created [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class). + ## addDoc() Add a new document to specified `CollectionReference` with the given data, assigning it a document ID automatically. @@ -1372,9 +1448,19 @@ Describes the different query constraints available in this SDK. export declare type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; ``` +## QueryFilterConstraint + +`QueryFilterConstraint` is a helper union type that represents [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class) and [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class). + +Signature: + +```typescript +export declare type QueryFilterConstraint = QueryFieldFilterConstraint | QueryCompositeFilterConstraint; +``` + ## QueryNonFilterConstraint -`QueryNonFilterConstraint` is a helper union type that represents QueryConstraints which are used to narrow or order the set of documents, but that do not explicitly filter on a document field. `QueryNonFilterConstraint`s are created by invoking [orderBy()](./firestore_.md#orderby), , , , , [limit()](./firestore_.md#limit) or [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the `QueryConstraint`. +`QueryNonFilterConstraint` is a helper union type that represents QueryConstraints which are used to narrow or order the set of documents, but that do not explicitly filter on a document field. `QueryNonFilterConstraint`s are created by invoking [orderBy()](./firestore_.md#orderby), [startAt()](./firestore_.md#startat), [startAfter()](./firestore_.md#startafter), [endBefore()](./firestore_.md#endbefore), [endAt()](./firestore_.md#endat), [limit()](./firestore_.md#limit) or [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the `QueryConstraint`. Signature: diff --git a/docs-devsite/firestore_lite.querycompositefilterconstraint.md b/docs-devsite/firestore_lite.querycompositefilterconstraint.md new file mode 100644 index 00000000000..b0bd12f96ec --- /dev/null +++ b/docs-devsite/firestore_lite.querycompositefilterconstraint.md @@ -0,0 +1,35 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# QueryCompositeFilterConstraint class +A `QueryCompositeFilterConstraint` is used to narrow the set of documents returned by a Firestore query by performing the logical OR or AND of multiple [QueryFieldFilterConstraint](./firestore_.queryfieldfilterconstraint.md#queryfieldfilterconstraint_class)s or [QueryCompositeFilterConstraint](./firestore_.querycompositefilterconstraint.md#querycompositefilterconstraint_class)s. `QueryCompositeFilterConstraint`s are created by invoking [or()](./firestore_.md#or) or [and()](./firestore_.md#and) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains the `QueryCompositeFilterConstraint`. + +Signature: + +```typescript +export declare class QueryCompositeFilterConstraint +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [type](./firestore_lite.querycompositefilterconstraint.md#querycompositefilterconstrainttype) | | 'or' \| 'and' | The type of this query constraint | + +## QueryCompositeFilterConstraint.type + +The type of this query constraint + +Signature: + +```typescript +readonly type: 'or' | 'and'; +``` diff --git a/docs-devsite/firestore_lite.queryconstraint.md b/docs-devsite/firestore_lite.queryconstraint.md index dae8d5f0069..f9d2d6ddb0a 100644 --- a/docs-devsite/firestore_lite.queryconstraint.md +++ b/docs-devsite/firestore_lite.queryconstraint.md @@ -10,7 +10,7 @@ https://github.com/firebase/firebase-js-sdk {% endcomment %} # QueryConstraint class -A `QueryConstraint` is used to narrow the set of documents returned by a Firestore query. `QueryConstraint`s are created by invoking [where()](./firestore_.md#where), [orderBy()](./firestore_.md#orderby), , , , , [limit()](./firestore_.md#limit), [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this `QueryConstraint`. +A `QueryConstraint` is used to narrow the set of documents returned by a Firestore query. `QueryConstraint`s are created by invoking [where()](./firestore_.md#where), [orderBy()](./firestore_.md#orderby), [startAt()](./firestore_.md#startat), [startAfter()](./firestore_.md#startafter), [endBefore()](./firestore_.md#endbefore), [endAt()](./firestore_.md#endat), [limit()](./firestore_.md#limit), [limitToLast()](./firestore_.md#limittolast) and can then be passed to [query()](./firestore_.md#query) to create a new query instance that also contains this `QueryConstraint`. Signature: diff --git a/packages/firestore/src/lite-api/query.ts b/packages/firestore/src/lite-api/query.ts index 078b3a6e91b..3afa282048f 100644 --- a/packages/firestore/src/lite-api/query.ts +++ b/packages/firestore/src/lite-api/query.ts @@ -101,9 +101,9 @@ export abstract class AppliableConstraint { /** * A `QueryConstraint` is used to narrow the set of documents returned by a * Firestore query. `QueryConstraint`s are created by invoking {@link where}, - * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link - * endBefore}, {@link endAt}, {@link limit}, {@link limitToLast} and - * can then be passed to {@link query} to create a new query instance that + * {@link orderBy}, {@link (startAt:1)}, {@link (startAfter:1)}, {@link + * (endBefore:1)}, {@link (endAt:1)}, {@link limit}, {@link limitToLast} and + * can then be passed to {@link (query:1)} to create a new query instance that * also contains this `QueryConstraint`. */ export abstract class QueryConstraint extends AppliableConstraint { @@ -179,7 +179,7 @@ export function query( * A `QueryFieldFilterConstraint` is used to narrow the set of documents returned by * a Firestore query by filtering on one or more document fields. * `QueryFieldFilterConstraint`s are created by invoking {@link where} and can then - * be passed to {@link query} to create a new query instance that also contains + * be passed to {@link (query:1)} to create a new query instance that also contains * this `QueryFieldFilterConstraint`. */ export class QueryFieldFilterConstraint extends QueryConstraint { @@ -273,7 +273,7 @@ export function where( * returned by a Firestore query by performing the logical OR or AND of multiple * {@link QueryFieldFilterConstraint}s or {@link QueryCompositeFilterConstraint}s. * `QueryCompositeFilterConstraint`s are created by invoking {@link or} or - * {@link and} and can then be passed to {@link query} to create a new query + * {@link and} and can then be passed to {@link (query:1)} to create a new query * instance that also contains the `QueryCompositeFilterConstraint`. */ export class QueryCompositeFilterConstraint extends AppliableConstraint { @@ -339,8 +339,8 @@ export class QueryCompositeFilterConstraint extends AppliableConstraint { * QueryConstraints which are used to narrow or order the set of documents, * but that do not explicitly filter on a document field. * `QueryNonFilterConstraint`s are created by invoking {@link orderBy}, - * {@link startAt}, {@link startAfter}, {@link endBefore}, {@link endAt}, - * {@link limit} or {@link limitToLast} and can then be passed to {@link query} + * {@link (startAt:1)}, {@link (startAfter:1)}, {@link (endBefore:1)}, {@link (endAt:1)}, + * {@link limit} or {@link limitToLast} and can then be passed to {@link (query:1)} * to create a new query instance that also contains the `QueryConstraint`. */ export type QueryNonFilterConstraint = @@ -352,9 +352,6 @@ export type QueryNonFilterConstraint = /** * `QueryFilterConstraint` is a helper union type that represents * {@link QueryFieldFilterConstraint} and {@link QueryCompositeFilterConstraint}. - * `QueryFilterConstraint`s are created by invoking {@link or} or {@link and} - * and can then be passed to {@link query} to create a new query instance that - * also contains the `QueryConstraint`. */ export type QueryFilterConstraint = | QueryFieldFilterConstraint @@ -411,7 +408,7 @@ export function and( /** * A `QueryOrderByConstraint` is used to sort the set of documents returned by a * Firestore query. `QueryOrderByConstraint`s are created by invoking - * {@link orderBy} and can then be passed to {@link query} to create a new query + * {@link orderBy} and can then be passed to {@link (query:1)} to create a new query * instance that also contains this `QueryOrderByConstraint`. * * Note: Documents that do not contain the orderBy field will not be present in @@ -479,7 +476,7 @@ export function orderBy( * A `QueryLimitConstraint` is used to limit the number of documents returned by * a Firestore query. * `QueryLimitConstraint`s are created by invoking {@link limit} or - * {@link limitToLast} and can then be passed to {@link query} to create a new + * {@link limitToLast} and can then be passed to {@link (query:1)} to create a new * query instance that also contains this `QueryLimitConstraint`. */ export class QueryLimitConstraint extends QueryConstraint { @@ -543,7 +540,7 @@ export function limitToLast(limit: number): QueryLimitConstraint { * A `QueryStartAtConstraint` is used to exclude documents from the start of a * result set returned by a Firestore query. * `QueryStartAtConstraint`s are created by invoking {@link (startAt:1)} or - * {@link (startAfter:1)} and can then be passed to {@link query} to create a + * {@link (startAfter:1)} and can then be passed to {@link (query:1)} to create a * new query instance that also contains this `QueryStartAtConstraint`. */ export class QueryStartAtConstraint extends QueryConstraint { @@ -650,7 +647,7 @@ export function startAfter( * A `QueryEndAtConstraint` is used to exclude documents from the end of a * result set returned by a Firestore query. * `QueryEndAtConstraint`s are created by invoking {@link (endAt:1)} or - * {@link (endBefore:1)} and can then be passed to {@link query} to create a new + * {@link (endBefore:1)} and can then be passed to {@link (query:1)} to create a new * query instance that also contains this `QueryEndAtConstraint`. */ export class QueryEndAtConstraint extends QueryConstraint { diff --git a/packages/firestore/src/lite-api/reference.ts b/packages/firestore/src/lite-api/reference.ts index 7ade1ec0714..0a9fdcf71cd 100644 --- a/packages/firestore/src/lite-api/reference.ts +++ b/packages/firestore/src/lite-api/reference.ts @@ -242,7 +242,7 @@ export class Query { /** * A `CollectionReference` object can be used for adding documents, getting - * document references, and querying for documents (using {@link query}). + * document references, and querying for documents (using {@link (query:1)}). */ export class CollectionReference extends Query { /** The type of this Firestore reference. */ From 9b65f10e0b295d613f92d22e54ee0b830dd906d4 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 1 Mar 2023 09:08:34 -0700 Subject: [PATCH 10/12] Removing and renaming tests based on PR feedback. --- .../test/integration/api/query.test.ts | 61 +------------------ 1 file changed, 2 insertions(+), 59 deletions(-) diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index 415f192638b..4b2f6f8bca7 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1458,7 +1458,7 @@ apiDescribe('Queries', (persistence: boolean) => { }); }); - it('can use or queries with in and not-in', () => { + it('can use or queries with in', () => { const testDocs = { doc1: { a: 1, b: 0 }, doc2: { b: 1 }, @@ -1531,69 +1531,12 @@ apiDescribe('Queries', (persistence: boolean) => { 'doc6' ); - // Two IN operations on different fields with conjunction. - await checkOnlineAndOfflineResultsMatch( - query(coll, and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2]))), - 'doc3' - ); - - // Two IN operations on the same field. - // a IN [1,2,3] && a IN [0,1,4] should result in "a==1". - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [1, 2, 3]), where('a', 'in', [0, 1, 4])) - ), - 'doc1', - 'doc4', - 'doc5' - ); - - // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an - // empty set. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('a', 'in', [0, 1, 4])) - ) - ); - // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]). await checkOnlineAndOfflineResultsMatch( query(coll, or(where('a', 'in', [0, 3]), where('a', 'in', [0, 2]))), 'doc3', 'doc6' ); - - // Nested composite filter on the same field. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [1, 3]), - or( - where('a', 'in', [0, 2]), - and(where('b', '==', 2), where('a', 'in', [1, 3])) - ) - ) - ), - 'doc3' - ); - - // Nested composite filter on the different fields. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('b', 'in', [0, 3]), - or( - where('b', 'in', [1]), - and(where('b', 'in', [2, 3]), where('a', 'in', [1, 3])) - ) - ) - ), - 'doc4' - ); }); }); @@ -1806,7 +1749,7 @@ apiDescribe('Queries', (persistence: boolean) => { }); }); - it('can use or queries with in and not-in', () => { + it('can use or queries with not-in', () => { const testDocs = { doc1: { a: 1, b: 0 }, doc2: { b: 1 }, From 097ccc702afdc150e4fe5aca24204e805ec56e1d Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 1 Mar 2023 09:13:03 -0700 Subject: [PATCH 11/12] Correcting the change type in the changeset file. --- .changeset/sweet-rats-compete.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/sweet-rats-compete.md b/.changeset/sweet-rats-compete.md index a48062ea92e..0c10ba8bac5 100644 --- a/.changeset/sweet-rats-compete.md +++ b/.changeset/sweet-rats-compete.md @@ -1,5 +1,6 @@ --- -"@firebase/firestore": feat +"@firebase/firestore": minor +'firebase': minor --- OR Query public API From e880dd716fc8aa9ef6ac1cfb6abae21dbdda0b87 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Fri, 3 Mar 2023 13:29:20 -0700 Subject: [PATCH 12/12] Disable tests that have multiple ins or array-contains-any per query. --- .../test/integration/api/query.test.ts | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index 4b2f6f8bca7..bec924e3fb9 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1511,8 +1511,10 @@ apiDescribe('Queries', (persistence: boolean) => { }); }); + // TODO(orquery) enable this test when the backend supports + // one in per disjunction // eslint-disable-next-line no-restricted-properties - it('supports multiple in ops', () => { + it.skip('supports multiple in ops', () => { const testDocs = { doc1: { a: 1, b: 0 }, doc2: { b: 1 }, @@ -1540,8 +1542,10 @@ apiDescribe('Queries', (persistence: boolean) => { }); }); + // TODO(orquery) enable this test when the backend supports + // one in or array-contains-any per disjunction // eslint-disable-next-line no-restricted-properties - it('supports using in with array contains any', () => { + it.skip('supports using in with array contains any', () => { const testDocs = { doc1: { a: 1, b: [0] }, doc2: { b: [1] }, @@ -1566,17 +1570,6 @@ apiDescribe('Queries', (persistence: boolean) => { 'doc6' ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc3' - ); - await checkOnlineAndOfflineResultsMatch( query( coll, @@ -1589,18 +1582,6 @@ apiDescribe('Queries', (persistence: boolean) => { 'doc3', 'doc4' ); - - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - or(where('b', 'array-contains-any', [0, 7]), where('c', '==', 20)) - ) - ), - 'doc3', - 'doc6' - ); }); });