From 5cd10e4274341e7245442c94b28830986ce89115 Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Wed, 18 Jul 2018 15:15:19 -0400 Subject: [PATCH 1/7] Eliminate -Wimplicit-fallthrough triggers (#1553) Replace fallthrough comments with `ABSL_FALLTHROUGH_INTENDED`, allowing clean building with -Wimplicit-fallthrough. --- Firestore/Source/Local/FSTLevelDBMigrations.mm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Firestore/Source/Local/FSTLevelDBMigrations.mm b/Firestore/Source/Local/FSTLevelDBMigrations.mm index bd72c97a99b..b3ed935e76f 100644 --- a/Firestore/Source/Local/FSTLevelDBMigrations.mm +++ b/Firestore/Source/Local/FSTLevelDBMigrations.mm @@ -23,6 +23,7 @@ #import "Firestore/Source/Local/FSTLevelDBQueryCache.h" #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" +#include "absl/base/macros.h" #include "absl/strings/match.h" #include "leveldb/write_batch.h" @@ -108,11 +109,11 @@ + (void)runMigrationsWithTransaction:(firebase::firestore::local::LevelDbTransac switch (currentVersion) { case 0: EnsureTargetGlobal(transaction); - // Fallthrough + ABSL_FALLTHROUGH_INTENDED; case 1: // We're now guaranteed that the target global exists. We can safely add a count to it. AddTargetCount(transaction); - // Fallthrough + ABSL_FALLTHROUGH_INTENDED; default: if (currentVersion < kSchemaVersion) { SaveVersion(kSchemaVersion, transaction); From 7d438b5e0c112c5c67ca59688703ea4daa30c5a9 Mon Sep 17 00:00:00 2001 From: Michael Lehenbauer Date: Wed, 18 Jul 2018 20:33:32 -0700 Subject: [PATCH 2/7] b/72533250: Fix issue with limbo resolutions triggering incorrect manufactured deletes. (#1556) [Port of https://github.com/firebase/firebase-js-sdk/pull/1014] This fixes an issue occurring when a limbo target receives a documentUpdate, then a global snapshot, and then a CURRENT. Because there was a global snapshot before the CURRENT, WatchChangeAggregator has no pending document updates and calls SyncEngine.remoteKeysForTarget to see if we previously got any document from the backend for the target. See: https://github.com/firebase/firebase-js-sdk/blob/6905339235ad801291edc696dd75a08e80647f5b/packages/firestore/src/remote/watch_change.ts#L422 Prior to this change, remoteKeysForTarget returned empty because it relies on our Views to track the contents of the target, and we don't have Views for limbo targets. Thus WatchChangeAggregator incorrectly manufactures a NoDocument document update which deletes data from our cache. The fix is to have SyncEngine track the fact that we did indeed get a document for the limbo resolution and return it from remoteKeysForTarget. --- .../Tests/SpecTests/json/limbo_spec_test.json | 417 +++++++++++++++++- Firestore/Source/Core/FSTSyncEngine.mm | 78 +++- 2 files changed, 483 insertions(+), 12 deletions(-) diff --git a/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json b/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json index a186496f029..fe307b9b191 100644 --- a/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json @@ -1147,9 +1147,9 @@ } ] }, - "Limbo documents handle receiving ack and then current": { + "Limbo resolution handles snapshot before CURRENT": { "describeName": "Limbo Documents:", - "itName": "Limbo documents handle receiving ack and then current", + "itName": "Limbo resolution handles snapshot before CURRENT", "tags": [], "config": { "useGarbageCollection": false @@ -1473,6 +1473,11 @@ ] } }, + { + "watchSnapshot": { + "version": 2000 + } + }, { "watchCurrent": [ [ @@ -1557,5 +1562,413 @@ } } ] + }, + "Limbo resolution handles snapshot before CURRENT [no document update]": { + "describeName": "Limbo Documents:", + "itName": "Limbo resolution handles snapshot before CURRENT [no document update]", + "tags": [], + "config": { + "useGarbageCollection": false + }, + "steps": [ + { + "userListen": [ + 2, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 1000, + { + "include": true, + "key": "a" + } + ], + [ + "collection/b", + 1000, + { + "include": true, + "key": "b" + } + ] + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "added": [ + [ + "collection/a", + 1000, + { + "include": true, + "key": "a" + } + ], + [ + "collection/b", + 1000, + { + "include": true, + "key": "b" + } + ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userUnlisten": [ + 2, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": {} + } + }, + { + "userListen": [ + 4, + { + "path": "collection", + "limit": 1, + "filters": [ + [ + "include", + "==", + true + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "4": { + "query": { + "path": "collection", + "limit": 1, + "filters": [ + [ + "include", + "==", + true + ] + ], + "orderBys": [] + }, + "resumeToken": "" + } + } + }, + "expect": [ + { + "query": { + "path": "collection", + "limit": 1, + "filters": [ + [ + "include", + "==", + true + ] + ], + "orderBys": [] + }, + "added": [ + [ + "collection/a", + 1000, + { + "include": true, + "key": "a" + } + ] + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false + } + ] + }, + { + "watchAck": [ + 4 + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 1000, + { + "include": true, + "key": "a" + } + ] + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, + "expect": [ + { + "query": { + "path": "collection", + "limit": 1, + "filters": [ + [ + "include", + "==", + true + ] + ], + "orderBys": [] + }, + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userPatch": [ + "collection/a", + { + "include": false + } + ], + "expect": [ + { + "query": { + "path": "collection", + "limit": 1, + "filters": [ + [ + "include", + "==", + true + ] + ], + "orderBys": [] + }, + "added": [ + [ + "collection/b", + 1000, + { + "include": true, + "key": "b" + } + ] + ], + "removed": [ + [ + "collection/a", + 1000, + { + "include": true, + "key": "a" + } + ] + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false + } + ], + "stateExpect": { + "limboDocs": [ + "collection/b" + ], + "activeTargets": { + "1": { + "query": { + "path": "collection/b", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + }, + "4": { + "query": { + "path": "collection", + "limit": 1, + "filters": [ + [ + "include", + "==", + true + ] + ], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 1 + ] + }, + { + "watchSnapshot": { + "version": 2000 + } + }, + { + "watchCurrent": [ + [ + 1 + ], + "resume-token-3000" + ] + }, + { + "watchSnapshot": { + "version": 3000 + }, + "stateExpect": { + "limboDocs": [], + "activeTargets": { + "4": { + "query": { + "path": "collection", + "limit": 1, + "filters": [ + [ + "include", + "==", + true + ] + ], + "orderBys": [] + }, + "resumeToken": "" + } + } + }, + "expect": [ + { + "query": { + "path": "collection", + "limit": 1, + "filters": [ + [ + "include", + "==", + true + ] + ], + "orderBys": [] + }, + "removed": [ + [ + "collection/b", + 1000, + { + "include": true, + "key": "b" + } + ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 1000, + { + "include": true, + "key": "a" + } + ] + ], + "removedTargets": [ + 4 + ] + } + }, + { + "watchSnapshot": { + "version": 4000 + } + } + ] } } diff --git a/Firestore/Source/Core/FSTSyncEngine.mm b/Firestore/Source/Core/FSTSyncEngine.mm index bf7b053964b..dad81d4cd3b 100644 --- a/Firestore/Source/Core/FSTSyncEngine.mm +++ b/Firestore/Source/Core/FSTSyncEngine.mm @@ -114,6 +114,27 @@ - (instancetype)initWithQuery:(FSTQuery *)query @end +#pragma mark - LimboResolution + +/** Tracks a limbo resolution. */ +class LimboResolution { + public: + LimboResolution() { + } + + explicit LimboResolution(const DocumentKey &key) : key{key} { + } + + DocumentKey key; + + /** + * Set to true once we've received a document. This is used in remoteKeysForTarget and + * ultimately used by FSTWatchChangeAggregator to decide whether it needs to manufacture a delete + * event for the target once the target is CURRENT. + */ + bool document_received = false; +}; + #pragma mark - FSTSyncEngine @interface FSTSyncEngine () @@ -151,8 +172,11 @@ @implementation FSTSyncEngine { */ std::map _limboTargetsByKey; - /** The inverse of _limboTargetsByKey, a map of TargetId to the key of the limbo doc. */ - std::map _limboKeysByTarget; + /** + * Basically the inverse of limboTargetsByKey, a map of target ID to a LimboResolution (which + * includes the DocumentKey as well as whether we've received a document for the target). + */ + std::map _limboResolutionsByTarget; User _currentUser; } @@ -288,6 +312,35 @@ - (void)transactionWithRetries:(int)retries - (void)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent { [self assertDelegateExistsForSelector:_cmd]; + // Update `receivedDocument` as appropriate for any limbo targets. + for (const auto &entry : remoteEvent.targetChanges) { + FSTTargetID targetID = entry.first; + FSTTargetChange *change = entry.second; + const auto iter = _limboResolutionsByTarget.find(targetID); + if (iter != _limboResolutionsByTarget.end()) { + LimboResolution &limboResolution = iter->second; + // Since this is a limbo resolution lookup, it's for a single document and it could be + // added, modified, or removed, but not a combination. + HARD_ASSERT(change.addedDocuments.size() + change.modifiedDocuments.size() + + change.removedDocuments.size() <= + 1, + "Limbo resolution for single document contains multiple changes."); + + if (change.addedDocuments.size() > 0) { + limboResolution.document_received = true; + } else if (change.modifiedDocuments.size() > 0) { + HARD_ASSERT(limboResolution.document_received, + "Received change for limbo target document without add."); + } else if (change.removedDocuments.size() > 0) { + HARD_ASSERT(limboResolution.document_received, + "Received remove for limbo target document without add."); + limboResolution.document_received = false; + } else { + // This was probably just a CURRENT targetChange or similar. + } + } + } + FSTMaybeDocumentDictionary *changes = [self.localStore applyRemoteEvent:remoteEvent]; [self emitNewSnapshotsWithChanges:changes remoteEvent:remoteEvent]; } @@ -310,13 +363,13 @@ - (void)applyChangedOnlineState:(FSTOnlineState)onlineState { - (void)rejectListenWithTargetID:(const TargetId)targetID error:(NSError *)error { [self assertDelegateExistsForSelector:_cmd]; - const auto iter = _limboKeysByTarget.find(targetID); - if (iter != _limboKeysByTarget.end()) { - const DocumentKey limboKey = iter->second; + const auto iter = _limboResolutionsByTarget.find(targetID); + if (iter != _limboResolutionsByTarget.end()) { + const DocumentKey limboKey = iter->second.key; // Since this query failed, we won't want to manually unlisten to it. // So go ahead and remove it from bookkeeping. _limboTargetsByKey.erase(limboKey); - _limboKeysByTarget.erase(targetID); + _limboResolutionsByTarget.erase(targetID); // TODO(dimond): Retry on transient errors? @@ -484,7 +537,7 @@ - (void)trackLimboChange:(FSTLimboDocumentChange *)limboChange { targetID:limboTargetID listenSequenceNumber:kIrrelevantSequenceNumber purpose:FSTQueryPurposeLimboResolution]; - _limboKeysByTarget[limboTargetID] = key; + _limboResolutionsByTarget.emplace(limboTargetID, LimboResolution{key}); [self.remoteStore listenToTargetWithQueryData:queryData]; _limboTargetsByKey[key] = limboTargetID; } @@ -499,7 +552,7 @@ - (void)removeLimboTargetForKey:(const DocumentKey &)key { TargetId limboTargetID = iter->second; [self.remoteStore stopListeningToTargetID:limboTargetID]; _limboTargetsByKey.erase(key); - _limboKeysByTarget.erase(limboTargetID); + _limboResolutionsByTarget.erase(limboTargetID); } // Used for testing @@ -520,8 +573,13 @@ - (void)userDidChange:(const User &)user { } - (firebase::firestore::model::DocumentKeySet)remoteKeysForTarget:(FSTBoxedTargetID *)targetId { - FSTQueryView *queryView = self.queryViewsByTarget[targetId]; - return queryView ? queryView.view.syncedDocuments : DocumentKeySet{}; + const auto iter = _limboResolutionsByTarget.find([targetId intValue]); + if (iter != _limboResolutionsByTarget.end() && iter->second.document_received) { + return DocumentKeySet{iter->second.key}; + } else { + FSTQueryView *queryView = self.queryViewsByTarget[targetId]; + return queryView ? queryView.view.syncedDocuments : DocumentKeySet{}; + } } @end From 9cc378a420a925aaada6030959105d548ebb4c14 Mon Sep 17 00:00:00 2001 From: Gil Date: Thu, 19 Jul 2018 07:15:47 -0700 Subject: [PATCH 3/7] Allow remote updates from watch to heal a cache with synthesized deletes in it (#1557) * Update spec tests from the js-sdk * Allow remote updates from watch to heal a cache with synthesized deletes in it Port of https://github.com/firebase/firebase-js-sdk/pull/1015 Addresses #1548 --- .../SpecTests/json/listen_spec_test.json | 689 +++++++++++++++++- Firestore/Source/Local/FSTLocalStore.mm | 27 +- 2 files changed, 710 insertions(+), 6 deletions(-) diff --git a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json index e838d2fda72..0448b2f43f2 100644 --- a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json @@ -1627,7 +1627,7 @@ } ] ], - "targets": [ + "removedTargets": [ 2 ] } @@ -1765,6 +1765,693 @@ } ] }, + "Individual (deleted) documents cannot revert": { + "describeName": "Listens:", + "itName": "Individual (deleted) documents cannot revert", + "tags": [], + "config": { + "useGarbageCollection": false + }, + "steps": [ + { + "userListen": [ + 2, + { + "path": "collection", + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "collection", + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 1000, + { + "v": "v1000", + "visible": true + } + ] + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [] + }, + "added": [ + [ + "collection/a", + 1000, + { + "v": "v1000", + "visible": true + } + ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userUnlisten": [ + 2, + { + "path": "collection", + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": {} + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": [ + 4, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "4": { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "added": [ + [ + "collection/a", + 1000, + { + "v": "v1000", + "visible": true + } + ] + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false + } + ] + }, + { + "watchAck": [ + 4 + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 3000, + null + ] + ], + "removedTargets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-4000" + ] + }, + { + "watchSnapshot": { + "version": 4000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "removed": [ + [ + "collection/a", + 1000, + { + "v": "v1000", + "visible": true + } + ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userUnlisten": [ + 4, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": {} + } + }, + { + "watchRemove": { + "targetIds": [ + 4 + ] + } + }, + { + "userListen": [ + 2, + { + "path": "collection", + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "collection", + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [] + }, + "resumeToken": "resume-token-1000" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 2000, + { + "v": "v2000", + "visible": false + } + ] + ], + "removedTargets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-5000" + ] + }, + { + "watchSnapshot": { + "version": 5000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [] + }, + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userUnlisten": [ + 2, + { + "path": "collection", + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": {} + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": [ + 4, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "4": { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "resumeToken": "resume-token-4000" + } + } + } + }, + { + "watchAck": [ + 4 + ] + }, + { + "watchEntity": { + "docs": [], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-6000" + ] + }, + { + "watchSnapshot": { + "version": 6000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + } + ] + }, + "Deleted documents in cache are fixed": { + "describeName": "Listens:", + "itName": "Deleted documents in cache are fixed", + "tags": [], + "config": { + "useGarbageCollection": false + }, + "steps": [ + { + "userListen": [ + 2, + { + "path": "collection", + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "collection", + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 1000, + { + "key": "a" + } + ] + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [] + }, + "added": [ + [ + "collection/a", + 1000, + { + "key": "a" + } + ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 2000, + null + ] + ], + "removedTargets": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "version": 2000, + "targetIds": [ + 2 + ], + "resumeToken": "resume-token-2000" + } + }, + { + "watchSnapshot": { + "version": 2000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [] + }, + "removed": [ + [ + "collection/a", + 1000, + { + "key": "a" + } + ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userUnlisten": [ + 2, + { + "path": "collection", + "filters": [ + [ + "key", + "==", + "a" + ] + ], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": {} + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": [ + 4, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "4": { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 4 + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 1000, + { + "key": "a" + } + ] + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-3000" + ] + }, + { + "watchSnapshot": { + "version": 3000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "added": [ + [ + "collection/a", + 1000, + { + "key": "a" + } + ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + } + ] + }, "Listens are reestablished after network disconnect": { "describeName": "Listens:", "itName": "Listens are reestablished after network disconnect", diff --git a/Firestore/Source/Local/FSTLocalStore.mm b/Firestore/Source/Local/FSTLocalStore.mm index 6aab78e8b6e..aed0e552ef5 100644 --- a/Firestore/Source/Local/FSTLocalStore.mm +++ b/Firestore/Source/Local/FSTLocalStore.mm @@ -268,6 +268,7 @@ - (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent { FSTListenSequenceNumber sequenceNumber = [self.listenSequence next]; id queryCache = self.queryCache; + DocumentKeySet authoritativeUpdates; for (const auto &entry : remoteEvent.targetChanges) { FSTTargetID targetID = entry.first; FSTBoxedTargetID *boxedTargetID = @(targetID); @@ -279,6 +280,21 @@ - (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent { continue; } + // When a global snapshot contains updates (either add or modify) we can completely trust + // these updates as authoritative and blindly apply them to our cache (as a defensive measure + // to promote self-healing in the unfortunate case that our cache is ever somehow corrupted / + // out-of-sync). + // + // If the document is only updated while removing it from a target then watch isn't obligated + // to send the absolute latest version: it can send the first version that caused the document + // not to match. + for (const DocumentKey &key : change.addedDocuments) { + authoritativeUpdates = authoritativeUpdates.insert(key); + } + for (const DocumentKey &key : change.modifiedDocuments) { + authoritativeUpdates = authoritativeUpdates.insert(key); + } + [queryCache removeMatchingKeys:change.removedDocuments forTargetID:targetID]; [queryCache addMatchingKeys:change.addedDocuments forTargetID:targetID]; @@ -303,11 +319,12 @@ - (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent { FSTMaybeDocument *doc = kv.second; changedDocKeys = changedDocKeys.insert(key); FSTMaybeDocument *existingDoc = [self.remoteDocumentCache entryForKey:key]; - // Make sure we don't apply an old document version to the remote cache, though we - // make an exception for SnapshotVersion::None() which can happen for manufactured - // events (e.g. in the case of a limbo document resolution failing). - if (!existingDoc || SnapshotVersion{doc.version} == SnapshotVersion::None() || - SnapshotVersion{doc.version} >= SnapshotVersion{existingDoc.version}) { + + // If a document update isn't authoritative, make sure we don't apply an old document version + // to the remote cache. We make an exception for SnapshotVersion.MIN which can happen for + // manufactured events (e.g. in the case of a limbo document resolution failing). + if (!existingDoc || doc.version == SnapshotVersion::None() || + authoritativeUpdates.contains(doc.key) || doc.version >= existingDoc.version) { [self.remoteDocumentCache addEntry:doc]; } else { LOG_DEBUG( From c22a190871edb5694909f349782ea6518dfbfcf2 Mon Sep 17 00:00:00 2001 From: Gil Date: Thu, 19 Jul 2018 11:21:22 -0700 Subject: [PATCH 4/7] Add a schema migration that drops the query cache (#1558) This is a force fix for potential existence filter mismatches caused by firebase/firebase-ios-sdk#1548 The essential problem is that when resuming a query, the server is allowed to forget deletes. If the number of incorrectly synthesized deletes matches the number of server-forgotten deletes then the existence filter can give a false positive, preventing the cache from self healing. Dropping the query cache clears any client-side resume token which prevents a false positive existence filter mismatch. Note that the remote document cache and mutation queues are unaffected so any cached documents that do exist will still work while offline https://github.com/firebase/firebase-js-sdk/pull/1019 --- .../Tests/Local/FSTLevelDBMigrationsTests.mm | 149 ++++++++++++++---- Firestore/Source/Local/FSTLevelDB.mm | 4 +- Firestore/Source/Local/FSTLevelDBMigrations.h | 8 +- .../Source/Local/FSTLevelDBMigrations.mm | 113 ++++++------- .../firestore/local/leveldb_transaction.h | 4 + 5 files changed, 191 insertions(+), 87 deletions(-) diff --git a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm index e6ab720a523..4f8c575ee80 100644 --- a/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm +++ b/Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm @@ -18,22 +18,29 @@ #include #include +#include #import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" #import "Firestore/Source/Local/FSTLevelDB.h" #import "Firestore/Source/Local/FSTLevelDBKey.h" #import "Firestore/Source/Local/FSTLevelDBMigrations.h" +#import "Firestore/Source/Local/FSTLevelDBMutationQueue.h" #import "Firestore/Source/Local/FSTLevelDBQueryCache.h" #include "Firestore/core/src/firebase/firestore/util/ordered_code.h" +#include "Firestore/core/src/firebase/firestore/util/status.h" +#include "Firestore/core/test/firebase/firestore/testutil/testutil.h" +#include "absl/strings/match.h" #include "leveldb/db.h" #import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h" NS_ASSUME_NONNULL_BEGIN +using firebase::firestore::FirestoreErrorCode; using firebase::firestore::local::LevelDbTransaction; using firebase::firestore::util::OrderedCode; +using firebase::firestore::testutil::Key; using leveldb::DB; using leveldb::Options; using leveldb::Status; @@ -64,54 +71,136 @@ - (void)tearDown { - (void)testAddsTargetGlobal { FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()]; XCTAssertNil(metadata, @"Not expecting metadata yet, we should have an empty db"); - LevelDbTransaction transaction(_db.get(), "testAddsTargetGlobal"); - [FSTLevelDBMigrations runMigrationsWithTransaction:&transaction]; - transaction.Commit(); + [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get()]; + metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()]; XCTAssertNotNil(metadata, @"Migrations should have added the metadata"); } - (void)testSetsVersionNumber { - LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber"); - FSTLevelDBSchemaVersion initial = - [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction]; - XCTAssertEqual(0, initial, "No version should be equivalent to 0"); - - // Pick an arbitrary high migration number and migrate to it. - [FSTLevelDBMigrations runMigrationsWithTransaction:&transaction]; - FSTLevelDBSchemaVersion actual = [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction]; - XCTAssertGreaterThan(actual, 0, @"Expected to migrate to a schema version > 0"); + { + LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber before"); + FSTLevelDBSchemaVersion initial = + [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction]; + XCTAssertEqual(0, initial, "No version should be equivalent to 0"); + } + + { + // Pick an arbitrary high migration number and migrate to it. + [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get()]; + + LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber after"); + FSTLevelDBSchemaVersion actual = + [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction]; + XCTAssertGreaterThan(actual, 0, @"Expected to migrate to a schema version > 0"); + } } -- (void)testCountsQueries { - NSUInteger expected = 50; +#define ASSERT_NOT_FOUND(transaction, key) \ + do { \ + std::string unused_result; \ + Status status = transaction.Get(key, &unused_result); \ + XCTAssertTrue(status.IsNotFound()); \ + } while (0) + +#define ASSERT_FOUND(transaction, key) \ + do { \ + std::string unused_result; \ + Status status = transaction.Get(key, &unused_result); \ + XCTAssertTrue(status.ok()); \ + } while (0) + +- (void)testDropsTheQueryCache { + NSString *userID = @"user"; + FSTBatchID batchID = 1; + FSTTargetID targetID = 2; + + FSTDocumentKey *key1 = Key("documents/1"); + FSTDocumentKey *key2 = Key("documents/2"); + + std::string targetKeys[] = { + [FSTLevelDBTargetKey keyWithTargetID:targetID], + [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key1], + [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key2], + [FSTLevelDBDocumentTargetKey keyWithDocumentKey:key1 targetID:targetID], + [FSTLevelDBDocumentTargetKey keyWithDocumentKey:key2 targetID:targetID], + [FSTLevelDBQueryTargetKey keyWithCanonicalID:"foo.bar.baz" targetID:targetID], + }; + + // Keys that should not be modified by the dropping the query cache + std::string preservedKeys[] = { + [self dummyKeyForTable:"targetA"], + [FSTLevelDBMutationQueueKey keyWithUserID:userID], + [FSTLevelDBMutationKey keyWithUserID:userID batchID:batchID], + }; + + [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:2]; { // Setup some targets to be counted in the migration. - LevelDbTransaction transaction(_db.get(), "testCountsQueries setup"); - for (int i = 0; i < expected; i++) { - std::string key = [FSTLevelDBTargetKey keyWithTargetID:i]; - transaction.Put(key, "dummy"); + LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCache setup"); + for (const std::string &key : targetKeys) { + transaction.Put(key, "target"); + } + for (const std::string &key : preservedKeys) { + transaction.Put(key, "preserved"); } - // Add a dummy entry after the targets to make sure the iteration is correctly bounded. - // Use a table that would sort logically right after that table 'target'. - std::string dummyKey; - // Magic number that indicates a table name follows. Needed to mimic the prefix to the target - // table. - OrderedCode::WriteSignedNumIncreasing(&dummyKey, 5); - OrderedCode::WriteString(&dummyKey, "targetA"); - transaction.Put(dummyKey, "dummy"); transaction.Commit(); } + [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:3]; { - LevelDbTransaction transaction(_db.get(), "testCountsQueries"); - [FSTLevelDBMigrations runMigrationsWithTransaction:&transaction]; - transaction.Commit(); + LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCache"); + for (const std::string &key : targetKeys) { + ASSERT_NOT_FOUND(transaction, key); + } + for (const std::string &key : preservedKeys) { + ASSERT_FOUND(transaction, key); + } + FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()]; - XCTAssertEqual(expected, metadata.targetCount, @"Failed to count all of the targets we added"); + XCTAssertNotNil(metadata, @"Metadata should have been added"); + XCTAssertEqual(metadata.targetCount, 0); } } +- (void)testDropsTheQueryCacheWithThousandsOfEntries { + [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:2]; + { + // Setup some targets to be destroyed. + LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCacheWithThousandsOfEntries setup"); + for (int i = 0; i < 10000; ++i) { + transaction.Put([FSTLevelDBTargetKey keyWithTargetID:i], ""); + } + transaction.Commit(); + } + + [FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:3]; + { + LevelDbTransaction transaction(_db.get(), "Verify"); + std::string prefix = [FSTLevelDBTargetKey keyPrefix]; + + auto it = transaction.NewIterator(); + std::vector found_keys; + for (it->Seek(prefix); it->Valid() && absl::StartsWith(it->key(), prefix); it->Next()) { + found_keys.push_back(std::string{it->key()}); + } + + XCTAssertEqual(found_keys, std::vector{}); + } +} + +/** + * Creates the name of a dummy entry to make sure the iteration is correctly bounded. + */ +- (std::string)dummyKeyForTable:(const char *)tableName { + std::string dummyKey; + // Magic number that indicates a table name follows. Needed to mimic the prefix to the target + // table. + OrderedCode::WriteSignedNumIncreasing(&dummyKey, 5); + OrderedCode::WriteString(&dummyKey, tableName); + return dummyKey; +} + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Local/FSTLevelDB.mm b/Firestore/Source/Local/FSTLevelDB.mm index 9dc50a2c8a2..db9992e3eaa 100644 --- a/Firestore/Source/Local/FSTLevelDB.mm +++ b/Firestore/Source/Local/FSTLevelDB.mm @@ -150,9 +150,7 @@ - (BOOL)start:(NSError **)error { return NO; } _ptr.reset(database); - LevelDbTransaction transaction(_ptr.get(), "Start LevelDB"); - [FSTLevelDBMigrations runMigrationsWithTransaction:&transaction]; - transaction.Commit(); + [FSTLevelDBMigrations runMigrationsWithDatabase:_ptr.get()]; return YES; } diff --git a/Firestore/Source/Local/FSTLevelDBMigrations.h b/Firestore/Source/Local/FSTLevelDBMigrations.h index 1724edfa453..0987da53c61 100644 --- a/Firestore/Source/Local/FSTLevelDBMigrations.h +++ b/Firestore/Source/Local/FSTLevelDBMigrations.h @@ -36,7 +36,13 @@ typedef int32_t FSTLevelDBSchemaVersion; /** * Runs any migrations needed to bring the given database up to the current schema version */ -+ (void)runMigrationsWithTransaction:(firebase::firestore::local::LevelDbTransaction *)transaction; ++ (void)runMigrationsWithDatabase:(leveldb::DB *)database; + +/** + * Runs any migrations needed to bring the given database up to the given schema version + */ ++ (void)runMigrationsWithDatabase:(leveldb::DB *)database + upToVersion:(FSTLevelDBSchemaVersion)version; @end diff --git a/Firestore/Source/Local/FSTLevelDBMigrations.mm b/Firestore/Source/Local/FSTLevelDBMigrations.mm index b3ed935e76f..80348cefbc0 100644 --- a/Firestore/Source/Local/FSTLevelDBMigrations.mm +++ b/Firestore/Source/Local/FSTLevelDBMigrations.mm @@ -24,32 +24,34 @@ #include "Firestore/core/src/firebase/firestore/util/hard_assert.h" #include "absl/base/macros.h" +#include "absl/memory/memory.h" #include "absl/strings/match.h" #include "leveldb/write_batch.h" NS_ASSUME_NONNULL_BEGIN -// Current version of the schema defined in this file. -static FSTLevelDBSchemaVersion kSchemaVersion = 2; +/** + * Schema version for the iOS client. + * + * Note that tables aren't a concept in LevelDB. They exist in our schema as just prefixes on keys. + * This means tables don't need to be created but they also can't easily be dropped and re-created. + * + * Migrations: + * * Migration 1 used to ensure the target_global row existed, without clearing it. No longer + * required because migration 3 unconditionally clears it. + * * Migration 2 used to ensure that the target_global row had a correct count of targets. No + * longer required because migration 3 deletes them all. + * * Migration 3 deletes the entire query cache to deal with cache corruption related to + * limbo resolution. Addresses https://github.com/firebase/firebase-ios-sdk/issues/1548. + */ +static FSTLevelDBSchemaVersion kSchemaVersion = 3; using firebase::firestore::local::LevelDbTransaction; -using leveldb::DB; using leveldb::Iterator; using leveldb::Status; using leveldb::Slice; using leveldb::WriteOptions; -/** - * Ensures that the global singleton target metadata row exists in LevelDB. - */ -static void EnsureTargetGlobal(LevelDbTransaction *transaction) { - FSTPBTargetGlobal *targetGlobal = - [FSTLevelDBQueryCache readTargetMetadataWithTransaction:transaction]; - if (!targetGlobal) { - transaction->Put([FSTLevelDBTargetGlobalKey key], [FSTPBTargetGlobal message]); - } -} - /** * Save the given version number as the current version of the schema of the database. * @param version The version to save @@ -61,30 +63,39 @@ static void SaveVersion(FSTLevelDBSchemaVersion version, LevelDbTransaction *tra transaction->Put(key, version_string); } -/** - * This function counts the number of targets that currently exist in the given db. It - * then reads the target global row, adds the count to the metadata from that row, and writes - * the metadata back. - * - * It assumes the metadata has already been written and is able to be read in this transaction. - */ -static void AddTargetCount(LevelDbTransaction *transaction) { - auto it = transaction->NewIterator(); - std::string start_key = [FSTLevelDBTargetKey keyPrefix]; - it->Seek(start_key); - - int32_t count = 0; - while (it->Valid() && absl::StartsWith(it->key(), start_key)) { - count++; - it->Next(); +static void DeleteEverythingWithPrefix(const std::string &prefix, leveldb::DB *db) { + bool more_deletes = true; + while (more_deletes) { + LevelDbTransaction transaction(db, "Delete everything with prefix"); + auto it = transaction.NewIterator(); + + more_deletes = false; + for (it->Seek(prefix); it->Valid() && absl::StartsWith(it->key(), prefix); it->Next()) { + if (transaction.changed_keys() >= 1000) { + more_deletes = true; + break; + } + transaction.Delete(it->key()); + } + + transaction.Commit(); } +} + +/** Migration 3. */ +static void ClearQueryCache(leveldb::DB *db) { + DeleteEverythingWithPrefix([FSTLevelDBTargetKey keyPrefix], db); + DeleteEverythingWithPrefix([FSTLevelDBDocumentTargetKey keyPrefix], db); + DeleteEverythingWithPrefix([FSTLevelDBTargetDocumentKey keyPrefix], db); + DeleteEverythingWithPrefix([FSTLevelDBQueryTargetKey keyPrefix], db); + + LevelDbTransaction transaction(db, "Drop query cache"); - FSTPBTargetGlobal *targetGlobal = - [FSTLevelDBQueryCache readTargetMetadataWithTransaction:transaction]; - HARD_ASSERT(targetGlobal != nil, - "We should have a metadata row as it was added in an earlier migration"); - targetGlobal.targetCount = count; - transaction->Put([FSTLevelDBTargetGlobalKey key], targetGlobal); + // Reset the target global entry too (to reset the target count). + transaction.Put([FSTLevelDBTargetGlobalKey key], [FSTPBTargetGlobal message]); + + SaveVersion(3, &transaction); + transaction.Commit(); } @implementation FSTLevelDBMigrations @@ -101,23 +112,19 @@ + (FSTLevelDBSchemaVersion)schemaVersionWithTransaction: } } -+ (void)runMigrationsWithTransaction:(firebase::firestore::local::LevelDbTransaction *)transaction { - FSTLevelDBSchemaVersion currentVersion = [self schemaVersionWithTransaction:transaction]; - // Each case in this switch statement intentionally falls through. This lets us - // start at the current schema version and apply any migrations that have not yet - // been applied, to bring us up to current, as defined by the kSchemaVersion constant. - switch (currentVersion) { - case 0: - EnsureTargetGlobal(transaction); - ABSL_FALLTHROUGH_INTENDED; - case 1: - // We're now guaranteed that the target global exists. We can safely add a count to it. - AddTargetCount(transaction); - ABSL_FALLTHROUGH_INTENDED; - default: - if (currentVersion < kSchemaVersion) { - SaveVersion(kSchemaVersion, transaction); - } ++ (void)runMigrationsWithDatabase:(leveldb::DB *)database { + [self runMigrationsWithDatabase:database upToVersion:kSchemaVersion]; +} + ++ (void)runMigrationsWithDatabase:(leveldb::DB *)database + upToVersion:(FSTLevelDBSchemaVersion)toVersion { + LevelDbTransaction transaction{database, "Read schema version"}; + FSTLevelDBSchemaVersion fromVersion = [self schemaVersionWithTransaction:&transaction]; + + // This must run unconditionally because schema migrations were added to iOS after the first + // release. There may be clients that have never run any migrations that have existing targets. + if (fromVersion < 3 && toVersion >= 3) { + ClearQueryCache(database); } } diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h index a6ddce2d5d9..9b308fcf86c 100644 --- a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h +++ b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h @@ -144,6 +144,10 @@ class LevelDbTransaction { */ static const leveldb::WriteOptions& DefaultWriteOptions(); + size_t changed_keys() const { + return mutations_.size() + deletions_.size(); + } + /** * Remove the database entry (if any) for "key". It is not an error if "key" * did not exist in the database. From 1fee4465e25dcf1ec652639a361c8dc01b305a79 Mon Sep 17 00:00:00 2001 From: Marek Gilbert Date: Thu, 19 Jul 2018 10:39:03 -0700 Subject: [PATCH 5/7] Update CHANGELOG for Firestore v0.12.6 --- Firestore/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index c7b745d3f56..dbdfdc033ef 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,5 +1,10 @@ # Unreleased +# v0.12.6 +- [fixed] Fixed an issue where queries returned fewer results than they should, + caused by documents that were cached as deleted when they should not have + been (#1548). + # v0.12.5 - [changed] Internal improvements. From 9a241b2d7ed6d7243e0b91237c0f6b36fb880af6 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 6 Jul 2018 20:46:00 +0200 Subject: [PATCH 6/7] Add spec test support for target-scoped resume tokens (#1498) --- .../Tests/SpecTests/FSTMockDatastore.mm | 7 +- .../Example/Tests/SpecTests/FSTSpecTests.mm | 63 +-- .../SpecTests/json/collection_spec_test.json | 8 +- .../json/existence_filter_spec_test.json | 191 ++++++-- .../Tests/SpecTests/json/limbo_spec_test.json | 156 ++++-- .../Tests/SpecTests/json/limit_spec_test.json | 122 +++-- .../SpecTests/json/listen_spec_test.json | 459 +++++++++++++++--- .../SpecTests/json/offline_spec_test.json | 61 ++- .../SpecTests/json/orderby_spec_test.json | 8 +- .../SpecTests/json/persistence_spec_test.json | 24 +- .../json/remote_store_spec_test.json | 56 ++- .../json/resume_token_spec_test.json | 24 +- .../Tests/SpecTests/json/write_spec_test.json | 303 +++++++++--- 13 files changed, 1172 insertions(+), 310 deletions(-) diff --git a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm index 63c3d7225ab..e27fc65a80d 100644 --- a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm +++ b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm @@ -138,7 +138,7 @@ - (void)failStreamWithError:(NSError *)error { #pragma mark - Helper methods. -- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(const SnapshotVersion &)snap { +- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(SnapshotVersion)snap { if ([change isKindOfClass:[FSTWatchTargetChange class]]) { FSTWatchTargetChange *targetChange = (FSTWatchTargetChange *)change; if (targetChange.cause) { @@ -152,6 +152,11 @@ - (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(const Snapsho [self.activeTargets removeObjectForKey:targetID]; } } + if ([targetChange.targetIDs count] != 0) { + // If the list of target IDs is not empty, we reset the snapshot version to NONE as + // done in `FSTSerializerBeta.versionFromListenResponse:`. + snap = SnapshotVersion::None(); + } } [self.delegate watchStreamDidChange:change snapshotVersion:snap]; } diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm index c131f7e4152..7fe6434145e 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm @@ -194,25 +194,25 @@ - (void)doDelete:(NSString *)key { [self.driver writeUserMutation:FSTTestDeleteMutation(key)]; } -- (void)doWatchAck:(NSArray *)ackedTargets snapshot:(NSNumber *)watchSnapshot { +- (void)doWatchAck:(NSArray *)ackedTargets { FSTWatchTargetChange *change = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateAdded targetIDs:ackedTargets cause:nil]; - [self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]]; + [self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()]; } -- (void)doWatchCurrent:(NSArray *)currentSpec snapshot:(NSNumber *)watchSnapshot { +- (void)doWatchCurrent:(NSArray *)currentSpec { NSArray *currentTargets = currentSpec[0]; NSData *resumeToken = [currentSpec[1] dataUsingEncoding:NSUTF8StringEncoding]; FSTWatchTargetChange *change = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:currentTargets resumeToken:resumeToken]; - [self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]]; + [self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()]; } -- (void)doWatchRemove:(NSDictionary *)watchRemoveSpec snapshot:(NSNumber *)watchSnapshot { +- (void)doWatchRemove:(NSDictionary *)watchRemoveSpec { NSError *error = nil; NSDictionary *cause = watchRemoveSpec[@"cause"]; if (cause) { @@ -226,19 +226,16 @@ - (void)doWatchRemove:(NSDictionary *)watchRemoveSpec snapshot:(NSNumber *)watch [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateRemoved targetIDs:watchRemoveSpec[@"targetIds"] cause:error]; - [self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]]; + [self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()]; // Unlike web, the FSTMockDatastore detects a watch removal with cause and will remove active // targets } -- (void)doWatchEntity:(NSDictionary *)watchEntity snapshot:(NSNumber *_Nullable)watchSnapshot { +- (void)doWatchEntity:(NSDictionary *)watchEntity { if (watchEntity[@"docs"]) { HARD_ASSERT(!watchEntity[@"doc"], "Exactly one of |doc| or |docs| needs to be set."); - int count = 0; NSArray *docs = watchEntity[@"docs"]; for (NSDictionary *doc in docs) { - count++; - bool isLast = (count == docs.count); NSMutableDictionary *watchSpec = [NSMutableDictionary dictionary]; watchSpec[@"doc"] = doc; if (watchEntity[@"targets"]) { @@ -247,11 +244,7 @@ - (void)doWatchEntity:(NSDictionary *)watchEntity snapshot:(NSNumber *_Nullable) if (watchEntity[@"removedTargets"]) { watchSpec[@"removedTargets"] = watchEntity[@"removedTargets"]; } - NSNumber *_Nullable version = nil; - if (isLast) { - version = watchSnapshot; - } - [self doWatchEntity:watchSpec snapshot:version]; + [self doWatchEntity:watchSpec]; } } else if (watchEntity[@"doc"]) { NSArray *docSpec = watchEntity[@"doc"]; @@ -270,7 +263,7 @@ - (void)doWatchEntity:(NSDictionary *)watchEntity snapshot:(NSNumber *_Nullable) removedTargetIDs:watchEntity[@"removedTargets"] documentKey:doc.key document:doc]; - [self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]]; + [self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()]; } else if (watchEntity[@"key"]) { FSTDocumentKey *docKey = FSTTestDocKey(watchEntity[@"key"]); FSTWatchChange *change = @@ -278,13 +271,13 @@ - (void)doWatchEntity:(NSDictionary *)watchEntity snapshot:(NSNumber *_Nullable) removedTargetIDs:watchEntity[@"removedTargets"] documentKey:docKey document:nil]; - [self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]]; + [self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()]; } else { HARD_FAIL("Either key, doc or docs must be set."); } } -- (void)doWatchFilter:(NSArray *)watchFilter snapshot:(NSNumber *_Nullable)watchSnapshot { +- (void)doWatchFilter:(NSArray *)watchFilter { NSArray *targets = watchFilter[0]; HARD_ASSERT(targets.count == 1, "ExistenceFilters currently support exactly one target only."); @@ -294,15 +287,29 @@ - (void)doWatchFilter:(NSArray *)watchFilter snapshot:(NSNumber *_Nullable)watch FSTExistenceFilter *filter = [FSTExistenceFilter filterWithCount:keyCount]; FSTExistenceFilterWatchChange *change = [FSTExistenceFilterWatchChange changeWithFilter:filter targetID:targets[0].intValue]; - [self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]]; + [self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()]; } -- (void)doWatchReset:(NSArray *)watchReset snapshot:(NSNumber *_Nullable)watchSnapshot { +- (void)doWatchReset:(NSArray *)watchReset { FSTWatchTargetChange *change = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset targetIDs:watchReset cause:nil]; - [self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]]; + [self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()]; +} + +- (void)doWatchSnapshot:(NSDictionary *)watchSnapshot { + // The client will only respond to watchSnapshots if they are on a target change with an empty + // set of target IDs. + NSArray *targetIDs = + watchSnapshot[@"targetIds"] ? watchSnapshot[@"targetIds"] : [NSArray array]; + NSData *resumeToken = [watchSnapshot[@"resumeToken"] dataUsingEncoding:NSUTF8StringEncoding]; + FSTWatchTargetChange *change = + [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange + targetIDs:targetIDs + resumeToken:resumeToken]; + [self.driver receiveWatchChange:change + snapshotVersion:[self parseVersion:watchSnapshot[@"version"]]]; } - (void)doWatchStreamClose:(NSDictionary *)closeSpec { @@ -415,17 +422,19 @@ - (void)doStep:(NSDictionary *)step { } else if (step[@"userDelete"]) { [self doDelete:step[@"userDelete"]]; } else if (step[@"watchAck"]) { - [self doWatchAck:step[@"watchAck"] snapshot:step[@"watchSnapshot"]]; + [self doWatchAck:step[@"watchAck"]]; } else if (step[@"watchCurrent"]) { - [self doWatchCurrent:step[@"watchCurrent"] snapshot:step[@"watchSnapshot"]]; + [self doWatchCurrent:step[@"watchCurrent"]]; } else if (step[@"watchRemove"]) { - [self doWatchRemove:step[@"watchRemove"] snapshot:step[@"watchSnapshot"]]; + [self doWatchRemove:step[@"watchRemove"]]; } else if (step[@"watchEntity"]) { - [self doWatchEntity:step[@"watchEntity"] snapshot:step[@"watchSnapshot"]]; + [self doWatchEntity:step[@"watchEntity"]]; } else if (step[@"watchFilter"]) { - [self doWatchFilter:step[@"watchFilter"] snapshot:step[@"watchSnapshot"]]; + [self doWatchFilter:step[@"watchFilter"]]; } else if (step[@"watchReset"]) { - [self doWatchReset:step[@"watchReset"] snapshot:step[@"watchSnapshot"]]; + [self doWatchReset:step[@"watchReset"]]; + } else if (step[@"watchSnapshot"]) { + [self doWatchSnapshot:step[@"watchSnapshot"]]; } else if (step[@"watchStreamClose"]) { [self doWatchStreamClose:step[@"watchStreamClose"]]; } else if (step[@"watchProto"]) { diff --git a/Firestore/Example/Tests/SpecTests/json/collection_spec_test.json b/Firestore/Example/Tests/SpecTests/json/collection_spec_test.json index ef41afe0ff4..3b177734e95 100644 --- a/Firestore/Example/Tests/SpecTests/json/collection_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/collection_spec_test.json @@ -56,8 +56,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { 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 3e5d4fb264e..d7a617577b2 100644 --- a/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/existence_filter_spec_test.json @@ -56,8 +56,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -86,8 +90,12 @@ 2 ], "collection/1" - ], - "watchSnapshot": 2000 + ] + }, + { + "watchSnapshot": { + "version": 2000 + } } ] }, @@ -132,8 +140,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -169,8 +181,12 @@ 2 ], "collection/1" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -236,8 +252,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -257,8 +277,12 @@ 2 ], "collection/1" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -331,8 +355,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -436,8 +464,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -517,8 +549,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -554,8 +590,12 @@ 2 ], "collection/1" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -615,8 +655,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [ "collection/2" @@ -652,8 +696,12 @@ 1 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -755,8 +803,12 @@ 2 ], "existence-filter-resume-token" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -818,8 +870,12 @@ 2 ], "collection/1" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -879,8 +935,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [ "collection/2" @@ -916,8 +976,12 @@ 1 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -1012,8 +1076,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -1059,8 +1127,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -1143,8 +1215,12 @@ 2 ], "resume-token-3000" - ], - "watchSnapshot": 3000, + ] + }, + { + "watchSnapshot": { + "version": 3000 + }, "expect": [ { "query": { @@ -1226,8 +1302,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -1255,8 +1335,12 @@ [ 2 ] - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -1345,8 +1429,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -1382,8 +1470,12 @@ 2 ], "collection/1" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -1443,8 +1535,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [ "collection/2" @@ -1491,7 +1587,6 @@ }, "limboDocs": [] }, - "watchSnapshot": 3000, "expect": [ { "query": { diff --git a/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json b/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json index fe307b9b191..29b2b981cc3 100644 --- a/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/limbo_spec_test.json @@ -56,8 +56,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -91,8 +95,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "stateExpect": { "limboDocs": [ "collection/a" @@ -140,8 +148,12 @@ 1 ], "resume-token-2" - ], - "watchSnapshot": 1002, + ] + }, + { + "watchSnapshot": { + "version": 1002 + }, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -236,8 +248,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -271,8 +287,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "stateExpect": { "limboDocs": [ "collection/a" @@ -327,8 +347,12 @@ 1 ], "resume-token-1002" - ], - "watchSnapshot": 1002, + ] + }, + { + "watchSnapshot": { + "version": 1002 + }, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -435,8 +459,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -476,8 +504,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "stateExpect": { "limboDocs": [ "collection/a" @@ -553,8 +585,12 @@ 1 ], "resume-token-1002" - ], - "watchSnapshot": 1002, + ] + }, + { + "watchSnapshot": { + "version": 1002 + }, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -673,8 +709,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -762,8 +802,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "stateExpect": { "limboDocs": [ "collection/a" @@ -859,8 +903,12 @@ 4 ], "resume-token-1002" - ], - "watchSnapshot": 1002, + ] + }, + { + "watchSnapshot": { + "version": 1002 + }, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -953,8 +1001,12 @@ 1 ], "resume-token-1003" - ], - "watchSnapshot": 1003 + ] + }, + { + "watchSnapshot": { + "version": 1003 + } } ] }, @@ -1022,8 +1074,12 @@ 2 ], "resume-token-1002" - ], - "watchSnapshot": 1002, + ] + }, + { + "watchSnapshot": { + "version": 1002 + }, "expect": [ { "query": { @@ -1059,8 +1115,12 @@ "removedTargets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 1003 }, - "watchSnapshot": 1003, "stateExpect": { "limboDocs": [ "collection/b" @@ -1108,8 +1168,12 @@ 1 ], "resume-token-1004" - ], - "watchSnapshot": 1004, + ] + }, + { + "watchSnapshot": { + "version": 1004 + }, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -1213,8 +1277,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -1352,8 +1420,12 @@ 4 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -1484,8 +1556,12 @@ 1 ], "resume-token-3000" - ], - "watchSnapshot": 3000 + ] + }, + { + "watchSnapshot": { + "version": 3000 + } }, { "watchEntity": { @@ -1519,8 +1595,12 @@ "targets": [ 4 ] + } + }, + { + "watchSnapshot": { + "version": 4000 }, - "watchSnapshot": 4000, "expect": [ { "query": { diff --git a/Firestore/Example/Tests/SpecTests/json/limit_spec_test.json b/Firestore/Example/Tests/SpecTests/json/limit_spec_test.json index 6aa1daa0029..890744ebac6 100644 --- a/Firestore/Example/Tests/SpecTests/json/limit_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/limit_spec_test.json @@ -65,8 +65,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { @@ -127,8 +131,12 @@ "removedTargets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 1002 }, - "watchSnapshot": 1002, "expect": [ { "query": { @@ -229,8 +237,12 @@ 2 ], "resume-token-1002" - ], - "watchSnapshot": 1002, + ] + }, + { + "watchSnapshot": { + "version": 1002 + }, "expect": [ { "query": { @@ -295,8 +307,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [ "collection/a" @@ -346,8 +362,12 @@ 1 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -462,8 +482,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { @@ -516,8 +540,12 @@ "removedTargets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 1002 }, - "watchSnapshot": 1002, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -701,8 +729,12 @@ 4 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { @@ -783,8 +815,12 @@ "removedTargets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 1002 }, - "watchSnapshot": 1002, "stateExpect": { "limboDocs": [], "activeTargets": { @@ -928,8 +964,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { @@ -1081,8 +1121,12 @@ 4 ], "resume-token-1005" - ], - "watchSnapshot": 1005, + ] + }, + { + "watchSnapshot": { + "version": 1005 + }, "expect": [ { "query": { @@ -1160,8 +1204,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [ "collection/a", @@ -1228,8 +1276,12 @@ 1 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "stateExpect": { "limboDocs": [ "collection/b", @@ -1340,8 +1392,12 @@ 3 ], "resume-token-2001" - ], - "watchSnapshot": 2001, + ] + }, + { + "watchSnapshot": { + "version": 2001 + }, "stateExpect": { "limboDocs": [ "collection/c", @@ -1452,8 +1508,12 @@ 5 ], "resume-token-2002" - ], - "watchSnapshot": 2002, + ] + }, + { + "watchSnapshot": { + "version": 2002 + }, "stateExpect": { "limboDocs": [ "collection/d" @@ -1555,8 +1615,12 @@ 7 ], "resume-token-2003" - ], - "watchSnapshot": 2003, + ] + }, + { + "watchSnapshot": { + "version": 2003 + }, "stateExpect": { "limboDocs": [], "activeTargets": { diff --git a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json index 0448b2f43f2..427cd76b76e 100644 --- a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json @@ -2,7 +2,10 @@ "Contents of query are cleared when listen is removed.": { "describeName": "Listens:", "itName": "Contents of query are cleared when listen is removed.", - "tags": [], + "tags": [ + "no-lru" + ], + "comment": "Explicitly tests eager GC behavior", "config": { "useGarbageCollection": true }, @@ -56,8 +59,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -174,8 +181,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -212,8 +223,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -373,8 +388,12 @@ { "watchAck": [ 6 - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -476,8 +495,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -559,7 +582,7 @@ "Does not raise event for initial document delete": { "describeName": "Listens:", "itName": "Does not raise event for initial document delete", - "tags": [""], + "tags": [], "config": { "useGarbageCollection": true }, @@ -603,8 +626,12 @@ "removedTargets": [ 2 ] - }, - "watchSnapshot": 1000 + } + }, + { + "watchSnapshot": { + "version": 1000 + } }, { "watchCurrent": [ @@ -612,8 +639,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -833,8 +864,12 @@ 4 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -916,8 +951,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -954,8 +993,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -1068,8 +1111,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000 + ] + }, + { + "watchSnapshot": { + "version": 1000 + } }, { "watchEntity": { @@ -1085,8 +1132,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -1159,8 +1210,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -1197,8 +1252,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -1298,8 +1357,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000 + ] + }, + { + "watchSnapshot": { + "version": 1000 + } }, { "watchEntity": { @@ -1315,8 +1378,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -1402,8 +1469,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -1531,8 +1602,12 @@ 4 ], "resume-token-4000" - ], - "watchSnapshot": 4000, + ] + }, + { + "watchSnapshot": { + "version": 4000 + }, "expect": [ { "query": { @@ -1638,8 +1713,12 @@ 2 ], "resume-token-5000" - ], - "watchSnapshot": 5000, + ] + }, + { + "watchSnapshot": { + "version": 5000 + }, "expect": [ { "query": { @@ -1748,8 +1827,12 @@ 4 ], "resume-token-6000" - ], - "watchSnapshot": 6000, + ] + }, + { + "watchSnapshot": { + "version": 6000 + }, "expect": [ { "query": { @@ -2510,8 +2593,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -2596,8 +2683,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -2686,8 +2777,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -2854,8 +2949,12 @@ 4 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -3000,8 +3099,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -3112,8 +3215,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -3195,8 +3302,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -3246,8 +3357,12 @@ "targets": [ 2 ] - }, - "watchSnapshot": 2000 + } + }, + { + "watchSnapshot": { + "version": 2000 + } }, { "watchRemove": { @@ -3350,8 +3465,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 1000 }, - "watchSnapshot": 1000, "expect": [ { "query": { @@ -3380,8 +3499,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -3452,5 +3575,229 @@ ] } ] + }, + "Persists resume token sent with target": { + "describeName": "Listens:", + "itName": "Persists resume token sent with target", + "tags": [ + "exclusive" + ], + "config": { + "useGarbageCollection": false + }, + "steps": [ + { + "userListen": [ + 2, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "watchEntity": { + "docs": [ + [ + "collection/a", + 2000, + { + "key": "a" + } + ] + ], + "targets": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "version": 2000, + "targetIds": [ + 2 + ], + "resumeToken": "resume-token-2000" + } + }, + { + "watchSnapshot": { + "version": 2000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "added": [ + [ + "collection/a", + 2000, + { + "key": "a" + } + ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + }, + { + "userUnlisten": [ + 2, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": {} + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": [ + 2, + { + "path": "collection", + "filters": [], + "orderBys": [] + } + ], + "stateExpect": { + "activeTargets": { + "2": { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "resumeToken": "resume-token-2000" + } + } + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "added": [ + [ + "collection/a", + 2000, + { + "key": "a" + } + ] + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false + } + ] + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-3000" + ] + }, + { + "watchSnapshot": { + "version": 3000 + }, + "expect": [ + { + "query": { + "path": "collection", + "filters": [], + "orderBys": [] + }, + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false + } + ] + } + ] } } diff --git a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json index 1af4c16e2f8..dbc6d10d9d7 100644 --- a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json @@ -176,7 +176,10 @@ "Removing all listeners delays \"Offline\" status on next listen": { "describeName": "Offline:", "itName": "Removing all listeners delays \"Offline\" status on next listen", - "tags": [], + "tags": [ + "no-lru" + ], + "comment": "Marked as no-lru because when a listen is re-added, it gets a new target id rather than reusing one", "config": { "useGarbageCollection": true }, @@ -366,8 +369,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -460,8 +467,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -534,8 +545,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -569,8 +584,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "stateExpect": { "limboDocs": [ "collection/a" @@ -673,8 +692,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001 + ] + }, + { + "watchSnapshot": { + "version": 1001 + } }, { "watchAck": [ @@ -695,8 +718,12 @@ 1 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { @@ -824,8 +851,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { diff --git a/Firestore/Example/Tests/SpecTests/json/orderby_spec_test.json b/Firestore/Example/Tests/SpecTests/json/orderby_spec_test.json index 100920687b9..58b5d16bcb7 100644 --- a/Firestore/Example/Tests/SpecTests/json/orderby_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/orderby_spec_test.json @@ -119,8 +119,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { diff --git a/Firestore/Example/Tests/SpecTests/json/persistence_spec_test.json b/Firestore/Example/Tests/SpecTests/json/persistence_spec_test.json index 158e3370869..7303e36a821 100644 --- a/Firestore/Example/Tests/SpecTests/json/persistence_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/persistence_spec_test.json @@ -196,8 +196,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -331,8 +335,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -697,8 +705,12 @@ 2 ], "resume-token-500" - ], - "watchSnapshot": 500, + ] + }, + { + "watchSnapshot": { + "version": 500 + }, "expect": [ { "query": { diff --git a/Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json b/Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json index 6852c9017b1..876496375fc 100644 --- a/Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/remote_store_spec_test.json @@ -91,8 +91,12 @@ 2 ], "resume-token" - ], - "watchSnapshot": 1000 + ] + }, + { + "watchSnapshot": { + "version": 1000 + } }, { "watchRemove": { @@ -128,8 +132,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { @@ -316,8 +324,12 @@ 2 ], "resume-token" - ], - "watchSnapshot": 1000 + ] + }, + { + "watchSnapshot": { + "version": 1000 + } }, { "watchRemove": { @@ -353,8 +365,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001 + ] + }, + { + "watchSnapshot": { + "version": 1001 + } }, { "watchRemove": { @@ -390,8 +406,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001 + ] + }, + { + "watchSnapshot": { + "version": 1001 + } }, { "watchRemove": { @@ -427,8 +447,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { @@ -519,8 +543,12 @@ 2 ], "resume-token-1001" - ], - "watchSnapshot": 1001, + ] + }, + { + "watchSnapshot": { + "version": 1001 + }, "expect": [ { "query": { diff --git a/Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json b/Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json index f411d983813..cee3c2a44c1 100644 --- a/Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/resume_token_spec_test.json @@ -56,8 +56,12 @@ 2 ], "custom-query-resume-token" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -160,8 +164,12 @@ 2 ], "custom-query-resume-token" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -243,8 +251,12 @@ { "watchAck": [ 2 - ], - "watchSnapshot": 1001 + ] + }, + { + "watchSnapshot": { + "version": 1001 + } } ] } diff --git a/Firestore/Example/Tests/SpecTests/json/write_spec_test.json b/Firestore/Example/Tests/SpecTests/json/write_spec_test.json index d4d1e7c3fe4..5422f21f597 100644 --- a/Firestore/Example/Tests/SpecTests/json/write_spec_test.json +++ b/Firestore/Example/Tests/SpecTests/json/write_spec_test.json @@ -63,8 +63,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -138,8 +142,12 @@ "targets": [ 2 ] - }, - "watchSnapshot": 2000 + } + }, + { + "watchSnapshot": { + "version": 2000 + } }, { "writeAck": { @@ -212,8 +220,12 @@ "targets": [ 2 ] - }, - "watchSnapshot": 3000 + } + }, + { + "watchSnapshot": { + "version": 3000 + } }, { "writeAck": { @@ -301,8 +313,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -369,8 +385,12 @@ "targets": [ 2 ] - }, - "watchSnapshot": 2000 + } + }, + { + "watchSnapshot": { + "version": 2000 + } }, { "writeAck": { @@ -458,8 +478,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -526,8 +550,12 @@ "targets": [ 2 ] - }, - "watchSnapshot": 10000 + } + }, + { + "watchSnapshot": { + "version": 10000 + } }, { "writeAck": { @@ -615,8 +643,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -689,8 +721,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -772,8 +808,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -846,8 +886,12 @@ "targets": [ 2 ] - }, - "watchSnapshot": 2000 + } + }, + { + "watchSnapshot": { + "version": 2000 + } }, { "watchEntity": { @@ -870,8 +914,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 3000 }, - "watchSnapshot": 3000, "expect": [ { "query": { @@ -1421,8 +1469,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 1000 }, - "watchSnapshot": 1000, "expect": [ { "query": { @@ -1465,8 +1517,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -1509,8 +1565,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 3000 }, - "watchSnapshot": 3000, "expect": [ { "query": { @@ -1553,8 +1613,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 4000 }, - "watchSnapshot": 4000, "expect": [ { "query": { @@ -1597,8 +1661,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 5000 }, - "watchSnapshot": 5000, "expect": [ { "query": { @@ -1641,8 +1709,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 6000 }, - "watchSnapshot": 6000, "expect": [ { "query": { @@ -1685,8 +1757,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 7000 }, - "watchSnapshot": 7000, "expect": [ { "query": { @@ -1729,8 +1805,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 8000 }, - "watchSnapshot": 8000, "expect": [ { "query": { @@ -1773,8 +1853,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 9000 }, - "watchSnapshot": 9000, "expect": [ { "query": { @@ -1817,8 +1901,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 10000 }, - "watchSnapshot": 10000, "expect": [ { "query": { @@ -1861,8 +1949,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 11000 }, - "watchSnapshot": 11000, "expect": [ { "query": { @@ -1905,8 +1997,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 12000 }, - "watchSnapshot": 12000, "expect": [ { "query": { @@ -1949,8 +2045,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 13000 }, - "watchSnapshot": 13000, "expect": [ { "query": { @@ -1993,8 +2093,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 14000 }, - "watchSnapshot": 14000, "expect": [ { "query": { @@ -2037,8 +2141,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 15000 }, - "watchSnapshot": 15000, "expect": [ { "query": { @@ -3058,8 +3166,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -3191,8 +3303,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -3266,8 +3382,12 @@ 2 ], "resume-token-500" - ], - "watchSnapshot": 500, + ] + }, + { + "watchSnapshot": { + "version": 500 + }, "expect": [ { "query": { @@ -3374,8 +3494,12 @@ "targets": [ 2 ] + } + }, + { + "watchSnapshot": { + "version": 2000 }, - "watchSnapshot": 2000, "expect": [ { "query": { @@ -3456,8 +3580,12 @@ 2 ], "resume-token-500" - ], - "watchSnapshot": 500, + ] + }, + { + "watchSnapshot": { + "version": 500 + }, "expect": [ { "query": { @@ -3573,8 +3701,12 @@ 2 ], "resume-token-2000" - ], - "watchSnapshot": 2000, + ] + }, + { + "watchSnapshot": { + "version": 2000 + }, "expect": [ { "query": { @@ -3602,7 +3734,10 @@ "Held writes are released when there are no queries left.": { "describeName": "Writes:", "itName": "Held writes are released when there are no queries left.", - "tags": [], + "tags": [ + "no-lru" + ], + "comment": "This test expects a new target id for a new listen, but without eager gc, the same target id is reused", "config": { "useGarbageCollection": true }, @@ -3648,8 +3783,12 @@ 2 ], "resume-token-500" - ], - "watchSnapshot": 500, + ] + }, + { + "watchSnapshot": { + "version": 500 + }, "expect": [ { "query": { @@ -4735,8 +4874,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -4862,8 +5005,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -4989,8 +5136,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -5116,8 +5267,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -5243,8 +5398,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -5370,8 +5529,12 @@ 2 ], "resume-token-1000" - ], - "watchSnapshot": 1000, + ] + }, + { + "watchSnapshot": { + "version": 1000 + }, "expect": [ { "query": { @@ -5456,8 +5619,12 @@ 2 ], "resume-token-500" - ], - "watchSnapshot": 500, + ] + }, + { + "watchSnapshot": { + "version": 500 + }, "expect": [ { "query": { @@ -5534,8 +5701,12 @@ "targets": [ 2 ] - }, - "watchSnapshot": 2000 + } + }, + { + "watchSnapshot": { + "version": 2000 + } }, { "writeAck": { From 9cf8259ed1903bb465d46d0c8260231e4fa58a05 Mon Sep 17 00:00:00 2001 From: Marek Gilbert Date: Thu, 19 Jul 2018 12:25:33 -0700 Subject: [PATCH 7/7] Clarify extra bandwidth usage --- Firestore/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index dbdfdc033ef..535598246d8 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -3,7 +3,8 @@ # v0.12.6 - [fixed] Fixed an issue where queries returned fewer results than they should, caused by documents that were cached as deleted when they should not have - been (#1548). + been (#1548). Some cache data is cleared and so clients may use extra + bandwidth the first time they launch with this version of the SDK. # v0.12.5 - [changed] Internal improvements.