@@ -20,7 +20,8 @@ import { DocumentKey } from '../model/document_key';
20
20
import {
21
21
FieldIndex ,
22
22
fieldIndexGetArraySegment ,
23
- fieldIndexGetDirectionalSegments
23
+ fieldIndexGetDirectionalSegments ,
24
+ IndexKind
24
25
} from '../model/field_index' ;
25
26
import { FieldPath , ResourcePath } from '../model/path' ;
26
27
import {
@@ -304,70 +305,25 @@ export function targetGetLowerBound(
304
305
// For each segment, retrieve a lower bound if there is a suitable filter or
305
306
// startAt.
306
307
for ( const segment of fieldIndexGetDirectionalSegments ( fieldIndex ) ) {
307
- let segmentValue : ProtoValue | undefined = undefined ;
308
- let segmentInclusive = true ;
309
-
310
- // Process all filters to find a value for the current field segment
311
- for ( const fieldFilter of targetGetFieldFiltersForPath (
312
- target ,
313
- segment . fieldPath
314
- ) ) {
315
- let filterValue : ProtoValue | undefined = undefined ;
316
- let filterInclusive = true ;
317
-
318
- switch ( fieldFilter . op ) {
319
- case Operator . LESS_THAN :
320
- case Operator . LESS_THAN_OR_EQUAL :
321
- filterValue = valuesGetLowerBound ( fieldFilter . value ) ;
322
- break ;
323
- case Operator . EQUAL :
324
- case Operator . IN :
325
- case Operator . GREATER_THAN_OR_EQUAL :
326
- filterValue = fieldFilter . value ;
327
- break ;
328
- case Operator . GREATER_THAN :
329
- filterValue = fieldFilter . value ;
330
- filterInclusive = false ;
331
- break ;
332
- case Operator . NOT_EQUAL :
333
- case Operator . NOT_IN :
334
- filterValue = MIN_VALUE ;
335
- break ;
336
- default :
337
- // Remaining filters cannot be used as lower bounds.
338
- }
339
-
340
- if ( valuesMax ( segmentValue , filterValue ) === filterValue ) {
341
- segmentValue = filterValue ;
342
- segmentInclusive = filterInclusive ;
343
- }
344
- }
345
-
346
- // If there is a startAt bound, compare the values against the existing
347
- // boundary to see if we can narrow the scope.
348
- if ( target . startAt !== null ) {
349
- for ( let i = 0 ; i < target . orderBy . length ; ++ i ) {
350
- const orderBy = target . orderBy [ i ] ;
351
- if ( orderBy . field . isEqual ( segment . fieldPath ) ) {
352
- const cursorValue = target . startAt . position [ i ] ;
353
- if ( valuesMax ( segmentValue , cursorValue ) === cursorValue ) {
354
- segmentValue = cursorValue ;
355
- segmentInclusive = target . startAt . inclusive ;
356
- }
357
- break ;
358
- }
359
- }
360
- }
361
-
362
- if ( segmentValue === undefined ) {
308
+ const segmentBound =
309
+ segment . kind === IndexKind . ASCENDING
310
+ ? targetGetLowerBoundForField ( target , segment . fieldPath , target . startAt )
311
+ : targetGetUpperBoundForField (
312
+ target ,
313
+ segment . fieldPath ,
314
+ target . startAt
315
+ ) ;
316
+
317
+ if ( ! segmentBound . value ) {
363
318
// No lower bound exists
364
319
return null ;
365
320
}
366
- values . push ( segmentValue ) ;
367
- inclusive &&= segmentInclusive ;
321
+ values . push ( segmentBound . value ) ;
322
+ inclusive &&= segmentBound . inclusive ;
368
323
}
369
324
return new Bound ( values , inclusive ) ;
370
325
}
326
+
371
327
/**
372
328
* Returns an upper bound of field values that can be used as an ending point
373
329
* when scanning the index defined by `fieldIndex`. Returns `null` if no
@@ -383,71 +339,149 @@ export function targetGetUpperBound(
383
339
// For each segment, retrieve an upper bound if there is a suitable filter or
384
340
// endAt.
385
341
for ( const segment of fieldIndexGetDirectionalSegments ( fieldIndex ) ) {
386
- let segmentValue : ProtoValue | undefined = undefined ;
387
- let segmentInclusive = true ;
342
+ const segmentBound =
343
+ segment . kind === IndexKind . ASCENDING
344
+ ? targetGetUpperBoundForField ( target , segment . fieldPath , target . endAt )
345
+ : targetGetLowerBoundForField ( target , segment . fieldPath , target . endAt ) ;
388
346
389
- // Process all filters to find a value for the current field segment
390
- for ( const fieldFilter of targetGetFieldFiltersForPath (
391
- target ,
392
- segment . fieldPath
393
- ) ) {
394
- let filterValue : ProtoValue | undefined = undefined ;
395
- let filterInclusive = true ;
347
+ if ( ! segmentBound . value ) {
348
+ // No upper bound exists
349
+ return null ;
350
+ }
351
+ values . push ( segmentBound . value ) ;
352
+ inclusive &&= segmentBound . inclusive ;
353
+ }
396
354
397
- switch ( fieldFilter . op ) {
398
- case Operator . GREATER_THAN_OR_EQUAL :
399
- case Operator . GREATER_THAN :
400
- filterValue = valuesGetUpperBound ( fieldFilter . value ) ;
401
- filterInclusive = false ;
402
- break ;
403
- case Operator . EQUAL :
404
- case Operator . IN :
405
- case Operator . LESS_THAN_OR_EQUAL :
406
- filterValue = fieldFilter . value ;
407
- break ;
408
- case Operator . LESS_THAN :
409
- filterValue = fieldFilter . value ;
410
- filterInclusive = false ;
411
- break ;
412
- case Operator . NOT_EQUAL :
413
- case Operator . NOT_IN :
414
- filterValue = MAX_VALUE ;
415
- break ;
416
- default :
417
- // Remaining filters cannot be used as upper bounds.
418
- }
355
+ return new Bound ( values , inclusive ) ;
356
+ }
419
357
420
- if ( valuesMin ( segmentValue , filterValue ) === filterValue ) {
421
- segmentValue = filterValue ;
422
- segmentInclusive = filterInclusive ;
423
- }
358
+ /**
359
+ * Returns the value to use as the lower bound for ascending index segment at
360
+ * the provided `fieldPath` (or the upper bound for an descending segment).
361
+ */
362
+ function targetGetLowerBoundForField (
363
+ target : Target ,
364
+ fieldPath : FieldPath ,
365
+ bound : Bound | null
366
+ ) : { value : ProtoValue | undefined ; inclusive : boolean } {
367
+ let value : ProtoValue | undefined = undefined ;
368
+ let inclusive = true ;
369
+
370
+ // Process all filters to find a value for the current field segment
371
+ for ( const fieldFilter of targetGetFieldFiltersForPath ( target , fieldPath ) ) {
372
+ let filterValue : ProtoValue | undefined = undefined ;
373
+ let filterInclusive = true ;
374
+
375
+ switch ( fieldFilter . op ) {
376
+ case Operator . LESS_THAN :
377
+ case Operator . LESS_THAN_OR_EQUAL :
378
+ filterValue = valuesGetLowerBound ( fieldFilter . value ) ;
379
+ break ;
380
+ case Operator . EQUAL :
381
+ case Operator . IN :
382
+ case Operator . GREATER_THAN_OR_EQUAL :
383
+ filterValue = fieldFilter . value ;
384
+ break ;
385
+ case Operator . GREATER_THAN :
386
+ filterValue = fieldFilter . value ;
387
+ filterInclusive = false ;
388
+ break ;
389
+ case Operator . NOT_EQUAL :
390
+ case Operator . NOT_IN :
391
+ filterValue = MIN_VALUE ;
392
+ break ;
393
+ default :
394
+ // Remaining filters cannot be used as lower bounds.
424
395
}
425
396
426
- // If there is a endAt bound, compare the values against the existing
427
- // boundary to see if we can narrow the scope.
428
- if ( target . endAt !== null ) {
429
- for ( let i = 0 ; i < target . orderBy . length ; ++ i ) {
430
- const orderBy = target . orderBy [ i ] ;
431
- if ( orderBy . field . isEqual ( segment . fieldPath ) ) {
432
- const cursorValue = target . endAt . position [ i ] ;
433
- if ( valuesMin ( segmentValue , cursorValue ) === cursorValue ) {
434
- segmentValue = cursorValue ;
435
- segmentInclusive = target . endAt . inclusive ;
436
- }
437
- break ;
397
+ if ( valuesMax ( value , filterValue ) === filterValue ) {
398
+ value = filterValue ;
399
+ inclusive = filterInclusive ;
400
+ }
401
+ }
402
+
403
+ // If there is an additional bound, compare the values against the existing
404
+ // range to see if we can narrow the scope.
405
+ if ( bound !== null ) {
406
+ for ( let i = 0 ; i < target . orderBy . length ; ++ i ) {
407
+ const orderBy = target . orderBy [ i ] ;
408
+ if ( orderBy . field . isEqual ( fieldPath ) ) {
409
+ const cursorValue = bound . position [ i ] ;
410
+ if ( valuesMax ( value , cursorValue ) === cursorValue ) {
411
+ value = cursorValue ;
412
+ inclusive = bound . inclusive ;
438
413
}
414
+ break ;
439
415
}
440
416
}
417
+ }
441
418
442
- if ( segmentValue === undefined ) {
443
- // No upper bound exists
444
- return null ;
419
+ return { value, inclusive } ;
420
+ }
421
+
422
+ /**
423
+ * Returns the value to use as the upper bound for ascending index segment at
424
+ * the provided `fieldPath` (or the lower bound for an descending segment).
425
+ */
426
+ function targetGetUpperBoundForField (
427
+ target : Target ,
428
+ fieldPath : FieldPath ,
429
+ bound : Bound | null
430
+ ) : { value : ProtoValue | undefined ; inclusive : boolean } {
431
+ let value : ProtoValue | undefined = undefined ;
432
+ let inclusive = true ;
433
+
434
+ // Process all filters to find a value for the current field segment
435
+ for ( const fieldFilter of targetGetFieldFiltersForPath ( target , fieldPath ) ) {
436
+ let filterValue : ProtoValue | undefined = undefined ;
437
+ let filterInclusive = true ;
438
+
439
+ switch ( fieldFilter . op ) {
440
+ case Operator . GREATER_THAN_OR_EQUAL :
441
+ case Operator . GREATER_THAN :
442
+ filterValue = valuesGetUpperBound ( fieldFilter . value ) ;
443
+ filterInclusive = false ;
444
+ break ;
445
+ case Operator . EQUAL :
446
+ case Operator . IN :
447
+ case Operator . LESS_THAN_OR_EQUAL :
448
+ filterValue = fieldFilter . value ;
449
+ break ;
450
+ case Operator . LESS_THAN :
451
+ filterValue = fieldFilter . value ;
452
+ filterInclusive = false ;
453
+ break ;
454
+ case Operator . NOT_EQUAL :
455
+ case Operator . NOT_IN :
456
+ filterValue = MAX_VALUE ;
457
+ break ;
458
+ default :
459
+ // Remaining filters cannot be used as upper bounds.
460
+ }
461
+
462
+ if ( valuesMin ( value , filterValue ) === filterValue ) {
463
+ value = filterValue ;
464
+ inclusive = filterInclusive ;
445
465
}
446
- values . push ( segmentValue ) ;
447
- inclusive &&= segmentInclusive ;
448
466
}
449
467
450
- return new Bound ( values , inclusive ) ;
468
+ // If there is an additional bound, compare the values against the existing
469
+ // range to see if we can narrow the scope.
470
+ if ( bound !== null ) {
471
+ for ( let i = 0 ; i < target . orderBy . length ; ++ i ) {
472
+ const orderBy = target . orderBy [ i ] ;
473
+ if ( orderBy . field . isEqual ( fieldPath ) ) {
474
+ const cursorValue = bound . position [ i ] ;
475
+ if ( valuesMin ( value , cursorValue ) === cursorValue ) {
476
+ value = cursorValue ;
477
+ inclusive = bound . inclusive ;
478
+ }
479
+ break ;
480
+ }
481
+ }
482
+ }
483
+
484
+ return { value, inclusive } ;
451
485
}
452
486
453
487
export abstract class Filter {
0 commit comments