diff --git a/.changeset/quick-radios-obey.md b/.changeset/quick-radios-obey.md new file mode 100644 index 00000000000..83e452264a5 --- /dev/null +++ b/.changeset/quick-radios-obey.md @@ -0,0 +1,5 @@ +--- +"@firebase/firestore": patch +--- + +Update canonifyFilter to compute the canonization for flat conjunctions the same as implicit AND queries. diff --git a/packages/firestore/src/core/filter.ts b/packages/firestore/src/core/filter.ts index 4322c926658..16bb627f2dc 100644 --- a/packages/firestore/src/core/filter.ts +++ b/packages/firestore/src/core/filter.ts @@ -325,6 +325,14 @@ export function canonifyFilter(filter: Filter): string { filter.op.toString() + canonicalId(filter.value) ); + } else if (compositeFilterIsFlatConjunction(filter)) { + // Older SDK versions use an implicit AND operation between their filters. + // In the new SDK versions, the developer may use an explicit AND filter. + // To stay consistent with the old usages, we add a special case to ensure + // the canonical ID for these two are the same. For example: + // `col.whereEquals("a", 1).whereEquals("b", 2)` should have the same + // canonical ID as `col.where(and(equals("a",1), equals("b",2)))`. + return filter.filters.map(filter => canonifyFilter(filter)).join(','); } else { // filter instanceof CompositeFilter const canonicalIdsString = filter.filters diff --git a/packages/firestore/src/lite-api/query.ts b/packages/firestore/src/lite-api/query.ts index 2f1d7ecbf8f..0b3de2268d0 100644 --- a/packages/firestore/src/lite-api/query.ts +++ b/packages/firestore/src/lite-api/query.ts @@ -364,13 +364,14 @@ export type QueryFilterConstraint = | QueryCompositeFilterConstraint; /** - * Creates a {@link QueryCompositeFilterConstraint} that performs a logical OR - * of all the provided {@link QueryFilterConstraint}s. + * Creates a new {@link QueryCompositeFilterConstraint} that is a disjunction of + * the given filter constraints. A disjunction filter includes a document if it + * satisfies any of the given filters. * - * @param queryConstraints - Optional. The {@link QueryFilterConstraint}s - * for OR operation. These must be created with calls to {@link where}, - * {@link or}, or {@link and}. - * @returns The created {@link QueryCompositeFilterConstraint}. + * @param queryConstraints - Optional. The list of + * {@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( @@ -388,13 +389,14 @@ export function or( } /** - * Creates a {@link QueryCompositeFilterConstraint} that performs a logical AND - * of all the provided {@link QueryFilterConstraint}s. + * Creates a new {@link QueryCompositeFilterConstraint} that is a conjunction of + * the given filter constraints. A conjunction filter includes a document if it + * satisfies all of the given filters. * - * @param queryConstraints - Optional. The {@link QueryFilterConstraint}s - * for AND operation. These must be created with calls to {@link where}, - * {@link or}, or {@link and}. - * @returns The created {@link QueryCompositeFilterConstraint}. + * @param queryConstraints - Optional. The list of + * {@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( diff --git a/packages/firestore/test/unit/core/filter.test.ts b/packages/firestore/test/unit/core/filter.test.ts index 3011e3f1647..a8ea6277c9a 100644 --- a/packages/firestore/test/unit/core/filter.test.ts +++ b/packages/firestore/test/unit/core/filter.test.ts @@ -25,7 +25,9 @@ import { FieldFilter, Operator } from '../../../src/core/filter'; -import { andFilter, filter, orFilter } from '../../util/helpers'; +import { queryToTarget } from '../../../src/core/query'; +import { canonifyTarget } from '../../../src/core/target'; +import { andFilter, filter, orFilter, query } from '../../util/helpers'; describe('FieldFilter', () => { it('exposes field filter members', () => { @@ -93,4 +95,14 @@ describe('CompositeFilter', () => { expect(compositeFilterIsFlat(orFilter2)).false; expect(compositeFilterIsFlatConjunction(orFilter2)).false; }); + + it('computes canonical id of flat conjunctions', () => { + const target1 = query('col', a, b, c); + + const target2 = query('col', andFilter(a, b, c)); + + expect(canonifyTarget(queryToTarget(target1))).to.equal( + canonifyTarget(queryToTarget(target2)) + ); + }); });