Skip to content

Commit a4b69fc

Browse files
authored
Merge f90ab2e into 465e8df
2 parents 465e8df + f90ab2e commit a4b69fc

File tree

13 files changed

+149
-54
lines changed

13 files changed

+149
-54
lines changed

packages/firestore/src/core/sync_engine_impl.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ import { primitiveComparator } from '../util/misc';
7171
import { ObjectMap } from '../util/obj_map';
7272
import { Deferred } from '../util/promise';
7373
import { SortedMap } from '../util/sorted_map';
74-
import { SortedSet } from '../util/sorted_set';
7574
import { BATCHID_UNKNOWN } from '../util/types';
7675

7776
import {
@@ -639,7 +638,9 @@ export async function syncEngineRejectListen(
639638
const event = new RemoteEvent(
640639
SnapshotVersion.min(),
641640
/* targetChanges= */ new Map<TargetId, TargetChange>(),
642-
/* targetMismatches= */ new SortedSet<TargetId>(primitiveComparator),
641+
/* targetMismatches= */ new SortedMap<TargetId, TargetPurpose>(
642+
primitiveComparator
643+
),
643644
documentUpdates,
644645
resolvedLimboDocuments
645646
);

packages/firestore/src/local/local_store_impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ export function localStoreApplyRemoteEventToLocalCache(
601601
let newTargetData = oldTargetData.withSequenceNumber(
602602
txn.currentSequenceNumber
603603
);
604-
if (remoteEvent.targetMismatches.has(targetId)) {
604+
if (remoteEvent.targetMismatches.get(targetId) !== null) {
605605
newTargetData = newTargetData
606606
.withResumeToken(
607607
ByteString.EMPTY_BYTE_STRING,

packages/firestore/src/local/target_data.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,17 @@ export const enum TargetPurpose {
2626
Listen,
2727

2828
/**
29-
* The query target was used to refill a query after an existence filter mismatch.
29+
* The query target was used to refill a query after an existence filter
30+
* mismatch.
3031
*/
3132
ExistenceFilterMismatch,
3233

34+
/**
35+
* The query target was used if the query is the result of a false positive in
36+
* the bloom filter.
37+
*/
38+
ExistenceFilterMismatchBloom,
39+
3340
/** The query target was used to resolve a limbo document. */
3441
LimboResolution
3542
}

packages/firestore/src/remote/remote_event.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717

1818
import { SnapshotVersion } from '../core/snapshot_version';
1919
import { TargetId } from '../core/types';
20+
import { TargetPurpose } from '../local/target_data';
2021
import {
2122
documentKeySet,
2223
DocumentKeySet,
2324
mutableDocumentMap,
24-
MutableDocumentMap,
25-
targetIdSet
25+
MutableDocumentMap
2626
} from '../model/collections';
2727
import { ByteString } from '../util/byte_string';
28-
import { SortedSet } from '../util/sorted_set';
28+
import { primitiveComparator } from '../util/misc';
29+
import { SortedMap } from '../util/sorted_map';
2930

3031
/**
3132
* An event from the RemoteStore. It is split into targetChanges (changes to the
@@ -43,10 +44,11 @@ export class RemoteEvent {
4344
*/
4445
readonly targetChanges: Map<TargetId, TargetChange>,
4546
/**
46-
* A set of targets that is known to be inconsistent. Listens for these
47-
* targets should be re-established without resume tokens.
47+
* A map of targets that is known to be inconsistent, and the purpose for
48+
* re-listening. Listens for these targets should be re-established without
49+
* resume tokens.
4850
*/
49-
readonly targetMismatches: SortedSet<TargetId>,
51+
readonly targetMismatches: SortedMap<TargetId, TargetPurpose>,
5052
/**
5153
* A set of which documents have changed or been deleted, along with the
5254
* doc's new values (if not deleted).
@@ -82,7 +84,7 @@ export class RemoteEvent {
8284
return new RemoteEvent(
8385
SnapshotVersion.min(),
8486
targetChanges,
85-
targetIdSet(),
87+
new SortedMap<TargetId, TargetPurpose>(primitiveComparator),
8688
mutableDocumentMap(),
8789
documentKeySet()
8890
);

packages/firestore/src/remote/remote_store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
localStoreGetNextMutationBatch
2525
} from '../local/local_store_impl';
2626
import { isIndexedDbTransactionError } from '../local/simple_db';
27-
import { TargetData, TargetPurpose } from '../local/target_data';
27+
import { TargetData } from '../local/target_data';
2828
import { MutationResult } from '../model/mutation';
2929
import { MutationBatch, MutationBatchResult } from '../model/mutation_batch';
3030
import { debugAssert, debugCast } from '../util/assert';
@@ -587,7 +587,7 @@ function raiseWatchSnapshot(
587587

588588
// Re-establish listens for the targets that have been invalidated by
589589
// existence filter mismatches.
590-
remoteEvent.targetMismatches.forEach(targetId => {
590+
remoteEvent.targetMismatches.forEach((targetId, targetPurpose) => {
591591
const targetData = remoteStoreImpl.listenTargets.get(targetId);
592592
if (!targetData) {
593593
// A watched target might have been removed already.
@@ -615,7 +615,7 @@ function raiseWatchSnapshot(
615615
const requestTargetData = new TargetData(
616616
targetData.target,
617617
targetId,
618-
TargetPurpose.ExistenceFilterMismatch,
618+
targetPurpose,
619619
targetData.sequenceNumber
620620
);
621621
sendWatchRequest(remoteStoreImpl, requestTargetData);

packages/firestore/src/remote/serializer.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ export function toListenRequestLabels(
10081008
serializer: JsonProtoSerializer,
10091009
targetData: TargetData
10101010
): ProtoApiClientObjectMap<string> | null {
1011-
const value = toLabel(serializer, targetData.purpose);
1011+
const value = toLabel(targetData.purpose);
10121012
if (value == null) {
10131013
return null;
10141014
} else {
@@ -1018,15 +1018,14 @@ export function toListenRequestLabels(
10181018
}
10191019
}
10201020

1021-
function toLabel(
1022-
serializer: JsonProtoSerializer,
1023-
purpose: TargetPurpose
1024-
): string | null {
1021+
export function toLabel(purpose: TargetPurpose): string | null {
10251022
switch (purpose) {
10261023
case TargetPurpose.Listen:
10271024
return null;
10281025
case TargetPurpose.ExistenceFilterMismatch:
10291026
return 'existence-filter-mismatch';
1027+
case TargetPurpose.ExistenceFilterMismatchBloom:
1028+
return 'existence-filter-mismatch-bloom';
10301029
case TargetPurpose.LimboResolution:
10311030
return 'limbo-document';
10321031
default:

packages/firestore/src/remote/watch_change.ts

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ export const enum WatchTargetChangeState {
8787
Reset
8888
}
8989

90+
const enum BloomFilterApplicationStatus {
91+
Success,
92+
Skipped,
93+
FalsePositive
94+
}
9095
export class WatchTargetChange {
9196
constructor(
9297
/** What kind of change occurred to the watch target. */
@@ -280,11 +285,13 @@ export class WatchChangeAggregator {
280285
private pendingDocumentTargetMapping = documentTargetMap();
281286

282287
/**
283-
* A list of targets with existence filter mismatches. These targets are
288+
* A map of targets with existence filter mismatches. These targets are
284289
* known to be inconsistent and their listens needs to be re-established by
285290
* RemoteStore.
286291
*/
287-
private pendingTargetResets = new SortedSet<TargetId>(primitiveComparator);
292+
private pendingTargetResets = new SortedMap<TargetId, TargetPurpose>(
293+
primitiveComparator
294+
);
288295

289296
/**
290297
* Processes and adds the DocumentWatchChange to the current set of changes.
@@ -422,31 +429,40 @@ export class WatchChangeAggregator {
422429
// raise a snapshot with `isFromCache:true`.
423430
if (currentSize !== expectedCount) {
424431
// Apply bloom filter to identify and mark removed documents.
425-
const bloomFilterApplied = this.applyBloomFilter(
426-
watchChange,
427-
currentSize
428-
);
429-
if (!bloomFilterApplied) {
432+
const status = this.applyBloomFilter(watchChange, currentSize);
433+
434+
if (status !== BloomFilterApplicationStatus.Success) {
430435
// If bloom filter application fails, we reset the mapping and
431436
// trigger re-run of the query.
432437
this.resetTarget(targetId);
433-
this.pendingTargetResets = this.pendingTargetResets.add(targetId);
438+
439+
const purpose: TargetPurpose =
440+
status === BloomFilterApplicationStatus.FalsePositive
441+
? TargetPurpose.ExistenceFilterMismatchBloom
442+
: TargetPurpose.ExistenceFilterMismatch;
443+
this.pendingTargetResets = this.pendingTargetResets.insert(
444+
targetId,
445+
purpose
446+
);
434447
}
435448
}
436449
}
437450
}
438451
}
439452

440-
/** Returns whether a bloom filter removed the deleted documents successfully. */
453+
/**
454+
* Apply bloom filter to remove the deleted documents, and return the
455+
* application status.
456+
*/
441457
private applyBloomFilter(
442458
watchChange: ExistenceFilterChange,
443459
currentCount: number
444-
): boolean {
460+
): BloomFilterApplicationStatus {
445461
const { unchangedNames, count: expectedCount } =
446462
watchChange.existenceFilter;
447463

448464
if (!unchangedNames || !unchangedNames.bits) {
449-
return false;
465+
return BloomFilterApplicationStatus.Skipped;
450466
}
451467

452468
const {
@@ -464,7 +480,7 @@ export class WatchChangeAggregator {
464480
err.message +
465481
'); ignoring the bloom filter and falling back to full re-query.'
466482
);
467-
return false;
483+
return BloomFilterApplicationStatus.Skipped;
468484
} else {
469485
throw err;
470486
}
@@ -480,15 +496,23 @@ export class WatchChangeAggregator {
480496
} else {
481497
logWarn('Applying bloom filter failed: ', err);
482498
}
483-
return false;
499+
return BloomFilterApplicationStatus.Skipped;
500+
}
501+
502+
if (bloomFilter.bitCount === 0) {
503+
return BloomFilterApplicationStatus.Skipped;
484504
}
485505

486506
const removedDocumentCount = this.filterRemovedDocuments(
487507
watchChange.targetId,
488508
bloomFilter
489509
);
490510

491-
return expectedCount === currentCount - removedDocumentCount;
511+
if (expectedCount !== currentCount - removedDocumentCount) {
512+
return BloomFilterApplicationStatus.FalsePositive;
513+
}
514+
515+
return BloomFilterApplicationStatus.Success;
492516
}
493517

494518
/**
@@ -599,7 +623,9 @@ export class WatchChangeAggregator {
599623

600624
this.pendingDocumentUpdates = mutableDocumentMap();
601625
this.pendingDocumentTargetMapping = documentTargetMap();
602-
this.pendingTargetResets = new SortedSet<TargetId>(primitiveComparator);
626+
this.pendingTargetResets = new SortedMap<TargetId, TargetPurpose>(
627+
primitiveComparator
628+
);
603629

604630
return remoteEvent;
605631
}

packages/firestore/test/integration/api/query.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,8 +2061,8 @@ apiDescribe('Queries', (persistence: boolean) => {
20612061
});
20622062
});
20632063

2064-
// TODO(Mila): Skip the test when using emulator as there is a bug related to
2065-
// sending existence filter in response: b/270731363. Remove the condition
2064+
// TODO(Mila): Skip the test when using emulator as there is a bug related to
2065+
// sending existence filter in response: b/270731363. Remove the condition
20662066
// here once the bug is resolved.
20672067
// eslint-disable-next-line no-restricted-properties
20682068
(USE_EMULATOR ? it.skip : it)(

packages/firestore/test/unit/remote/remote_event.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,9 @@ describe('RemoteEvent', () => {
431431
expectTargetChangeEquals(event.targetChanges.get(1)!, expected);
432432
});
433433

434+
// TODO(b/272564458): Add test cases for existence filter with bloom filter,
435+
// one will skip the re-query, one will yield false positive result and clears
436+
// target mapping.
434437
it('existence filters clears target mapping', () => {
435438
const targets = listens(1, 2);
436439

@@ -459,6 +462,9 @@ describe('RemoteEvent', () => {
459462
event = aggregator.createRemoteEvent(version(3));
460463
expect(event.documentUpdates.size).to.equal(0);
461464
expect(event.targetMismatches.size).to.equal(1);
465+
expect(event.targetMismatches.get(1)).to.equal(
466+
TargetPurpose.ExistenceFilterMismatch
467+
);
462468
expect(event.targetChanges.size).to.equal(1);
463469

464470
const expected = updateMapping(
@@ -499,6 +505,9 @@ describe('RemoteEvent', () => {
499505
const event = aggregator.createRemoteEvent(version(3));
500506
expect(event.documentUpdates.size).to.equal(1);
501507
expect(event.targetMismatches.size).to.equal(1);
508+
expect(event.targetMismatches.get(1)).to.equal(
509+
TargetPurpose.ExistenceFilterMismatch
510+
);
502511
expect(event.targetChanges.get(1)!.current).to.be.false;
503512
});
504513

packages/firestore/test/unit/specs/existence_filter_spec.test.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { newQueryForPath } from '../../../src/core/query';
19+
import { TargetPurpose } from '../../../src/local/target_data';
1920
import { Code } from '../../../src/util/error';
2021
import {
2122
deletedDoc,
@@ -305,7 +306,11 @@ describeSpec('Existence Filters:', [], () => {
305306
// BloomFilter correctly identifies docC is deleted, but yields false
306307
// positive results for docB. Re-run query is triggered.
307308
.expectEvents(query1, { fromCache: true })
308-
.expectActiveTargets({ query: query1, resumeToken: '' })
309+
.expectActiveTargets({
310+
query: query1,
311+
resumeToken: '',
312+
targetPurpose: TargetPurpose.ExistenceFilterMismatchBloom
313+
})
309314
);
310315
}
311316
);
@@ -394,7 +399,11 @@ describeSpec('Existence Filters:', [], () => {
394399
.watchSnapshots(2000)
395400
// Re-run query is triggered.
396401
.expectEvents(query1, { fromCache: true })
397-
.expectActiveTargets({ query: query1, resumeToken: '' })
402+
.expectActiveTargets({
403+
query: query1,
404+
resumeToken: '',
405+
targetPurpose: TargetPurpose.ExistenceFilterMismatch
406+
})
398407
);
399408
}
400409
);
@@ -424,7 +433,11 @@ describeSpec('Existence Filters:', [], () => {
424433
.watchSnapshots(2000)
425434
// Re-run query is triggered.
426435
.expectEvents(query1, { fromCache: true })
427-
.expectActiveTargets({ query: query1, resumeToken: '' })
436+
.expectActiveTargets({
437+
query: query1,
438+
resumeToken: '',
439+
targetPurpose: TargetPurpose.ExistenceFilterMismatch
440+
})
428441
);
429442
}
430443
);
@@ -452,7 +465,11 @@ describeSpec('Existence Filters:', [], () => {
452465
.watchSnapshots(2000)
453466
// Re-run query is triggered.
454467
.expectEvents(query1, { fromCache: true })
455-
.expectActiveTargets({ query: query1, resumeToken: '' })
468+
.expectActiveTargets({
469+
query: query1,
470+
resumeToken: '',
471+
targetPurpose: TargetPurpose.ExistenceFilterMismatch
472+
})
456473
);
457474
});
458475

0 commit comments

Comments
 (0)