diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm index 7ab419f357d..4d51d93577e 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm @@ -101,6 +101,7 @@ using firebase::firestore::nanopb::MakeByteString; using firebase::firestore::nanopb::Message; using firebase::firestore::remote::BloomFilter; +using firebase::firestore::remote::BloomFilterParameters; using firebase::firestore::remote::DocumentWatchChange; using firebase::firestore::remote::ExistenceFilter; using firebase::firestore::remote::ExistenceFilterWatchChange; @@ -329,11 +330,24 @@ - (SnapshotVersion)parseVersion:(NSNumber *_Nullable)version { return Version(version.longLongValue); } -- (absl::optional)parseBloomFilter:(NSDictionary *_Nullable)bloomFilterProto { - // TODO(Mila): None of the ported spec tests actually has the bloom filter json string, so hard - // code a null value for now. Actual parsing code will be written in the next PR, where we can - // validate the parsing result. - return absl::nullopt; +- (absl::optional)parseBloomFilterParameter: + (NSDictionary *_Nullable)bloomFilterProto { + if (bloomFilterProto == nil) { + return absl::nullopt; + } + NSDictionary *bitsData = bloomFilterProto[@"bits"]; + + // Decode base64 string into uint8_t vector. If bitmap is not specified in proto, use default + // empty string. + NSString *bitmapEncoded = bitsData[@"bitmap"]; + std::string bitmapDecoded; + absl::Base64Unescape([bitmapEncoded cStringUsingEncoding:NSASCIIStringEncoding], &bitmapDecoded); + std::vector bitmap(bitmapDecoded.begin(), bitmapDecoded.end()); + + // If not specified in proto, default padding and hashCount to 0. + int32_t padding = [bitsData[@"padding"] intValue]; + int32_t hashCount = [bloomFilterProto[@"hashCount"] intValue]; + return BloomFilterParameters{std::move(bitmap), padding, hashCount}; } - (DocumentViewChange)parseChange:(NSDictionary *)jsonDoc ofType:(DocumentViewChange::Type)type { @@ -481,9 +495,10 @@ - (void)doWatchFilter:(NSDictionary *)watchFilter { NSArray *keys = watchFilter[@"keys"]; int keyCount = keys ? (int)keys.count : 0; - absl::optional bloomFilter = [self parseBloomFilter:watchFilter[@"bloomFilter"]]; + absl::optional bloomFilterParameters = + [self parseBloomFilterParameter:watchFilter[@"bloomFilter"]]; - ExistenceFilter filter{keyCount, std::move(bloomFilter)}; + ExistenceFilter filter{keyCount, std::move(bloomFilterParameters)}; ExistenceFilterWatchChange change{std::move(filter), targets[0].intValue}; [self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()]; } @@ -693,8 +708,18 @@ - (void)validateEvent:(FSTQueryEvent *)actual matches:(NSDictionary *)expected { } XCTAssertEqual(actual.viewSnapshot.value().document_changes().size(), expectedChanges.size()); - for (size_t i = 0; i != expectedChanges.size(); ++i) { - XCTAssertTrue((actual.viewSnapshot.value().document_changes()[i] == expectedChanges[i])); + + auto comparator = [](const DocumentViewChange &lhs, const DocumentViewChange &rhs) { + return lhs.document()->key() < rhs.document()->key(); + }; + + std::vector expectedChangesSorted = expectedChanges; + std::sort(expectedChangesSorted.begin(), expectedChangesSorted.end(), comparator); + std::vector actualChangesSorted = + actual.viewSnapshot.value().document_changes(); + std::sort(actualChangesSorted.begin(), actualChangesSorted.end(), comparator); + for (size_t i = 0; i != expectedChangesSorted.size(); ++i) { + XCTAssertTrue((actualChangesSorted[i] == expectedChangesSorted[i])); } BOOL expectedHasPendingWrites = @@ -921,7 +946,7 @@ - (void)validateActiveTargets { XCTAssertEqual(actual.resume_token(), targetData.resume_token()); if (targetData.expected_count().has_value()) { if (!actual.expected_count().has_value()) { - XCTFail("Actual target data doesn't have an expected_count."); + XCTFail(@"Actual target data doesn't have an expected_count."); } else { XCTAssertEqual(actual.expected_count().value(), targetData.expected_count().value()); } diff --git a/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json b/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json index 58369d84959..24f9d92cf53 100644 --- a/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json @@ -1,9 +1,8 @@ { - "Existence filter clears resume token": { + "Bloom filter can process special characters in document name": { "describeName": "Existence Filters:", - "itName": "Existence filter clears resume token", + "itName": "Bloom filter can process special characters in document name", "tags": [ - "durable-persistence" ], "config": { "numClients": 1, @@ -47,7 +46,7 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/ÀÒ∑", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -58,13 +57,13 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/À∑Ò", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 2 + "v": 1 }, "version": 1000 } @@ -92,7 +91,7 @@ { "added": [ { - "key": "collection/1", + "key": "collection/ÀÒ∑", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -103,13 +102,13 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/À∑Ò", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 2 + "v": 1 }, "version": 1000 } @@ -129,8 +128,15 @@ }, { "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "IIAAIIAIIAAIIAIIAA==", + "padding": 4 + }, + "hashCount": 10 + }, "keys": [ - "collection/1" + "collection/ÀÒ∑" ], "targetIds": [ 2 @@ -158,7 +164,23 @@ } ], "expectedState": { + "activeLimboDocs": [ + "collection/À∑Ò" + ], "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/À∑Ò" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, "2": { "queries": [ { @@ -169,23 +191,23 @@ "path": "collection" } ], - "resumeToken": "", - "targetPurpose": 1 + "resumeToken": "" } } } - }, - { - "restart": true, - "expectedState": { - "activeLimboDocs": [ - ], - "activeTargets": { - }, - "enqueuedLimboDocs": [ - ] - } - }, + } + ] + }, + "Bloom filter fills in default values for undefined padding and hashCount": { + "describeName": "Existence Filters:", + "itName": "Bloom filter fills in default values for undefined padding and hashCount", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ { "userListen": { "query": { @@ -197,11 +219,78 @@ }, "targetId": 2 }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -212,7 +301,7 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -223,6 +312,42 @@ "version": 1000 } ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "AhAAApAAAIAEBIAABA==" + } + }, + "keys": [ + "collection/a" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { "errorCode": 0, "fromCache": true, "hasPendingWrites": false, @@ -247,16 +372,17 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "", + "targetPurpose": 1 } } } } ] }, - "Existence filter handled at global snapshot": { + "Bloom filter is handled at global snapshot": { "describeName": "Existence Filters:", - "itName": "Existence filter handled at global snapshot", + "itName": "Bloom filter is handled at global snapshot", "tags": [ ], "config": { @@ -301,7 +427,7 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -310,6 +436,17 @@ "v": 1 }, "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 } ], "targets": [ @@ -335,7 +472,7 @@ { "added": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -344,6 +481,17 @@ "v": 1 }, "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 } ], "errorCode": 0, @@ -361,9 +509,15 @@ }, { "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "AhAAApAAAIAEBIAABA==", + "padding": 4 + }, + "hashCount": 10 + }, "keys": [ - "collection/1", - "collection/2" + "collection/a" ], "targetIds": [ 2 @@ -374,7 +528,7 @@ "watchEntity": { "docs": [ { - "key": "collection/3", + "key": "collection/c", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -400,7 +554,7 @@ { "added": [ { - "key": "collection/3", + "key": "collection/c", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -424,7 +578,23 @@ } ], "expectedState": { + "activeLimboDocs": [ + "collection/b" + ], "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/b" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, "2": { "queries": [ { @@ -435,17 +605,49 @@ "path": "collection" } ], - "resumeToken": "", - "targetPurpose": 1 + "resumeToken": "" } } } - }, + } + ] + }, + "Bloom filter limbo resolution is denied": { + "describeName": "Existence Filters:", + "itName": "Bloom filter limbo resolution is denied", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ { - "watchRemove": { - "targetIds": [ - 2 - ] + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } } }, { @@ -457,7 +659,7 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -468,50 +670,5275 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 2 + "v": 1 }, - "version": 2000 - }, - { + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "AhAAApAAAIAEBIAABA==", + "padding": 4 + }, + "hashCount": 10 + }, + "keys": [ + "collection/a" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeLimboDocs": [ + "collection/b" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/b" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchRemove": { + "cause": { + "code": 7 + }, + "targetIds": [ + 1 + ] + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ] + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + } + ] + }, + "Bloom filter with large size works as expected": { + "describeName": "Existence Filters:", + "itName": "Bloom filter with large size works as expected", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/doc0", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc3", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc4", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc5", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc6", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc7", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc8", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc9", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc10", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc11", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc12", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc13", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc14", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc15", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc16", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc17", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc18", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc19", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc20", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc21", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc22", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc23", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc24", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc25", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc26", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc27", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc28", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc29", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc30", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc31", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc32", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc33", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc34", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc35", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc36", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc37", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc38", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc39", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc40", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc41", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc42", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc43", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc44", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc45", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc46", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc47", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc48", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc49", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc50", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc51", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc52", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc53", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc54", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc55", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc56", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc57", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc58", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc59", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc60", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc61", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc62", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc63", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc64", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc65", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc66", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc67", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc68", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc69", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc70", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc71", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc72", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc73", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc74", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc75", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc76", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc77", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc78", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc79", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc80", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc81", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc82", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc83", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc84", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc85", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc86", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc87", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc88", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc89", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc90", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc91", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc92", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc93", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc94", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc95", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc96", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc97", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc98", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc99", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/doc0", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc3", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc4", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc5", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc6", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc7", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc8", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc9", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc10", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc11", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc12", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc13", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc14", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc15", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc16", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc17", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc18", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc19", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc20", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc21", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc22", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc23", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc24", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc25", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc26", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc27", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc28", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc29", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc30", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc31", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc32", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc33", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc34", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc35", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc36", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc37", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc38", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc39", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc40", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc41", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc42", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc43", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc44", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc45", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc46", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc47", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc48", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc49", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc50", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc51", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc52", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc53", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc54", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc55", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc56", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc57", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc58", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc59", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc60", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc61", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc62", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc63", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc64", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc65", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc66", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc67", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc68", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc69", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc70", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc71", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc72", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc73", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc74", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc75", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc76", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc77", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc78", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc79", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc80", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc81", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc82", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc83", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc84", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc85", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc86", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc87", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc88", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc89", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc90", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc91", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc92", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc93", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc94", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc95", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc96", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc97", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc98", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/doc99", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "+9oMQXUptl274DOaET8sfebQ4aCu0Roiddbja3z8TfadKuyPV/9XWV5Ksv+vywRXTfZSNIn8z+xk/oq1+cbOPepeNvbXVOF6H92fCOAz/KiS3Mcw338R9tXE3Y7QB1L2kbvbvVHW3Kn/k3Vx8k9Oa19eWX6RYE97Q+oCcVU=", + "padding": 0 + }, + "hashCount": 16 + }, + "keys": [ + "collection/doc0", + "collection/doc1", + "collection/doc2", + "collection/doc3", + "collection/doc4", + "collection/doc5", + "collection/doc6", + "collection/doc7", + "collection/doc8", + "collection/doc9", + "collection/doc10", + "collection/doc11", + "collection/doc12", + "collection/doc13", + "collection/doc14", + "collection/doc15", + "collection/doc16", + "collection/doc17", + "collection/doc18", + "collection/doc19", + "collection/doc20", + "collection/doc21", + "collection/doc22", + "collection/doc23", + "collection/doc24", + "collection/doc25", + "collection/doc26", + "collection/doc27", + "collection/doc28", + "collection/doc29", + "collection/doc30", + "collection/doc31", + "collection/doc32", + "collection/doc33", + "collection/doc34", + "collection/doc35", + "collection/doc36", + "collection/doc37", + "collection/doc38", + "collection/doc39", + "collection/doc40", + "collection/doc41", + "collection/doc42", + "collection/doc43", + "collection/doc44", + "collection/doc45", + "collection/doc46", + "collection/doc47", + "collection/doc48", + "collection/doc49" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeLimboDocs": [ + "collection/doc50", + "collection/doc51", + "collection/doc52", + "collection/doc53", + "collection/doc54", + "collection/doc55", + "collection/doc56", + "collection/doc57", + "collection/doc58", + "collection/doc59", + "collection/doc60", + "collection/doc61", + "collection/doc62", + "collection/doc63", + "collection/doc64", + "collection/doc65", + "collection/doc66", + "collection/doc67", + "collection/doc68", + "collection/doc69", + "collection/doc70", + "collection/doc71", + "collection/doc72", + "collection/doc73", + "collection/doc74", + "collection/doc75", + "collection/doc76", + "collection/doc77", + "collection/doc78", + "collection/doc79", + "collection/doc80", + "collection/doc81", + "collection/doc82", + "collection/doc83", + "collection/doc84", + "collection/doc85", + "collection/doc86", + "collection/doc87", + "collection/doc88", + "collection/doc89", + "collection/doc90", + "collection/doc91", + "collection/doc92", + "collection/doc93", + "collection/doc94", + "collection/doc95", + "collection/doc96", + "collection/doc97", + "collection/doc98", + "collection/doc99" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc50" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "11": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc55" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "13": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc56" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "15": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc57" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "17": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc58" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "19": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc59" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, + "21": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc60" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "23": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc61" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "25": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc62" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "27": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc63" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "29": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc64" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "3": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc51" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "31": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc65" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "33": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc66" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "35": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc67" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "37": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc68" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "39": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc69" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "41": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc70" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "43": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc71" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "45": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc72" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "47": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc73" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "49": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc74" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "5": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc52" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "51": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc75" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "53": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc76" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "55": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc77" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "57": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc78" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "59": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc79" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "61": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc80" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "63": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc81" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "65": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc82" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "67": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc83" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "69": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc84" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "7": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc53" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "71": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc85" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "73": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc86" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "75": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc87" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "77": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc88" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "79": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc89" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "81": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc90" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "83": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc91" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "85": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc92" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "87": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc93" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "89": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc94" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "9": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc54" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "91": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc95" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "93": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc96" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "95": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc97" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "97": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc98" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "99": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/doc99" + } + ], + "resumeToken": "", + "targetPurpose": 2 + } + } + } + } + ] + }, + "Existence filter clears resume token": { + "describeName": "Existence Filters:", + "itName": "Existence filter clears resume token", + "tags": [ + "durable-persistence" + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "keys": [ + "collection/1" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + }, + { + "restart": true, + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + }, + "enqueuedLimboDocs": [ + ] + } + }, + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + } + ] + }, + "Existence filter handled at global snapshot": { + "describeName": "Existence Filters:", + "itName": "Existence filter handled at global snapshot", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "keys": [ + "collection/1", + "collection/2" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/3", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 3 + }, + "version": 3000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/3", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 3 + }, + "version": 3000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 + }, + { "key": "collection/3", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 3 + "v": 3 + }, + "version": 3000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-3000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 3000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Existence filter ignored with pending target": { + "describeName": "Existence Filters:", + "itName": "Existence filter ignored with pending target", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + } + } + }, + { + "watchFilter": { + "keys": [ + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Existence filter limbo resolution is denied": { + "describeName": "Existence Filters:", + "itName": "Existence filter limbo resolution is denied", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "keys": [ + "collection/1" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedState": { + "activeLimboDocs": [ + "collection/2" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/2" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + }, + { + "watchRemove": { + "cause": { + "code": 7 + }, + "targetIds": [ + 1 + ] + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ] + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + } + ] + }, + "Existence filter match": { + "describeName": "Existence Filters:", + "itName": "Existence filter match", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "keys": [ + "collection/1" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + } + } + ] + }, + "Existence filter match after pending update": { + "describeName": "Existence Filters:", + "itName": "Existence filter match after pending update", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchFilter": { + "keys": [ + "collection/1" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Existence filter mismatch triggers re-run of query": { + "describeName": "Existence Filters:", + "itName": "Existence filter mismatch triggers re-run of query", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "keys": [ + "collection/1" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedState": { + "activeLimboDocs": [ + "collection/2" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/2" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + }, + { + "watchAck": [ + 1 + ] + }, + { + "watchCurrent": [ + [ + 1 + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ] + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + } + ] + }, + "Existence filter mismatch will drop resume token": { + "describeName": "Existence Filters:", + "itName": "Existence filter mismatch will drop resume token", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 }, - "version": 3000 + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "existence-filter-resume-token" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchStreamClose": { + "error": { + "code": 14, + "message": "Simulated Backend Error" + }, + "runBackoffTimer": true + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "existence-filter-resume-token" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchFilter": { + "keys": [ + "collection/1" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 } ], - "targets": [ - 2 - ] + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedState": { + "activeLimboDocs": [ + "collection/2" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/2" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } } }, + { + "watchAck": [ + 1 + ] + }, { "watchCurrent": [ [ - 2 + 1 ], - "resume-token-3000" + "resume-token-2000" ] }, { "watchSnapshot": { "targetIds": [ ], - "version": 3000 + "version": 2000 }, "expectedSnapshotEvents": [ { - "added": [ + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ { "key": "collection/2", "options": { @@ -521,32 +5948,41 @@ "value": { "v": 2 }, - "version": 2000 + "version": 1000 } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ + ] + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } ], - "path": "collection" + "resumeToken": "", + "targetPurpose": 1 } } - ] + } } ] }, - "Existence filter ignored with pending target": { + "Existence filter synthesizes deletes": { "describeName": "Existence Filters:", - "itName": "Existence filter ignored with pending target", + "itName": "Existence filter synthesizes deletes", "tags": [ ], "config": { "numClients": 1, - "useGarbageCollection": false + "useGarbageCollection": true }, "steps": [ { @@ -556,7 +5992,7 @@ ], "orderBys": [ ], - "path": "collection" + "path": "collection/a" }, "targetId": 2 }, @@ -569,7 +6005,7 @@ ], "orderBys": [ ], - "path": "collection" + "path": "collection/a" } ], "resumeToken": "" @@ -586,15 +6022,15 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 2 + "v": 1 }, - "version": 2000 + "version": 1000 } ], "targets": [ @@ -620,15 +6056,15 @@ { "added": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 2 + "v": 1 }, - "version": 2000 + "version": 1000 } ], "errorCode": 0, @@ -639,65 +6075,77 @@ ], "orderBys": [ ], - "path": "collection" + "path": "collection/a" } } ] }, { - "userUnlisten": [ - 2, - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "expectedState": { - "activeTargets": { - } + "watchFilter": { + "keys": [ + ], + "targetIds": [ + 2 + ] } }, { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 }, "expectedSnapshotEvents": [ { - "added": [ + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/a" + }, + "removed": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 2 + "v": 1 }, - "version": 2000 + "version": 1000 } - ], - "errorCode": 0, - "fromCache": true, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } + ] } - ], + ] + } + ] + }, + "Existence filter with empty target": { + "describeName": "Existence Filters:", + "itName": "Existence filter with empty target", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, "expectedState": { "activeTargets": { "2": { @@ -710,27 +6158,11 @@ "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "" } } } }, - { - "watchFilter": { - "keys": [ - ], - "targetIds": [ - 2 - ] - } - }, - { - "watchRemove": { - "targetIds": [ - 2 - ] - } - }, { "watchAck": [ 2 @@ -741,7 +6173,7 @@ [ 2 ], - "resume-token-2000" + "resume-token-1000" ] }, { @@ -763,13 +6195,61 @@ "path": "collection" } } - ] + ] + }, + { + "watchFilter": { + "keys": [ + "collection/1" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } } ] }, - "Existence filter limbo resolution is denied": { + "Full re-query is skipped when bloom filter can identify documents deleted": { "describeName": "Existence Filters:", - "itName": "Existence filter limbo resolution is denied", + "itName": "Full re-query is skipped when bloom filter can identify documents deleted", "tags": [ ], "config": { @@ -814,7 +6294,7 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -825,7 +6305,7 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -859,7 +6339,7 @@ { "added": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -870,7 +6350,7 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -896,8 +6376,15 @@ }, { "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "AhAAApAAAIAEBIAABA==", + "padding": 4 + }, + "hashCount": 10 + }, "keys": [ - "collection/1" + "collection/a" ], "targetIds": [ 2 @@ -924,73 +6411,9 @@ } } ], - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "", - "targetPurpose": 1 - } - } - } - }, - { - "watchRemove": { - "targetIds": [ - 2 - ] - } - }, - { - "watchAck": [ - 2 - ] - }, - { - "watchEntity": { - "docs": [ - { - "key": "collection/1", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": 1 - }, - "version": 1000 - } - ], - "targets": [ - 2 - ] - } - }, - { - "watchCurrent": [ - [ - 2 - ], - "resume-token-2000" - ] - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - }, "expectedState": { "activeLimboDocs": [ - "collection/2" + "collection/b" ], "activeTargets": { "1": { @@ -1000,7 +6423,7 @@ ], "orderBys": [ ], - "path": "collection/2" + "path": "collection/b" } ], "resumeToken": "", @@ -1016,20 +6439,29 @@ "path": "collection" } ], - "resumeToken": "", - "targetPurpose": 1 + "resumeToken": "" } } } }, { - "watchRemove": { - "cause": { - "code": 7 - }, - "targetIds": [ + "watchAck": [ + 1 + ] + }, + { + "watchCurrent": [ + [ 1 - ] + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 }, "expectedSnapshotEvents": [ { @@ -1045,7 +6477,7 @@ }, "removed": [ { - "key": "collection/2", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1066,148 +6498,25 @@ "queries": [ { "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "", - "targetPurpose": 1 - } - } - } - } - ] - }, - "Existence filter match": { - "describeName": "Existence Filters:", - "itName": "Existence filter match", - "tags": [ - ], - "config": { - "numClients": 1, - "useGarbageCollection": true - }, - "steps": [ - { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 - }, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "" - } - } - } - }, - { - "watchAck": [ - 2 - ] - }, - { - "watchEntity": { - "docs": [ - { - "key": "collection/1", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": 1 - }, - "version": 1000 - } - ], - "targets": [ - 2 - ] - } - }, - { - "watchCurrent": [ - [ - 2 - ], - "resume-token-1000" - ] - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 1000 - }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/1", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": 1 - }, - "version": 1000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ] - }, - { - "watchFilter": { - "keys": [ - "collection/1" - ], - "targetIds": [ - 2 - ] - } - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } } } ] }, - "Existence filter match after pending update": { + "Full re-query is triggered when bloom filter bitmap is invalid": { "describeName": "Existence Filters:", - "itName": "Existence filter match after pending update", + "itName": "Full re-query is triggered when bloom filter bitmap is invalid", "tags": [ + "no-ios", + "no-android" ], "config": { "numClients": 1, @@ -1247,6 +6556,37 @@ 2 ] }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, { "watchCurrent": [ [ @@ -1259,10 +6599,34 @@ "watchSnapshot": { "targetIds": [ ], - "version": 2000 + "version": 1000 }, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -1276,30 +6640,17 @@ } ] }, - { - "watchEntity": { - "docs": [ - { - "key": "collection/1", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": 2 - }, - "version": 2000 - } - ], - "targets": [ - 2 - ] - } - }, { "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "INVALID_BASE_64", + "padding": 4 + }, + "hashCount": 10 + }, "keys": [ - "collection/1" + "collection/a" ], "targetIds": [ 2 @@ -1314,21 +6665,8 @@ }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/1", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": 2 - }, - "version": 2000 - } - ], "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ @@ -1338,13 +6676,30 @@ "path": "collection" } } - ] + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "", + "targetPurpose": 1 + } + } + } } ] }, - "Existence filter mismatch triggers re-run of query": { + "Full re-query is triggered when bloom filter can not identify documents deleted": { "describeName": "Existence Filters:", - "itName": "Existence filter mismatch triggers re-run of query", + "itName": "Full re-query is triggered when bloom filter can not identify documents deleted", "tags": [ ], "config": { @@ -1389,7 +6744,7 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1400,7 +6755,18 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + }, + { + "key": "collection/c", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1434,7 +6800,7 @@ { "added": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1445,7 +6811,18 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + }, + { + "key": "collection/c", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1471,8 +6848,15 @@ }, { "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "AxBIApBIAIAWBoCQBA==", + "padding": 4 + }, + "hashCount": 10 + }, "keys": [ - "collection/1" + "collection/a" ], "targetIds": [ 2 @@ -1512,16 +6896,49 @@ } ], "resumeToken": "", - "targetPurpose": 1 + "targetPurpose": 2 } } } - }, + } + ] + }, + "Full re-query is triggered when bloom filter hashCount is invalid": { + "describeName": "Existence Filters:", + "itName": "Full re-query is triggered when bloom filter hashCount is invalid", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ { - "watchRemove": { - "targetIds": [ - 2 - ] + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } } }, { @@ -1533,7 +6950,7 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1542,82 +6959,64 @@ "v": 1 }, "version": 1000 - } - ], - "targets": [ - 2 - ] - } - }, - { - "watchCurrent": [ - [ - 2 - ], - "resume-token-2000" - ] - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - }, - "expectedState": { - "activeLimboDocs": [ - "collection/2" - ], - "activeTargets": { - "1": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection/2" - } - ], - "resumeToken": "", - "targetPurpose": 2 }, - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "", - "targetPurpose": 1 + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 } - } + ], + "targets": [ + 2 + ] } }, - { - "watchAck": [ - 1 - ] - }, { "watchCurrent": [ [ - 1 + 2 ], - "resume-token-2000" + "resume-token-1000" ] }, { "watchSnapshot": { "targetIds": [ ], - "version": 2000 + "version": 1000 }, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -1627,25 +7026,48 @@ "orderBys": [ ], "path": "collection" + } + } + ] + }, + { + "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "AhAAApAAAIAEBIAABA==", + "padding": 4 }, - "removed": [ - { - "key": "collection/2", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": 2 - }, - "version": 1000 - } - ] + "hashCount": -1 + }, + "keys": [ + "collection/a" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } } ], "expectedState": { - "activeLimboDocs": [ - ], "activeTargets": { "2": { "queries": [ @@ -1665,9 +7087,9 @@ } ] }, - "Existence filter mismatch will drop resume token": { + "Full re-query is triggered when bloom filter is empty": { "describeName": "Existence Filters:", - "itName": "Existence filter mismatch will drop resume token", + "itName": "Full re-query is triggered when bloom filter is empty", "tags": [ ], "config": { @@ -1712,7 +7134,7 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1723,13 +7145,13 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 2 + "v": 1 }, "version": 1000 } @@ -1744,7 +7166,7 @@ [ 2 ], - "existence-filter-resume-token" + "resume-token-1000" ] }, { @@ -1757,7 +7179,7 @@ { "added": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1768,13 +7190,13 @@ "version": 1000 }, { - "key": "collection/2", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 2 + "v": 1 }, "version": 1000 } @@ -1792,40 +7214,17 @@ } ] }, - { - "watchStreamClose": { - "error": { - "code": 14, - "message": "Simulated Backend Error" - }, - "runBackoffTimer": true - }, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "existence-filter-resume-token" - } - } - } - }, - { - "watchAck": [ - 2 - ] - }, { "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "", + "padding": 0 + }, + "hashCount": 0 + }, "keys": [ - "collection/1" + "collection/a" ], "targetIds": [ 2 @@ -1869,12 +7268,55 @@ } } } - }, + } + ] + }, + "Same documents can have different bloom filters": { + "describeName": "Existence Filters:", + "itName": "Same documents can have different bloom filters", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ { - "watchRemove": { - "targetIds": [ - 2 - ] + "userListen": { + "query": { + "filters": [ + [ + "v", + "<=", + 2 + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + [ + "v", + "<=", + 2 + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } } }, { @@ -1886,7 +7328,7 @@ "watchEntity": { "docs": [ { - "key": "collection/1", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1895,6 +7337,17 @@ "v": 1 }, "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 } ], "targets": [ @@ -1907,83 +7360,80 @@ [ 2 ], - "resume-token-2000" - ] - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - }, - "expectedState": { - "activeLimboDocs": [ - "collection/2" - ], - "activeTargets": { - "1": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection/2" - } - ], - "resumeToken": "", - "targetPurpose": 2 - }, - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "", - "targetPurpose": 1 - } - } - } - }, - { - "watchAck": [ - 1 - ] - }, - { - "watchCurrent": [ - [ - 1 - ], - "resume-token-2000" + "resume-token-1000" ] }, { "watchSnapshot": { "targetIds": [ ], - "version": 2000 + "version": 1000 }, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 2 + }, + "version": 1000 + } + ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ + [ + "v", + "<=", + 2 + ] ], "orderBys": [ ], "path": "collection" - }, - "removed": [ + } + } + ] + }, + { + "userListen": { + "query": { + "filters": [ + [ + "v", + ">=", + 2 + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ { - "key": "collection/2", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -1993,62 +7443,56 @@ }, "version": 1000 } - ] + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "v", + ">=", + 2 + ] + ], + "orderBys": [ + ], + "path": "collection" + } } ], "expectedState": { - "activeLimboDocs": [ - ], "activeTargets": { "2": { "queries": [ { "filters": [ + [ + "v", + "<=", + 2 + ] ], "orderBys": [ ], "path": "collection" } ], - "resumeToken": "", - "targetPurpose": 1 - } - } - } - } - ] - }, - "Existence filter synthesizes deletes": { - "describeName": "Existence Filters:", - "itName": "Existence filter synthesizes deletes", - "tags": [ - ], - "config": { - "numClients": 1, - "useGarbageCollection": true - }, - "steps": [ - { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection/a" - }, - "targetId": 2 - }, - "expectedState": { - "activeTargets": { - "2": { + "resumeToken": "" + }, + "4": { "queries": [ { "filters": [ + [ + "v", + ">=", + 2 + ] ], "orderBys": [ ], - "path": "collection/a" + "path": "collection" } ], "resumeToken": "" @@ -2058,54 +7502,65 @@ }, { "watchAck": [ - 2 + 4 ] }, { "watchEntity": { "docs": [ { - "key": "collection/a", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 1 + "v": 2 + }, + "version": 1000 + }, + { + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 3 }, "version": 1000 } ], "targets": [ - 2 + 4 ] } }, { "watchCurrent": [ [ - 2 + 4 ], - "resume-token-1000" + "resume-token-1001" ] }, { "watchSnapshot": { "targetIds": [ ], - "version": 1000 + "version": 1001 }, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/a", + "key": "collection/c", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "v": 1 + "v": 3 }, "version": 1000 } @@ -2115,17 +7570,30 @@ "hasPendingWrites": false, "query": { "filters": [ + [ + "v", + ">=", + 2 + ] ], "orderBys": [ ], - "path": "collection/a" + "path": "collection" } } ] }, { "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "CQ==", + "padding": 3 + }, + "hashCount": 2 + }, "keys": [ + "collection/b" ], "targetIds": [ 2 @@ -2141,60 +7609,49 @@ "expectedSnapshotEvents": [ { "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ + [ + "v", + "<=", + 2 + ] ], "orderBys": [ ], - "path": "collection/a" - }, - "removed": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": 1 - }, - "version": 1000 - } - ] + "path": "collection" + } } - ] - } - ] - }, - "Existence filter with empty target": { - "describeName": "Existence Filters:", - "itName": "Existence filter with empty target", - "tags": [ - ], - "config": { - "numClients": 1, - "useGarbageCollection": true - }, - "steps": [ - { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 - }, + ], "expectedState": { + "activeLimboDocs": [ + "collection/a" + ], "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/a" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, "2": { "queries": [ { "filters": [ + [ + "v", + "<=", + 2 + ] ], "orderBys": [ ], @@ -2202,51 +7659,41 @@ } ], "resumeToken": "" - } - } - } - }, - { - "watchAck": [ - 2 - ] - }, - { - "watchCurrent": [ - [ - 2 - ], - "resume-token-1000" - ] - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - }, - "expectedSnapshotEvents": [ - { - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ + }, + "4": { + "queries": [ + { + "filters": [ + [ + "v", + ">=", + 2 + ] + ], + "orderBys": [ + ], + "path": "collection" + } ], - "path": "collection" + "resumeToken": "" } } - ] + } }, { "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "CA==", + "padding": 4 + }, + "hashCount": 1 + }, "keys": [ - "collection/1" + "collection/b" ], "targetIds": [ - 2 + 4 ] } }, @@ -2254,7 +7701,7 @@ "watchSnapshot": { "targetIds": [ ], - "version": 2000 + "version": 3000 }, "expectedSnapshotEvents": [ { @@ -2263,6 +7710,11 @@ "hasPendingWrites": false, "query": { "filters": [ + [ + "v", + ">=", + 2 + ] ], "orderBys": [ ], @@ -2271,19 +7723,70 @@ } ], "expectedState": { + "activeLimboDocs": [ + "collection/a", + "collection/c" + ], "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/a" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, "2": { "queries": [ { "filters": [ + [ + "v", + "<=", + 2 + ] ], "orderBys": [ ], "path": "collection" } ], + "resumeToken": "" + }, + "3": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/c" + } + ], "resumeToken": "", - "targetPurpose": 1 + "targetPurpose": 2 + }, + "4": { + "queries": [ + { + "filters": [ + [ + "v", + ">=", + 2 + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" } } } diff --git a/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json b/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json index bb1fd9d7266..3958930b2dc 100644 --- a/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json @@ -7841,6 +7841,388 @@ } ] }, + "Limbo resolution throttling with bloom filter application": { + "describeName": "Limbo Documents:", + "itName": "Limbo resolution throttling with bloom filter application", + "tags": [ + ], + "config": { + "maxConcurrentLimboResolutions": 2, + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/a1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a1" + }, + "version": 1000 + }, + { + "key": "collection/a2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a2" + }, + "version": 1000 + }, + { + "key": "collection/a3", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a3" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a1" + }, + "version": 1000 + }, + { + "key": "collection/a2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a2" + }, + "version": 1000 + }, + { + "key": "collection/a3", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a3" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "enableNetwork": false, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + }, + "enqueuedLimboDocs": [ + ] + } + }, + { + "enableNetwork": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/b1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b1" + }, + "version": 1000 + }, + { + "key": "collection/b2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b2" + }, + "version": 1000 + }, + { + "key": "collection/b3", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b3" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchFilter": { + "bloomFilter": { + "bits": { + "bitmap": "yABCEAeZURNRGAkgAQ==", + "padding": 4 + }, + "hashCount": 10 + }, + "keys": [ + "collection/b1", + "collection/b2", + "collection/b3" + ], + "targetIds": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1001 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/b1", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b1" + }, + "version": 1000 + }, + { + "key": "collection/b2", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b2" + }, + "version": 1000 + }, + { + "key": "collection/b3", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b3" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1002" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1002 + }, + "expectedState": { + "activeLimboDocs": [ + "collection/a1", + "collection/a2" + ], + "activeTargets": { + "1": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/a1" + } + ], + "resumeToken": "", + "targetPurpose": 2 + }, + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + }, + "3": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection/a2" + } + ], + "resumeToken": "", + "targetPurpose": 2 + } + }, + "enqueuedLimboDocs": [ + "collection/a3" + ] + } + } + ] + }, "Limbo resolution throttling with existence filter mismatch": { "describeName": "Limbo Documents:", "itName": "Limbo resolution throttling with existence filter mismatch", diff --git a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json index eb8a35f3074..1d1013681f9 100644 --- a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json @@ -1149,7 +1149,8 @@ "path": "collection/a" } ], - "resumeToken": "" + "resumeToken": "", + "targetPurpose": 2 }, "2": { "queries": [ @@ -1186,7 +1187,8 @@ "path": "collection/a" } ], - "resumeToken": "" + "resumeToken": "", + "targetPurpose": 2 }, "2": { "queries": [ diff --git a/Firestore/core/src/remote/datastore.cc b/Firestore/core/src/remote/datastore.cc index e3c664a9ed9..94691a4fad2 100644 --- a/Firestore/core/src/remote/datastore.cc +++ b/Firestore/core/src/remote/datastore.cc @@ -106,6 +106,7 @@ Datastore::Datastore( auth_credentials_{std::move(auth_credentials)}, rpc_executor_{CreateExecutor()}, connectivity_monitor_{connectivity_monitor}, + database_info_{database_info}, grpc_connection_{database_info, worker_queue, &grpc_queue_, connectivity_monitor_, firebase_metadata_provider}, datastore_serializer_{database_info} { diff --git a/Firestore/core/src/remote/datastore.h b/Firestore/core/src/remote/datastore.h index d02885ee6f8..4a2e2e3e9f4 100644 --- a/Firestore/core/src/remote/datastore.h +++ b/Firestore/core/src/remote/datastore.h @@ -139,6 +139,10 @@ class Datastore : public std::enable_shared_from_this { static std::string GetAllowlistedHeadersAsString( const GrpcCall::Metadata& headers); + const core::DatabaseInfo& database_info() const { + return database_info_; + } + Datastore(const Datastore& other) = delete; Datastore(Datastore&& other) = delete; Datastore& operator=(const Datastore& other) = delete; @@ -209,6 +213,7 @@ class Datastore : public std::enable_shared_from_this { std::unique_ptr rpc_executor_; grpc::CompletionQueue grpc_queue_; ConnectivityMonitor* connectivity_monitor_ = nullptr; + core::DatabaseInfo database_info_; GrpcConnection grpc_connection_; std::vector> active_calls_; diff --git a/Firestore/core/src/remote/existence_filter.h b/Firestore/core/src/remote/existence_filter.h index 968341df588..ed0b2298242 100644 --- a/Firestore/core/src/remote/existence_filter.h +++ b/Firestore/core/src/remote/existence_filter.h @@ -18,6 +18,7 @@ #define FIRESTORE_CORE_SRC_REMOTE_EXISTENCE_FILTER_H_ #include +#include #include "Firestore/core/src/remote/bloom_filter.h" @@ -25,29 +26,44 @@ namespace firebase { namespace firestore { namespace remote { +struct BloomFilterParameters { + std::vector bitmap; + int32_t padding; + int32_t hash_count; +}; + +inline bool operator==(const BloomFilterParameters& lhs, + const BloomFilterParameters& rhs) { + return lhs.padding == rhs.padding && lhs.hash_count == rhs.hash_count && + lhs.bitmap == rhs.bitmap; +} + class ExistenceFilter { public: ExistenceFilter() = default; - ExistenceFilter(int count, absl::optional bloom_filter) - : count_{count}, bloom_filter_{std::move(bloom_filter)} { + ExistenceFilter(int count, + absl::optional bloom_filter_parameters) + : count_{count}, + bloom_filter_parameters_{std::move(bloom_filter_parameters)} { } int count() const { return count_; } - const absl::optional& bloom_filter() const { - return bloom_filter_; + const absl::optional& bloom_filter_parameters() const { + return bloom_filter_parameters_; } private: int count_ = 0; - absl::optional bloom_filter_; + absl::optional bloom_filter_parameters_; }; inline bool operator==(const ExistenceFilter& lhs, const ExistenceFilter& rhs) { - return lhs.count() == rhs.count() && lhs.bloom_filter() == rhs.bloom_filter(); + return lhs.count() == rhs.count() && + lhs.bloom_filter_parameters() == rhs.bloom_filter_parameters(); } } // namespace remote diff --git a/Firestore/core/src/remote/remote_event.cc b/Firestore/core/src/remote/remote_event.cc index f6f5d86ccca..ca025e30723 100644 --- a/Firestore/core/src/remote/remote_event.cc +++ b/Firestore/core/src/remote/remote_event.cc @@ -16,9 +16,11 @@ #include "Firestore/core/src/remote/remote_event.h" +#include #include #include "Firestore/core/src/local/target_data.h" +#include "Firestore/core/src/util/log.h" namespace firebase { namespace firestore { @@ -28,6 +30,7 @@ using core::DocumentViewChange; using core::Target; using local::QueryPurpose; using local::TargetData; +using model::DatabaseId; using model::DocumentKey; using model::DocumentKeySet; using model::MutableDocument; @@ -235,15 +238,66 @@ void WatchChangeAggregator::HandleExistenceFilter( } else { int current_size = GetCurrentDocumentCountForTarget(target_id); if (current_size != expected_count) { - // Existence filter mismatch: We reset the mapping and raise a new - // snapshot with `isFromCache:true`. - ResetTarget(target_id); - pending_target_resets_.insert(target_id); + // TODO(Mila): Use application status instead of a boolean in next PR. + // Apply bloom filter to identify and mark removed documents. + bool bloom_filter_applied = + ApplyBloomFilter(existence_filter, current_size); + if (!bloom_filter_applied) { + // If bloom filter application fails, we reset the mapping and + // trigger re-run of the query. + ResetTarget(target_id); + pending_target_resets_.insert(target_id); + } } } } } +bool WatchChangeAggregator::ApplyBloomFilter( + const ExistenceFilterWatchChange& existence_filter, int current_count) { + const absl::optional& bloom_filter_parameters = + existence_filter.filter().bloom_filter_parameters(); + if (!bloom_filter_parameters.has_value()) { + return false; + } + + util::StatusOr maybe_bloom_filter = + BloomFilter::Create(bloom_filter_parameters.value().bitmap, + bloom_filter_parameters.value().padding, + bloom_filter_parameters.value().hash_count); + if (!maybe_bloom_filter.ok()) { + LOG_WARN("Creating BloomFilter failed: %s", + maybe_bloom_filter.status().error_message()); + return false; + } + + int removed_document_count = FilterRemovedDocuments( + maybe_bloom_filter.ValueOrDie(), existence_filter.target_id()); + + int expected_count = existence_filter.filter().count(); + return expected_count == (current_count - removed_document_count); +} + +int WatchChangeAggregator::FilterRemovedDocuments( + const BloomFilter& bloom_filter, int target_id) { + const DocumentKeySet existing_keys = + target_metadata_provider_->GetRemoteKeysForTarget(target_id); + int removalCount = 0; + for (const DocumentKey& key : existing_keys) { + const DatabaseId& database_id = target_metadata_provider_->GetDatabaseId(); + std::string document_path = util::StringFormat( + "projects/%s/databases/%s/documents/%s", database_id.project_id(), + database_id.database_id(), key.ToString()); + + if (!bloom_filter.MightContain(document_path)) { + RemoveDocumentFromTarget(target_id, key, + /*updatedDocument=*/absl::nullopt); + removalCount++; + } + } + return removalCount; +} + RemoteEvent WatchChangeAggregator::CreateRemoteEvent( const SnapshotVersion& snapshot_version) { std::unordered_map target_changes; diff --git a/Firestore/core/src/remote/remote_event.h b/Firestore/core/src/remote/remote_event.h index bdec91a1703..308c1f28d8d 100644 --- a/Firestore/core/src/remote/remote_event.h +++ b/Firestore/core/src/remote/remote_event.h @@ -62,6 +62,9 @@ class TargetMetadataProvider { */ virtual absl::optional GetTargetDataForTarget( model::TargetId target_id) const = 0; + + /** Returns the database ID of the Firestore instance. */ + virtual const model::DatabaseId& GetDatabaseId() const = 0; }; /** @@ -410,6 +413,17 @@ class WatchChangeAggregator { bool TargetContainsDocument(model::TargetId target_id, const model::DocumentKey& key); + /** Returns whether a bloom filter removed the deleted documents successfully. + */ + bool ApplyBloomFilter(const ExistenceFilterWatchChange& existence_filter, + int current_count); + + /** + * Filter out removed documents based on bloom filter membership result and + * return number of documents removed. + */ + int FilterRemovedDocuments(const BloomFilter& bloom_filter, int target_id); + /** The internal state of all tracked targets. */ std::unordered_map target_states_; diff --git a/Firestore/core/src/remote/remote_store.cc b/Firestore/core/src/remote/remote_store.cc index cf98ff67296..9f1b409ff6d 100644 --- a/Firestore/core/src/remote/remote_store.cc +++ b/Firestore/core/src/remote/remote_store.cc @@ -566,6 +566,10 @@ absl::optional RemoteStore::GetTargetDataForTarget( : absl::optional{}; } +const model::DatabaseId& RemoteStore::GetDatabaseId() const { + return datastore_->database_info().database_id(); +} + void RemoteStore::RestartNetwork() { is_network_enabled_ = false; DisableNetworkInternal(); diff --git a/Firestore/core/src/remote/remote_store.h b/Firestore/core/src/remote/remote_store.h index 469a8726a98..e052198f0f5 100644 --- a/Firestore/core/src/remote/remote_store.h +++ b/Firestore/core/src/remote/remote_store.h @@ -193,6 +193,7 @@ class RemoteStore : public TargetMetadataProvider, model::TargetId target_id) const override; absl::optional GetTargetDataForTarget( model::TargetId target_id) const override; + const model::DatabaseId& GetDatabaseId() const override; void RunCountQuery(const core::Query& query, api::CountQueryCallback&& result_callback); diff --git a/Firestore/core/src/remote/serializer.cc b/Firestore/core/src/remote/serializer.cc index 30590e8609f..5a57403f3a8 100644 --- a/Firestore/core/src/remote/serializer.cc +++ b/Firestore/core/src/remote/serializer.cc @@ -1450,23 +1450,24 @@ std::unique_ptr Serializer::DecodeExistenceFilterWatchChange( ExistenceFilter Serializer::DecodeExistenceFilter( const google_firestore_v1_ExistenceFilter& filter) const { - // Create bloom filter if there is an unchanged_names present in the filter - // and inputs are valid, otherwise keep it null. - absl::optional bloom_filter; - if (filter.has_unchanged_names) { - // TODO(Mila): None of the ported spec tests here actually has the bloom - // filter json string, so hard code an empty bloom filter for now. The - // actual parsing code will be written in the next PR, where we can validate - // the parsing result. - // TODO(Mila): handle bloom filter creation failure. - StatusOr maybe_bloom_filter = - BloomFilter::Create(std::vector{}, 0, 0); - if (maybe_bloom_filter.ok()) { - bloom_filter = std::move(maybe_bloom_filter).ValueOrDie(); - } + if (!filter.has_unchanged_names) { + return {filter.count, absl::nullopt}; } - return {filter.count, std::move(bloom_filter)}; + int32_t hash_count = filter.unchanged_names.hash_count; + int32_t padding = 0; + std::vector bitmap; + if (filter.unchanged_names.has_bits) { + padding = filter.unchanged_names.bits.padding; + + pb_bytes_array_t* bitmap_ptr = filter.unchanged_names.bits.bitmap; + if (bitmap_ptr != nullptr) { + bitmap = std::vector(bitmap_ptr->bytes, + bitmap_ptr->bytes + bitmap_ptr->size); + } + } + return {filter.count, + BloomFilterParameters{std::move(bitmap), padding, hash_count}}; } bool Serializer::IsLocalResourceName(const ResourcePath& path) const { diff --git a/Firestore/core/test/unit/remote/fake_target_metadata_provider.cc b/Firestore/core/test/unit/remote/fake_target_metadata_provider.cc index cf330a5100b..88d1407d559 100644 --- a/Firestore/core/test/unit/remote/fake_target_metadata_provider.cc +++ b/Firestore/core/test/unit/remote/fake_target_metadata_provider.cc @@ -102,6 +102,10 @@ absl::optional FakeTargetMetadataProvider::GetTargetDataForTarget( return it->second; } +const model::DatabaseId& FakeTargetMetadataProvider::GetDatabaseId() const { + return database_id_; +} + } // namespace remote } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/unit/remote/fake_target_metadata_provider.h b/Firestore/core/test/unit/remote/fake_target_metadata_provider.h index 6b231774578..d59bcd92f0d 100644 --- a/Firestore/core/test/unit/remote/fake_target_metadata_provider.h +++ b/Firestore/core/test/unit/remote/fake_target_metadata_provider.h @@ -18,6 +18,7 @@ #define FIRESTORE_CORE_TEST_UNIT_REMOTE_FAKE_TARGET_METADATA_PROVIDER_H_ #include +#include #include #include "Firestore/core/src/local/target_data.h" @@ -73,10 +74,21 @@ class FakeTargetMetadataProvider : public TargetMetadataProvider { model::TargetId target_id) const override; absl::optional GetTargetDataForTarget( model::TargetId target_id) const override; + const model::DatabaseId& GetDatabaseId() const override; + + /** + * Sets the database_id to a custom value, used for getting Document's full + * path. + */ + void SetDatabaseId(model::DatabaseId database_id) { + database_id_ = std::move(database_id); + } private: std::unordered_map synced_keys_; std::unordered_map target_data_; + model::DatabaseId database_id_ = + model::DatabaseId("test-project", "(default)"); }; } // namespace remote diff --git a/Firestore/core/test/unit/remote/remote_event_test.cc b/Firestore/core/test/unit/remote/remote_event_test.cc index 5e68c22ed66..9c25aa198c5 100644 --- a/Firestore/core/test/unit/remote/remote_event_test.cc +++ b/Firestore/core/test/unit/remote/remote_event_test.cc @@ -105,6 +105,10 @@ class RemoteEventTest : public testing::Test { DocumentKeySet existing_keys, const std::vector>& watch_changes); + void OverrideDefaultDatabaseId(model::DatabaseId database_id) { + target_metadata_provider_.SetDatabaseId(std::move(database_id)); + } + ByteString resume_token1_; FakeTargetMetadataProvider target_metadata_provider_; std::unordered_map no_outstanding_responses_; @@ -512,7 +516,6 @@ TEST_F(RemoteEventTest, NoChangeWillStillMarkTheAffectedTargets) { ASSERT_TRUE(event.target_changes().at(1) == target_change); } -// TODO(Mila): Add test coverage for when the bloom filter is not null TEST_F(RemoteEventTest, ExistenceFilterMismatchClearsTarget) { std::unordered_map target_map = ActiveQueries({1, 2}); @@ -564,6 +567,115 @@ TEST_F(RemoteEventTest, ExistenceFilterMismatchClearsTarget) { ASSERT_EQ(event.document_updates().size(), 0); } +TEST_F(RemoteEventTest, ExistenceFilterMismatchWithBloomFilterSuccess) { + std::unordered_map target_map = ActiveQueries({1, 2}); + + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); + auto change1 = MakeDocChange({1}, {}, doc1.key(), doc1); + MutableDocument doc2 = Doc("docs/2", 2, Map("value", 2)); + auto change2 = MakeDocChange({1}, {}, doc2.key(), doc2); + auto change3 = + MakeTargetChange(WatchTargetChangeState::Current, {1}, resume_token1_); + + WatchChangeAggregator aggregator = CreateAggregator( + target_map, no_outstanding_responses_, + DocumentKeySet{doc1.key(), doc2.key()}, + Changes(std::move(change1), std::move(change2), std::move(change3))); + + // The BloomFilterParameters value below is created based on the document + // paths that are constructed using the following pattern: + // "projects/test-project/databases/test-database/documents/"+document_key. + // Override the database ID to ensure that the document path matches the + // pattern above. + OverrideDefaultDatabaseId(model::DatabaseId("test-project", "test-database")); + + RemoteEvent event = aggregator.CreateRemoteEvent(testutil::Version(3)); + + ASSERT_EQ(event.snapshot_version(), testutil::Version(3)); + ASSERT_EQ(event.document_updates().size(), 2); + ASSERT_EQ(event.document_updates().at(doc1.key()), doc1); + ASSERT_EQ(event.document_updates().at(doc2.key()), doc2); + + ASSERT_EQ(event.target_changes().size(), 2); + + TargetChange target_change1{resume_token1_, true, DocumentKeySet{}, + DocumentKeySet{doc1.key(), doc2.key()}, + DocumentKeySet{}}; + ASSERT_TRUE(event.target_changes().at(1) == target_change1); + + TargetChange target_change2{resume_token1_, false, DocumentKeySet{}, + DocumentKeySet{}, DocumentKeySet{}}; + ASSERT_TRUE(event.target_changes().at(2) == target_change2); + + // The given BloomFilter will return false on MightContain(doc1) and true on + // MightContain(doc2). + ExistenceFilterWatchChange change4{ + ExistenceFilter{1, BloomFilterParameters{{0x0E, 0x0F}, 1, 7}}, 1}; + // The existence filter identifies that doc1 is deleted, and skips the full + // re-query. + aggregator.HandleExistenceFilter(change4); + + event = aggregator.CreateRemoteEvent(testutil::Version(4)); + + ASSERT_EQ(event.target_changes().size(), 1); + ASSERT_EQ(event.target_mismatches().size(), 0); + ASSERT_EQ(event.document_updates().size(), 0); +} + +TEST_F(RemoteEventTest, + ExistenceFilterMismatchWithBloomFilterFalsePositiveResult) { + std::unordered_map target_map = ActiveQueries({1, 2}); + + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); + auto change1 = MakeDocChange({1}, {}, doc1.key(), doc1); + MutableDocument doc2 = Doc("docs/2", 2, Map("value", 2)); + auto change2 = MakeDocChange({1}, {}, doc2.key(), doc2); + auto change3 = + MakeTargetChange(WatchTargetChangeState::Current, {1}, resume_token1_); + + WatchChangeAggregator aggregator = CreateAggregator( + target_map, no_outstanding_responses_, + DocumentKeySet{doc1.key(), doc2.key()}, + Changes(std::move(change1), std::move(change2), std::move(change3))); + + RemoteEvent event = aggregator.CreateRemoteEvent(testutil::Version(3)); + + ASSERT_EQ(event.snapshot_version(), testutil::Version(3)); + ASSERT_EQ(event.document_updates().size(), 2); + ASSERT_EQ(event.document_updates().at(doc1.key()), doc1); + ASSERT_EQ(event.document_updates().at(doc2.key()), doc2); + + ASSERT_EQ(event.target_changes().size(), 2); + + TargetChange target_change1{resume_token1_, true, DocumentKeySet{}, + DocumentKeySet{doc1.key(), doc2.key()}, + DocumentKeySet{}}; + ASSERT_TRUE(event.target_changes().at(1) == target_change1); + + TargetChange target_change2{resume_token1_, false, DocumentKeySet{}, + DocumentKeySet{}, DocumentKeySet{}}; + ASSERT_TRUE(event.target_changes().at(2) == target_change2); + + // The given BloomFilter will return true on both MightContain(doc1) and + // MightContain(doc2). + ExistenceFilterWatchChange change4{ + ExistenceFilter{1, BloomFilterParameters{{0x42, 0xFE}, 2, 7}}, 1}; + // The existence filter cannot identify which doc is deleted. It will remove + // the document from target 1, but not synthesize a document delete. + aggregator.HandleExistenceFilter(change4); + + event = aggregator.CreateRemoteEvent(testutil::Version(4)); + + TargetChange target_change3{ByteString(), false, DocumentKeySet{}, + DocumentKeySet{}, + DocumentKeySet{doc1.key(), doc2.key()}}; + ASSERT_TRUE(event.target_changes().at(1) == target_change3); + + ASSERT_EQ(event.target_changes().size(), 1); + ASSERT_EQ(event.target_mismatches().size(), 1); + ASSERT_EQ(event.document_updates().size(), 0); +} + TEST_F(RemoteEventTest, ExistenceFilterMismatchRemovesCurrentChanges) { std::unordered_map target_map = ActiveQueries({1}); diff --git a/Firestore/core/test/unit/remote/serializer_test.cc b/Firestore/core/test/unit/remote/serializer_test.cc index b30889eacf4..bea30617594 100644 --- a/Firestore/core/test/unit/remote/serializer_test.cc +++ b/Firestore/core/test/unit/remote/serializer_test.cc @@ -1773,7 +1773,6 @@ TEST_F(SerializerTest, DecodesListenResponseWithDocumentRemove) { ExpectDeserializationRoundTrip(model, proto); } -// TODO(Mila): Add test coverage for when the bloom filter is not null TEST_F(SerializerTest, DecodesListenResponseWithExistenceFilter) { ExistenceFilterWatchChange model( ExistenceFilter(2, /*bloom_filter=*/absl::nullopt), 100); @@ -1787,6 +1786,26 @@ TEST_F(SerializerTest, DecodesListenResponseWithExistenceFilter) { ExpectDeserializationRoundTrip(model, proto); } +TEST_F(SerializerTest, + DecodesListenResponseWithExistenceFilterWhenBloomFilterNotNull) { + ExistenceFilterWatchChange model( + ExistenceFilter(555, BloomFilterParameters{{0x42, 0xFE}, 7, 33}), 999); + + v1::ListenResponse proto; + proto.mutable_filter()->set_count(555); + proto.mutable_filter()->set_target_id(999); + + v1::BloomFilter* bloom_filter = + proto.mutable_filter()->mutable_unchanged_names(); + bloom_filter->set_hash_count(33); + bloom_filter->mutable_bits()->set_padding(7); + bloom_filter->mutable_bits()->set_bitmap("\x42\xFE"); + + SCOPED_TRACE( + "DecodesListenResponseWithExistenceFilterWhenBloomFilterNotNull"); + ExpectDeserializationRoundTrip(model, proto); +} + TEST_F(SerializerTest, DecodesVersion) { auto version = Version(123456789); SnapshotVersion model(version.timestamp()); diff --git a/Firestore/core/test/unit/remote/watch_change_test.cc b/Firestore/core/test/unit/remote/watch_change_test.cc index c560d63a3f5..fe48c8ee2e5 100644 --- a/Firestore/core/test/unit/remote/watch_change_test.cc +++ b/Firestore/core/test/unit/remote/watch_change_test.cc @@ -40,12 +40,23 @@ TEST(WatchChangeTest, CanCreateDocumentWatchChange) { EXPECT_EQ(change.new_document(), doc); } -// TODO(Mila): Add test coverage for when the bloom filter is not null TEST(WatchChangeTest, CanCreateExistenceFilterWatchChange) { - ExistenceFilter filter{7, /*bloom_filter=*/absl::nullopt}; - ExistenceFilterWatchChange change{filter, 5}; - EXPECT_EQ(change.filter().count(), 7); - EXPECT_EQ(change.target_id(), 5); + { + ExistenceFilter filter{7, /*bloom_filter=*/absl::nullopt}; + ExistenceFilterWatchChange change{filter, 5}; + EXPECT_EQ(change.filter().count(), 7); + EXPECT_EQ(change.filter().bloom_filter_parameters(), absl::nullopt); + EXPECT_EQ(change.target_id(), 5); + } + { + BloomFilterParameters bloom_filter_parameters{{0x42, 0xFE}, 7, 33}; + ExistenceFilter filter{7, bloom_filter_parameters}; + ExistenceFilterWatchChange change{std::move(filter), 5}; + EXPECT_EQ(change.filter().count(), 7); + EXPECT_EQ(change.filter().bloom_filter_parameters(), + bloom_filter_parameters); + EXPECT_EQ(change.target_id(), 5); + } } TEST(WatchChangeTest, CanCreateWatchTargetChange) {