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