Skip to content

Commit 513aa21

Browse files
committed
Rework the code that prevents implicit order-bys from being sent with aggregate queries.
1 parent 4d7e2c5 commit 513aa21

File tree

3 files changed

+37
-52
lines changed

3 files changed

+37
-52
lines changed

packages/firestore/src/core/query.ts

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const enum LimitType {
4343
/**
4444
* The Query interface defines all external properties of a query.
4545
*
46-
* QueryImpl implements this interface to provide memoization for `queryOrderBy`
46+
* QueryImpl implements this interface to provide memoization for `queryNormalizedOrderBy`
4747
* and `queryToTarget`.
4848
*/
4949
export interface Query {
@@ -65,7 +65,7 @@ export interface Query {
6565
* Visible for testing.
6666
*/
6767
export class QueryImpl implements Query {
68-
memoizedOrderBy: OrderBy[] | null = null;
68+
memoizedNormalizedOrderBy: OrderBy[] | null = null;
6969

7070
// The corresponding `Target` of this `Query` instance.
7171
memoizedTarget: Target | null = null;
@@ -86,13 +86,13 @@ export class QueryImpl implements Query {
8686
) {
8787
if (this.startAt) {
8888
debugAssert(
89-
this.startAt.position.length <= queryOrderBy(this).length,
89+
this.startAt.position.length <= queryNormalizedOrderBy(this).length,
9090
'Bound is longer than orderBy'
9191
);
9292
}
9393
if (this.endAt) {
9494
debugAssert(
95-
this.endAt.position.length <= queryOrderBy(this).length,
95+
this.endAt.position.length <= queryNormalizedOrderBy(this).length,
9696
'Bound is longer than orderBy'
9797
);
9898
}
@@ -211,32 +211,16 @@ export function isCollectionGroupQuery(query: Query): boolean {
211211
}
212212

213213
/**
214-
* Returns the implicit order by constraint that is used to execute the Query,
214+
* Returns the normalized order by constraint that is used to execute the Query,
215215
* which can be different from the order by constraints the user provided (e.g.
216-
* the SDK and backend always orders by `__name__`).
216+
* the SDK and backend always orders by `__name__`). The normalized order-by
217+
* includes implicit order-bys in addition to the explicit user provided
218+
* order-bys.
217219
*/
218-
export function queryOrderBy(
219-
query: Query,
220-
settings?: { includeImplicitOrderBy: boolean }
221-
): OrderBy[] {
222-
if (!settings) {
223-
settings = {
224-
includeImplicitOrderBy: true
225-
};
226-
}
227-
220+
export function queryNormalizedOrderBy(query: Query): OrderBy[] {
228221
const queryImpl = debugCast(query, QueryImpl);
229-
if (queryImpl.memoizedOrderBy === null) {
230-
queryImpl.memoizedOrderBy = [];
231-
232-
// If there are no explicit order-by operators, and we are not including
233-
// implicit order-bys, then return the empty memoizedOrderBy result
234-
if (
235-
queryImpl.explicitOrderBy.length === 0 &&
236-
!settings.includeImplicitOrderBy
237-
) {
238-
return queryImpl.memoizedOrderBy;
239-
}
222+
if (queryImpl.memoizedNormalizedOrderBy === null) {
223+
queryImpl.memoizedNormalizedOrderBy = [];
240224

241225
const inequalityField = getInequalityFilterField(queryImpl);
242226
const firstOrderByField = getFirstOrderByField(queryImpl);
@@ -245,9 +229,9 @@ export function queryOrderBy(
245229
// inequality filter field for it to be a valid query.
246230
// Note that the default inequality field and key ordering is ascending.
247231
if (!inequalityField.isKeyField()) {
248-
queryImpl.memoizedOrderBy.push(new OrderBy(inequalityField));
232+
queryImpl.memoizedNormalizedOrderBy.push(new OrderBy(inequalityField));
249233
}
250-
queryImpl.memoizedOrderBy.push(
234+
queryImpl.memoizedNormalizedOrderBy.push(
251235
new OrderBy(FieldPath.keyField(), Direction.ASCENDING)
252236
);
253237
} else {
@@ -259,7 +243,7 @@ export function queryOrderBy(
259243
);
260244
let foundKeyOrdering = false;
261245
for (const orderBy of queryImpl.explicitOrderBy) {
262-
queryImpl.memoizedOrderBy.push(orderBy);
246+
queryImpl.memoizedNormalizedOrderBy.push(orderBy);
263247
if (orderBy.field.isKeyField()) {
264248
foundKeyOrdering = true;
265249
}
@@ -272,52 +256,53 @@ export function queryOrderBy(
272256
? queryImpl.explicitOrderBy[queryImpl.explicitOrderBy.length - 1]
273257
.dir
274258
: Direction.ASCENDING;
275-
queryImpl.memoizedOrderBy.push(
259+
queryImpl.memoizedNormalizedOrderBy.push(
276260
new OrderBy(FieldPath.keyField(), lastDirection)
277261
);
278262
}
279263
}
280264
}
281-
return queryImpl.memoizedOrderBy;
265+
return queryImpl.memoizedNormalizedOrderBy;
282266
}
283267

284268
/**
285269
* Converts this `Query` instance to it's corresponding `Target` representation.
286270
*/
287271
export function queryToTarget(query: Query): Target {
288-
return _queryToTarget(query);
272+
return _queryToTarget(query, queryNormalizedOrderBy(query));
289273
}
290274

275+
/**
276+
* Converts this `Query` instance to it's corresponding `Target` representation,
277+
* for use within an aggregate query.
278+
*/
291279
export function aggregateQueryToTarget(query: Query): Target {
292-
return _queryToTarget(query, { includeImplicitOrderBy: false });
280+
// Do not include implicit order-bys for aggregate queries.
281+
return _queryToTarget(query, query.explicitOrderBy);
293282
}
294283

295-
export function _queryToTarget(
296-
query: Query,
297-
settings?: { includeImplicitOrderBy: boolean }
298-
): Target {
284+
export function _queryToTarget(query: Query, orderBys: OrderBy[]): Target {
299285
const queryImpl = debugCast(query, QueryImpl);
300286
if (!queryImpl.memoizedTarget) {
301287
if (queryImpl.limitType === LimitType.First) {
302288
queryImpl.memoizedTarget = newTarget(
303289
queryImpl.path,
304290
queryImpl.collectionGroup,
305-
queryOrderBy(queryImpl, settings),
291+
orderBys,
306292
queryImpl.filters,
307293
queryImpl.limit,
308294
queryImpl.startAt,
309295
queryImpl.endAt
310296
);
311297
} else {
312298
// Flip the orderBy directions since we want the last results
313-
const orderBys = [] as OrderBy[];
314-
for (const orderBy of queryOrderBy(queryImpl)) {
299+
orderBys = orderBys.map(orderBy => {
315300
const dir =
316301
orderBy.dir === Direction.DESCENDING
317302
? Direction.ASCENDING
318303
: Direction.DESCENDING;
319-
orderBys.push(new OrderBy(orderBy.field, dir));
320-
}
304+
return new OrderBy(orderBy.field, dir);
305+
});
321306

322307
// We need to swap the cursors to match the now-flipped query ordering.
323308
const startAt = queryImpl.endAt
@@ -490,13 +475,13 @@ function queryMatchesPathAndCollectionGroup(
490475
* in the results.
491476
*/
492477
function queryMatchesOrderBy(query: Query, doc: Document): boolean {
493-
// We must use `queryOrderBy()` to get the list of all orderBys (both implicit and explicit).
478+
// We must use `queryNormalizedOrderBy()` to get the list of all orderBys (both implicit and explicit).
494479
// Note that for OR queries, orderBy applies to all disjunction terms and implicit orderBys must
495480
// be taken into account. For example, the query "a > 1 || b==1" has an implicit "orderBy a" due
496481
// to the inequality, and is evaluated as "a > 1 orderBy a || b==1 orderBy a".
497482
// A document with content of {b:1} matches the filters, but does not match the orderBy because
498483
// it's missing the field 'a'.
499-
for (const orderBy of queryOrderBy(query)) {
484+
for (const orderBy of queryNormalizedOrderBy(query)) {
500485
// order by key always matches
501486
if (!orderBy.field.isKeyField() && doc.data.field(orderBy.field) === null) {
502487
return false;
@@ -518,13 +503,13 @@ function queryMatchesFilters(query: Query, doc: Document): boolean {
518503
function queryMatchesBounds(query: Query, doc: Document): boolean {
519504
if (
520505
query.startAt &&
521-
!boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)
506+
!boundSortsBeforeDocument(query.startAt, queryNormalizedOrderBy(query), doc)
522507
) {
523508
return false;
524509
}
525510
if (
526511
query.endAt &&
527-
!boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)
512+
!boundSortsAfterDocument(query.endAt, queryNormalizedOrderBy(query), doc)
528513
) {
529514
return false;
530515
}
@@ -555,7 +540,7 @@ export function newQueryComparator(
555540
): (d1: Document, d2: Document) => number {
556541
return (d1: Document, d2: Document): number => {
557542
let comparedOnKeyField = false;
558-
for (const orderBy of queryOrderBy(query)) {
543+
for (const orderBy of queryNormalizedOrderBy(query)) {
559544
const comp = compareDocs(orderBy, d1, d2);
560545
if (comp !== 0) {
561546
return comp;

packages/firestore/src/lite-api/query.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
isCollectionGroupQuery,
3434
LimitType,
3535
Query as InternalQuery,
36-
queryOrderBy,
36+
queryNormalizedOrderBy,
3737
queryWithAddedFilter,
3838
queryWithAddedOrderBy,
3939
queryWithEndAt,
@@ -907,7 +907,7 @@ export function newQueryBoundFromDocument(
907907
// the provided document. Without the key (by using the explicit sort
908908
// orders), multiple documents could match the position, yielding duplicate
909909
// results.
910-
for (const orderBy of queryOrderBy(query)) {
910+
for (const orderBy of queryNormalizedOrderBy(query)) {
911911
if (orderBy.field.isKeyField()) {
912912
components.push(refValue(databaseId, doc.key));
913913
} else {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
newQueryForPath,
2828
Query,
2929
queryMatches,
30-
queryOrderBy,
30+
queryNormalizedOrderBy,
3131
queryWithAddedFilter,
3232
queryWithEndAt,
3333
queryWithLimit,
@@ -866,7 +866,7 @@ describe('Query', () => {
866866
}
867867

868868
function assertImplicitOrderBy(query: Query, ...orderBys: OrderBy[]): void {
869-
expect(queryOrderBy(query)).to.deep.equal(orderBys);
869+
expect(queryNormalizedOrderBy(query)).to.deep.equal(orderBys);
870870
}
871871

872872
function assertCanonicalId(query: Query, expectedCanonicalId: string): void {

0 commit comments

Comments
 (0)