15
15
* limitations under the License.
16
16
*/
17
17
18
+ import { DatabaseId } from '../core/database_info' ;
18
19
import { SnapshotVersion } from '../core/snapshot_version' ;
19
20
import { targetIsDocumentTarget } from '../core/target' ;
20
21
import { TargetId } from '../core/types' ;
@@ -29,6 +30,7 @@ import { MutableDocument } from '../model/document';
29
30
import { DocumentKey } from '../model/document_key' ;
30
31
import { normalizeByteString } from '../model/normalize' ;
31
32
import { debugAssert , fail , hardAssert } from '../util/assert' ;
33
+ import { Base64DecodeError } from '../util/base64_decode_error' ;
32
34
import { ByteString } from '../util/byte_string' ;
33
35
import { FirestoreError } from '../util/error' ;
34
36
import { logDebug , logWarn } from '../util/log' ;
@@ -253,6 +255,11 @@ export interface TargetMetadataProvider {
253
255
* has become inactive
254
256
*/
255
257
getTargetDataForTarget ( targetId : TargetId ) : TargetData | null ;
258
+
259
+ /**
260
+ * Returns the database ID of the Firestore instance.
261
+ */
262
+ getDatabaseId ( ) : DatabaseId ;
256
263
}
257
264
258
265
const LOG_TAG = 'WatchChangeAggregator' ;
@@ -416,8 +423,7 @@ export class WatchChangeAggregator {
416
423
if ( currentSize !== expectedCount ) {
417
424
// Apply bloom filter to identify and mark removed documents.
418
425
const bloomFilterApplied = this . applyBloomFilter (
419
- watchChange . existenceFilter ,
420
- targetId ,
426
+ watchChange ,
421
427
currentSize
422
428
) ;
423
429
if ( ! bloomFilterApplied ) {
@@ -433,12 +439,11 @@ export class WatchChangeAggregator {
433
439
434
440
/** Returns whether a bloom filter removed the deleted documents successfully. */
435
441
private applyBloomFilter (
436
- existenceFilter : ExistenceFilter ,
437
- targetId : number ,
442
+ watchChange : ExistenceFilterChange ,
438
443
currentCount : number
439
444
) : boolean {
440
- const unchangedNames = existenceFilter . unchangedNames ;
441
- const expectedCount = existenceFilter . count ;
445
+ const { unchangedNames, count : expectedCount } =
446
+ watchChange . existenceFilter ;
442
447
443
448
if ( ! unchangedNames || ! unchangedNames . bits ) {
444
449
return false ;
@@ -449,17 +454,22 @@ export class WatchChangeAggregator {
449
454
hashCount = 0
450
455
} = unchangedNames ;
451
456
452
- // TODO(Mila): Remove this validation, add try catch to normalizeByteString.
453
- if ( typeof bitmap === 'string' ) {
454
- const isValidBitmap = this . isValidBase64String ( bitmap ) ;
455
- if ( ! isValidBitmap ) {
456
- logWarn ( 'Invalid base64 string. Applying bloom filter failed.' ) ;
457
+ let normalizedBitmap : Uint8Array ;
458
+ try {
459
+ normalizedBitmap = normalizeByteString ( bitmap ) . toUint8Array ( ) ;
460
+ } catch ( err ) {
461
+ if ( err instanceof Base64DecodeError ) {
462
+ logWarn (
463
+ 'Decoding the base64 bloom filter in existence filter failed (' +
464
+ err . message +
465
+ '); ignoring the bloom filter and falling back to full re-query.'
466
+ ) ;
457
467
return false ;
468
+ } else {
469
+ throw err ;
458
470
}
459
471
}
460
472
461
- const normalizedBitmap = normalizeByteString ( bitmap ) . toUint8Array ( ) ;
462
-
463
473
let bloomFilter : BloomFilter ;
464
474
try {
465
475
// BloomFilter throws error if the inputs are invalid.
@@ -474,37 +484,36 @@ export class WatchChangeAggregator {
474
484
}
475
485
476
486
const removedDocumentCount = this . filterRemovedDocuments (
477
- bloomFilter ,
478
- targetId
487
+ watchChange . targetId ,
488
+ bloomFilter
479
489
) ;
480
490
481
491
return expectedCount === currentCount - removedDocumentCount ;
482
492
}
483
493
484
- // TODO(Mila): Move the validation into normalizeByteString.
485
- private isValidBase64String ( value : string ) : boolean {
486
- const regExp = new RegExp (
487
- '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'
488
- ) ;
489
- return regExp . test ( value ) ;
490
- }
491
-
492
494
/**
493
495
* Filter out removed documents based on bloom filter membership result and
494
496
* return number of documents removed.
495
497
*/
496
498
private filterRemovedDocuments (
497
- bloomFilter : BloomFilter ,
498
- targetId : number
499
+ targetId : number ,
500
+ bloomFilter : BloomFilter
499
501
) : number {
500
502
const existingKeys = this . metadataProvider . getRemoteKeysForTarget ( targetId ) ;
501
503
let removalCount = 0 ;
504
+
502
505
existingKeys . forEach ( key => {
503
- if ( ! bloomFilter . mightContain ( key . path . toFullPath ( ) ) ) {
506
+ const databaseId = this . metadataProvider . getDatabaseId ( ) ;
507
+ const documentPath = `projects/${ databaseId . projectId } /databases/${
508
+ databaseId . database
509
+ } /documents/${ key . path . canonicalString ( ) } `;
510
+
511
+ if ( ! bloomFilter . mightContain ( documentPath ) ) {
504
512
this . removeDocumentFromTarget ( targetId , key , /*updatedDocument=*/ null ) ;
505
513
removalCount ++ ;
506
514
}
507
515
} ) ;
516
+
508
517
return removalCount ;
509
518
}
510
519
0 commit comments