@@ -43,7 +43,7 @@ export const enum LimitType {
43
43
/**
44
44
* The Query interface defines all external properties of a query.
45
45
*
46
- * QueryImpl implements this interface to provide memoization for `queryOrderBy `
46
+ * QueryImpl implements this interface to provide memoization for `queryNormalizedOrderBy `
47
47
* and `queryToTarget`.
48
48
*/
49
49
export interface Query {
@@ -65,11 +65,15 @@ export interface Query {
65
65
* Visible for testing.
66
66
*/
67
67
export class QueryImpl implements Query {
68
- memoizedOrderBy : OrderBy [ ] | null = null ;
68
+ memoizedNormalizedOrderBy : OrderBy [ ] | null = null ;
69
69
70
70
// The corresponding `Target` of this `Query` instance.
71
71
memoizedTarget : Target | null = null ;
72
72
73
+ // The corresponding `Target` of this `Query` instance for use with
74
+ // aggregate queries
75
+ memoizedAggregateTarget : Target | null = null ;
76
+
73
77
/**
74
78
* Initializes a Query with a path and optional additional query constraints.
75
79
* Path must currently be empty if this is a collection group query.
@@ -86,13 +90,13 @@ export class QueryImpl implements Query {
86
90
) {
87
91
if ( this . startAt ) {
88
92
debugAssert (
89
- this . startAt . position . length <= queryOrderBy ( this ) . length ,
93
+ this . startAt . position . length <= queryNormalizedOrderBy ( this ) . length ,
90
94
'Bound is longer than orderBy'
91
95
) ;
92
96
}
93
97
if ( this . endAt ) {
94
98
debugAssert (
95
- this . endAt . position . length <= queryOrderBy ( this ) . length ,
99
+ this . endAt . position . length <= queryNormalizedOrderBy ( this ) . length ,
96
100
'Bound is longer than orderBy'
97
101
) ;
98
102
}
@@ -211,14 +215,16 @@ export function isCollectionGroupQuery(query: Query): boolean {
211
215
}
212
216
213
217
/**
214
- * Returns the implicit order by constraint that is used to execute the Query,
218
+ * Returns the normalized order by constraint that is used to execute the Query,
215
219
* which can be different from the order by constraints the user provided (e.g.
216
- * the SDK and backend always orders by `__name__`).
220
+ * the SDK and backend always orders by `__name__`). The normalized order-by
221
+ * includes implicit order-bys in addition to the explicit user provided
222
+ * order-bys.
217
223
*/
218
- export function queryOrderBy ( query : Query ) : OrderBy [ ] {
224
+ export function queryNormalizedOrderBy ( query : Query ) : OrderBy [ ] {
219
225
const queryImpl = debugCast ( query , QueryImpl ) ;
220
- if ( queryImpl . memoizedOrderBy === null ) {
221
- queryImpl . memoizedOrderBy = [ ] ;
226
+ if ( queryImpl . memoizedNormalizedOrderBy === null ) {
227
+ queryImpl . memoizedNormalizedOrderBy = [ ] ;
222
228
223
229
const inequalityField = getInequalityFilterField ( queryImpl ) ;
224
230
const firstOrderByField = getFirstOrderByField ( queryImpl ) ;
@@ -227,9 +233,9 @@ export function queryOrderBy(query: Query): OrderBy[] {
227
233
// inequality filter field for it to be a valid query.
228
234
// Note that the default inequality field and key ordering is ascending.
229
235
if ( ! inequalityField . isKeyField ( ) ) {
230
- queryImpl . memoizedOrderBy . push ( new OrderBy ( inequalityField ) ) ;
236
+ queryImpl . memoizedNormalizedOrderBy . push ( new OrderBy ( inequalityField ) ) ;
231
237
}
232
- queryImpl . memoizedOrderBy . push (
238
+ queryImpl . memoizedNormalizedOrderBy . push (
233
239
new OrderBy ( FieldPath . keyField ( ) , Direction . ASCENDING )
234
240
) ;
235
241
} else {
@@ -241,7 +247,7 @@ export function queryOrderBy(query: Query): OrderBy[] {
241
247
) ;
242
248
let foundKeyOrdering = false ;
243
249
for ( const orderBy of queryImpl . explicitOrderBy ) {
244
- queryImpl . memoizedOrderBy . push ( orderBy ) ;
250
+ queryImpl . memoizedNormalizedOrderBy . push ( orderBy ) ;
245
251
if ( orderBy . field . isKeyField ( ) ) {
246
252
foundKeyOrdering = true ;
247
253
}
@@ -254,13 +260,13 @@ export function queryOrderBy(query: Query): OrderBy[] {
254
260
? queryImpl . explicitOrderBy [ queryImpl . explicitOrderBy . length - 1 ]
255
261
. dir
256
262
: Direction . ASCENDING ;
257
- queryImpl . memoizedOrderBy . push (
263
+ queryImpl . memoizedNormalizedOrderBy . push (
258
264
new OrderBy ( FieldPath . keyField ( ) , lastDirection )
259
265
) ;
260
266
}
261
267
}
262
268
}
263
- return queryImpl . memoizedOrderBy ;
269
+ return queryImpl . memoizedNormalizedOrderBy ;
264
270
}
265
271
266
272
/**
@@ -269,48 +275,76 @@ export function queryOrderBy(query: Query): OrderBy[] {
269
275
export function queryToTarget ( query : Query ) : Target {
270
276
const queryImpl = debugCast ( query , QueryImpl ) ;
271
277
if ( ! queryImpl . memoizedTarget ) {
272
- if ( queryImpl . limitType === LimitType . First ) {
273
- queryImpl . memoizedTarget = newTarget (
274
- queryImpl . path ,
275
- queryImpl . collectionGroup ,
276
- queryOrderBy ( queryImpl ) ,
277
- queryImpl . filters ,
278
- queryImpl . limit ,
279
- queryImpl . startAt ,
280
- queryImpl . endAt
281
- ) ;
282
- } else {
283
- // Flip the orderBy directions since we want the last results
284
- const orderBys = [ ] as OrderBy [ ] ;
285
- for ( const orderBy of queryOrderBy ( queryImpl ) ) {
286
- const dir =
287
- orderBy . dir === Direction . DESCENDING
288
- ? Direction . ASCENDING
289
- : Direction . DESCENDING ;
290
- orderBys . push ( new OrderBy ( orderBy . field , dir ) ) ;
291
- }
278
+ queryImpl . memoizedTarget = _queryToTarget (
279
+ queryImpl ,
280
+ queryNormalizedOrderBy ( query )
281
+ ) ;
282
+ }
292
283
293
- // We need to swap the cursors to match the now-flipped query ordering.
294
- const startAt = queryImpl . endAt
295
- ? new Bound ( queryImpl . endAt . position , queryImpl . endAt . inclusive )
296
- : null ;
297
- const endAt = queryImpl . startAt
298
- ? new Bound ( queryImpl . startAt . position , queryImpl . startAt . inclusive )
299
- : null ;
300
-
301
- // Now return as a LimitType.First query.
302
- queryImpl . memoizedTarget = newTarget (
303
- queryImpl . path ,
304
- queryImpl . collectionGroup ,
305
- orderBys ,
306
- queryImpl . filters ,
307
- queryImpl . limit ,
308
- startAt ,
309
- endAt
310
- ) ;
311
- }
284
+ return queryImpl . memoizedTarget ;
285
+ }
286
+
287
+ /**
288
+ * Converts this `Query` instance to it's corresponding `Target` representation,
289
+ * for use within an aggregate query.
290
+ */
291
+ export function queryToAggregateTarget ( query : Query ) : Target {
292
+ const queryImpl = debugCast ( query , QueryImpl ) ;
293
+
294
+ if ( ! queryImpl . memoizedAggregateTarget ) {
295
+ // Do not include implicit order-bys for aggregate queries.
296
+ queryImpl . memoizedAggregateTarget = _queryToTarget (
297
+ queryImpl ,
298
+ query . explicitOrderBy
299
+ ) ;
300
+ }
301
+
302
+ return queryImpl . memoizedAggregateTarget ;
303
+ }
304
+
305
+ export function _queryToTarget (
306
+ queryImpl : QueryImpl ,
307
+ orderBys : OrderBy [ ]
308
+ ) : Target {
309
+ if ( queryImpl . limitType === LimitType . First ) {
310
+ return newTarget (
311
+ queryImpl . path ,
312
+ queryImpl . collectionGroup ,
313
+ orderBys ,
314
+ queryImpl . filters ,
315
+ queryImpl . limit ,
316
+ queryImpl . startAt ,
317
+ queryImpl . endAt
318
+ ) ;
319
+ } else {
320
+ // Flip the orderBy directions since we want the last results
321
+ orderBys = orderBys . map ( orderBy => {
322
+ const dir =
323
+ orderBy . dir === Direction . DESCENDING
324
+ ? Direction . ASCENDING
325
+ : Direction . DESCENDING ;
326
+ return new OrderBy ( orderBy . field , dir ) ;
327
+ } ) ;
328
+
329
+ // We need to swap the cursors to match the now-flipped query ordering.
330
+ const startAt = queryImpl . endAt
331
+ ? new Bound ( queryImpl . endAt . position , queryImpl . endAt . inclusive )
332
+ : null ;
333
+ const endAt = queryImpl . startAt
334
+ ? new Bound ( queryImpl . startAt . position , queryImpl . startAt . inclusive )
335
+ : null ;
336
+
337
+ // Now return as a LimitType.First query.
338
+ return newTarget (
339
+ queryImpl . path ,
340
+ queryImpl . collectionGroup ,
341
+ orderBys ,
342
+ queryImpl . filters ,
343
+ queryImpl . limit ,
344
+ startAt ,
345
+ endAt
346
+ ) ;
312
347
}
313
- return queryImpl . memoizedTarget ! ;
314
348
}
315
349
316
350
export function queryWithAddedFilter ( query : Query , filter : Filter ) : Query {
@@ -461,13 +495,13 @@ function queryMatchesPathAndCollectionGroup(
461
495
* in the results.
462
496
*/
463
497
function queryMatchesOrderBy ( query : Query , doc : Document ) : boolean {
464
- // We must use `queryOrderBy ()` to get the list of all orderBys (both implicit and explicit).
498
+ // We must use `queryNormalizedOrderBy ()` to get the list of all orderBys (both implicit and explicit).
465
499
// Note that for OR queries, orderBy applies to all disjunction terms and implicit orderBys must
466
500
// be taken into account. For example, the query "a > 1 || b==1" has an implicit "orderBy a" due
467
501
// to the inequality, and is evaluated as "a > 1 orderBy a || b==1 orderBy a".
468
502
// A document with content of {b:1} matches the filters, but does not match the orderBy because
469
503
// it's missing the field 'a'.
470
- for ( const orderBy of queryOrderBy ( query ) ) {
504
+ for ( const orderBy of queryNormalizedOrderBy ( query ) ) {
471
505
// order by key always matches
472
506
if ( ! orderBy . field . isKeyField ( ) && doc . data . field ( orderBy . field ) === null ) {
473
507
return false ;
@@ -489,13 +523,13 @@ function queryMatchesFilters(query: Query, doc: Document): boolean {
489
523
function queryMatchesBounds ( query : Query , doc : Document ) : boolean {
490
524
if (
491
525
query . startAt &&
492
- ! boundSortsBeforeDocument ( query . startAt , queryOrderBy ( query ) , doc )
526
+ ! boundSortsBeforeDocument ( query . startAt , queryNormalizedOrderBy ( query ) , doc )
493
527
) {
494
528
return false ;
495
529
}
496
530
if (
497
531
query . endAt &&
498
- ! boundSortsAfterDocument ( query . endAt , queryOrderBy ( query ) , doc )
532
+ ! boundSortsAfterDocument ( query . endAt , queryNormalizedOrderBy ( query ) , doc )
499
533
) {
500
534
return false ;
501
535
}
@@ -526,7 +560,7 @@ export function newQueryComparator(
526
560
) : ( d1 : Document , d2 : Document ) => number {
527
561
return ( d1 : Document , d2 : Document ) : number => {
528
562
let comparedOnKeyField = false ;
529
- for ( const orderBy of queryOrderBy ( query ) ) {
563
+ for ( const orderBy of queryNormalizedOrderBy ( query ) ) {
530
564
const comp = compareDocs ( orderBy , d1 , d2 ) ;
531
565
if ( comp !== 0 ) {
532
566
return comp ;
0 commit comments