@@ -30,11 +30,7 @@ import {
30
30
} from '../core/target' ;
31
31
import { FirestoreIndexValueWriter } from '../index/firestore_index_value_writer' ;
32
32
import { IndexByteEncoder } from '../index/index_byte_encoder' ;
33
- import {
34
- compareByteArrays ,
35
- IndexEntry ,
36
- indexEntryComparator
37
- } from '../index/index_entry' ;
33
+ import { IndexEntry , indexEntryComparator } from '../index/index_entry' ;
38
34
import {
39
35
documentKeySet ,
40
36
DocumentKeySet ,
@@ -325,7 +321,7 @@ export class IndexedDbIndexManager implements IndexManager {
325
321
lowerBoundInclusive : boolean ,
326
322
upperBounds : Uint8Array [ ] | null ,
327
323
upperBoundInclusive : boolean ,
328
- notInValues : Uint8Array [ ] | null
324
+ notInValues : Uint8Array [ ]
329
325
) : IDBKeyRange [ ] {
330
326
// The number of total index scans we union together. This is similar to a
331
327
// distributed normal form, but adapted for array values. We create a single
@@ -345,31 +341,46 @@ export class IndexedDbIndexManager implements IndexManager {
345
341
const arrayValue = arrayValues
346
342
? this . encodeSingleElement ( arrayValues [ i / scansPerArrayElement ] )
347
343
: EMPTY_VALUE ;
344
+
345
+ const lowerBound = lowerBounds
346
+ ? this . generateLowerBound (
347
+ indexId ,
348
+ arrayValue ,
349
+ lowerBounds [ i % scansPerArrayElement ] ,
350
+ lowerBoundInclusive
351
+ )
352
+ : this . generateEmptyBound ( indexId ) ;
353
+ const upperBound = upperBounds
354
+ ? this . generateUpperBound (
355
+ indexId ,
356
+ arrayValue ,
357
+ upperBounds [ i % scansPerArrayElement ] ,
358
+ upperBoundInclusive
359
+ )
360
+ : this . generateEmptyBound ( indexId + 1 ) ;
361
+
348
362
indexRanges . push (
349
- IDBKeyRange . bound (
350
- lowerBounds
351
- ? this . generateLowerBound (
363
+ ...this . createRange (
364
+ lowerBound ,
365
+ /* lowerInclusive= */ true ,
366
+ upperBound ,
367
+ /* upperInclusive= */ false ,
368
+ notInValues . map (
369
+ (
370
+ notIn // make non-nullable
371
+ ) =>
372
+ this . generateLowerBound (
352
373
indexId ,
353
374
arrayValue ,
354
- lowerBounds [ i % scansPerArrayElement ] ,
355
- lowerBoundInclusive
375
+ notIn ,
376
+ /* inclusive= */ true
356
377
)
357
- : this . generateEmptyBound ( indexId ) ,
358
- upperBounds
359
- ? this . generateUpperBound (
360
- indexId ,
361
- arrayValue ,
362
- upperBounds [ i % scansPerArrayElement ] ,
363
- upperBoundInclusive
364
- )
365
- : this . generateEmptyBound ( indexId + 1 ) ,
366
- /* lowerOpen= */ false ,
367
- /* upperOpen= */ true
378
+ )
368
379
)
369
380
) ;
370
381
}
371
382
372
- return this . applyNotIn ( indexRanges , notInValues ) ;
383
+ return indexRanges ;
373
384
}
374
385
375
386
/** Generates the lower bound for `arrayValue` and `directionalValue`. */
@@ -378,14 +389,14 @@ export class IndexedDbIndexManager implements IndexManager {
378
389
arrayValue : Uint8Array ,
379
390
directionalValue : Uint8Array ,
380
391
inclusive : boolean
381
- ) : DbIndexEntryKey {
382
- return [
392
+ ) : IndexEntry {
393
+ const entry = new IndexEntry (
383
394
indexId ,
384
- this . uid ,
395
+ DocumentKey . empty ( ) ,
385
396
arrayValue ,
386
- inclusive ? directionalValue : successor ( directionalValue ) ,
387
- ''
388
- ] ;
397
+ directionalValue
398
+ ) ;
399
+ return inclusive ? entry : entry . successor ( ) ;
389
400
}
390
401
391
402
/** Generates the upper bound for `arrayValue` and `directionalValue`. */
@@ -394,22 +405,27 @@ export class IndexedDbIndexManager implements IndexManager {
394
405
arrayValue : Uint8Array ,
395
406
directionalValue : Uint8Array ,
396
407
inclusive : boolean
397
- ) : DbIndexEntryKey {
398
- return [
408
+ ) : IndexEntry {
409
+ const entry = new IndexEntry (
399
410
indexId ,
400
- this . uid ,
411
+ DocumentKey . empty ( ) ,
401
412
arrayValue ,
402
- inclusive ? successor ( directionalValue ) : directionalValue ,
403
- ''
404
- ] ;
413
+ directionalValue
414
+ ) ;
415
+ return inclusive ? entry . successor ( ) : entry ;
405
416
}
406
417
407
418
/**
408
419
* Generates an empty bound that scopes the index scan to the current index
409
420
* and user.
410
421
*/
411
- private generateEmptyBound ( indexId : number ) : DbIndexEntryKey {
412
- return [ indexId , this . uid , EMPTY_VALUE , EMPTY_VALUE , '' ] ;
422
+ private generateEmptyBound ( indexId : number ) : IndexEntry {
423
+ return new IndexEntry (
424
+ indexId ,
425
+ DocumentKey . empty ( ) ,
426
+ EMPTY_VALUE ,
427
+ EMPTY_VALUE
428
+ ) ;
413
429
}
414
430
415
431
getFieldIndex (
@@ -475,9 +491,9 @@ export class IndexedDbIndexManager implements IndexManager {
475
491
fieldIndex : FieldIndex ,
476
492
target : Target ,
477
493
bound : ProtoValue [ ] | null
478
- ) : Uint8Array [ ] | null {
479
- if ( bound == null ) {
480
- return null ;
494
+ ) : Uint8Array [ ] {
495
+ if ( bound === null ) {
496
+ return [ ] ;
481
497
}
482
498
483
499
let encoders : IndexByteEncoder [ ] = [ ] ;
@@ -835,90 +851,69 @@ export class IndexedDbIndexManager implements IndexManager {
835
851
}
836
852
837
853
/**
838
- * Applies notIn and != filters by taking the provided ranges and excluding
854
+ * Returns a new set of IDB ranges that splits the existing range and excludes
839
855
* any values that match the `notInValue` from these ranges. As an example,
840
856
* '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
841
857
*/
842
- private applyNotIn (
843
- indexRanges : IDBKeyRange [ ] ,
844
- notInValues : Uint8Array [ ] | null
858
+ private createRange (
859
+ lower : IndexEntry ,
860
+ lowerInclusive : boolean ,
861
+ upper : IndexEntry ,
862
+ upperInclusive : boolean ,
863
+ notInValues : IndexEntry [ ]
845
864
) : IDBKeyRange [ ] {
846
- if ( ! notInValues ) {
847
- return indexRanges ;
848
- }
865
+ // The notIb values need to be sorted and unique so that we can return a
866
+ // sorted set of non-overlapping ranges.
867
+ notInValues = notInValues
868
+ . sort ( ( l , r ) => indexEntryComparator ( l , r ) )
869
+ . filter (
870
+ ( el , i , values ) => ! i || indexEntryComparator ( el , values [ i - 1 ] ) !== 0
871
+ ) ;
849
872
850
- // The values need to be sorted so that we can return a sorted set of
851
- // non-overlapping ranges.
852
- notInValues . sort ( ( l , r ) => compareByteArrays ( l , r ) ) ;
873
+ const bounds : IndexEntry [ ] = [ ] ;
874
+ bounds . push ( lower ) ;
875
+ for ( const notInValue of notInValues ) {
876
+ const sortsAfter = indexEntryComparator ( notInValue , lower ) ;
877
+ const sortsBefore = indexEntryComparator ( notInValue , upper ) ;
878
+
879
+ if ( sortsAfter > 0 && sortsBefore < 0 ) {
880
+ bounds . push ( notInValue ) ;
881
+ bounds . push ( notInValue . successor ( ) ) ;
882
+ } else if ( sortsAfter === 0 ) {
883
+ // The lowest value in the range is excluded
884
+ bounds [ 0 ] = lower . successor ( ) ;
885
+ } else if ( sortsBefore === 0 ) {
886
+ // The largest value in the range is excluded
887
+ upperInclusive = false ;
888
+ }
889
+ }
890
+ bounds . push ( upper ) ;
853
891
854
892
const ranges : IDBKeyRange [ ] = [ ] ;
855
-
856
- // Use the existing bounds and interleave the notIn values. This means
857
- // that we would split an existing range into multiple ranges that exclude
858
- // the values from any notIn filter.
859
- for ( const indexRange of indexRanges ) {
860
- debugAssert (
861
- indexRange . lower [ 0 ] === indexRange . upper [ 0 ] ,
862
- 'Index ID must match for index range'
893
+ for ( let i = 0 ; i < bounds . length ; i += 2 ) {
894
+ ranges . push (
895
+ IDBKeyRange . bound (
896
+ [
897
+ bounds [ i ] . indexId ,
898
+ this . uid ,
899
+ bounds [ i ] . arrayValue ,
900
+ bounds [ i ] . directionalValue ,
901
+ ''
902
+ ] ,
903
+ [
904
+ bounds [ i + 1 ] . indexId ,
905
+ this . uid ,
906
+ bounds [ i + 1 ] . arrayValue ,
907
+ bounds [ i + 1 ] . directionalValue ,
908
+ ''
909
+ ] ,
910
+ ! lowerInclusive ,
911
+ ! upperInclusive
912
+ )
863
913
) ;
864
- const lowerBound = new Uint8Array ( indexRange . lower [ 3 ] ) ;
865
- const upperBound = new Uint8Array ( indexRange . upper [ 3 ] ) ;
866
-
867
- let lastBound = lowerBound ;
868
- let lastOpen = indexRange . lowerOpen ;
869
-
870
- const bounds = [ ...notInValues , upperBound ] ;
871
- for ( const currentBound of bounds ) {
872
- // Verify that the range in the bound is sensible, as the bound may get
873
- // rejected otherwise
874
- const sortsAfter = compareByteArrays ( currentBound , lastBound ) ;
875
- const sortsBefore = compareByteArrays ( currentBound , upperBound ) ;
876
- if ( ( lastOpen ? sortsAfter > 0 : sortsAfter >= 0 ) && sortsBefore <= 0 ) {
877
- ranges . push (
878
- IDBKeyRange . bound (
879
- this . generateBound ( indexRange . lower , lastBound ) ,
880
- this . generateBound ( indexRange . upper , currentBound ) ,
881
- lastOpen ,
882
- /* upperOpen= */ indexRange . upperOpen
883
- )
884
- ) ;
885
- lastOpen = true ;
886
- lastBound = successor ( currentBound ) ;
887
- }
888
- }
889
914
}
890
915
return ranges ;
891
916
}
892
-
893
- /**
894
- * Generates the index entry that can be used to create the cutoff for `value`
895
- * on the provided index range.
896
- */
897
- private generateBound (
898
- existingRange : DbIndexEntryKey ,
899
- value : Uint8Array
900
- ) : DbIndexEntryKey {
901
- return [
902
- /* indexId= */ existingRange [ 0 ] ,
903
- /* userId= */ existingRange [ 1 ] ,
904
- /* arrayValue= */ existingRange [ 2 ] ,
905
- value ,
906
- /* documentKey= */ ''
907
- ] ;
908
- }
909
- }
910
-
911
- /** Creates a Uint8Array value that sorts immediately after `value`. */
912
- function successor ( value : Uint8Array ) : Uint8Array {
913
- if ( value . length === 0 || value [ value . length - 1 ] === 255 ) {
914
- const successor = new Uint8Array ( value . length + 1 ) ;
915
- successor . set ( value , 0 ) ;
916
- successor . set ( [ 0 ] , value . length ) ;
917
- return successor ;
918
- } else {
919
- ++ value [ value . length - 1 ] ;
920
- }
921
- return value ;
922
917
}
923
918
924
919
/**
0 commit comments