aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm59
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.mm6
-rw-r--r--Firestore/Source/Remote/FSTRemoteEvent.h5
-rw-r--r--Firestore/Source/Remote/FSTRemoteEvent.mm85
4 files changed, 136 insertions, 19 deletions
diff --git a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
index 6ac2a6b..35903e7 100644
--- a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
+++ b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
@@ -18,6 +18,7 @@
#import <XCTest/XCTest.h>
+#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
@@ -581,14 +582,17 @@ NS_ASSUME_NONNULL_BEGIN
FSTDeletedDocument *expected =
[FSTDeletedDocument documentWithKey:synthesized version:event.snapshotVersion];
XCTAssertEqualObjects(expected, event.documentUpdates.at(synthesized));
+ XCTAssertTrue([event.limboDocumentChanges containsObject:synthesized]);
- DocumentKey notSynthesized1 = DocumentKey::FromPathString("docs/no1");
- [event synthesizeDeleteForLimboTargetChange:event.targetChanges[@2] key:notSynthesized1];
- XCTAssertEqual(event.documentUpdates.find(notSynthesized1), event.documentUpdates.end());
+ DocumentKey notSynthesized = DocumentKey::FromPathString("docs/no1");
+ [event synthesizeDeleteForLimboTargetChange:event.targetChanges[@2] key:notSynthesized];
+ XCTAssertEqual(event.documentUpdates.find(notSynthesized), event.documentUpdates.end());
+ XCTAssertFalse([event.limboDocumentChanges containsObject:notSynthesized]);
[event synthesizeDeleteForLimboTargetChange:event.targetChanges[@3] key:doc.key];
FSTMaybeDocument *docData = event.documentUpdates.at(doc.key);
XCTAssertFalse([docData isKindOfClass:[FSTDeletedDocument class]]);
+ XCTAssertFalse([event.limboDocumentChanges containsObject:doc.key]);
}
- (void)testFilterUpdates {
@@ -639,6 +643,55 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertTrue([resetMapping.documents containsObject:existingDocKey]);
}
+- (void)testTracksLimboDocuments {
+ // Add 3 docs: 1 is limbo and non-limbo, 2 is limbo-only, 3 is non-limbo
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"key" : @"value"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 1, @{@"key" : @"value"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("docs/3", 1, @{@"key" : @"value"}, NO);
+
+ // Target 2 is a limbo target
+
+ FSTWatchChange *docChange1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2 ]
+ removedTargetIDs:@[]
+ documentKey:doc1.key
+ document:doc1];
+
+ FSTWatchChange *docChange2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @2 ]
+ removedTargetIDs:@[]
+ documentKey:doc2.key
+ document:doc2];
+
+ FSTWatchChange *docChange3 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
+ removedTargetIDs:@[]
+ documentKey:doc3.key
+ document:doc3];
+
+ FSTWatchChange *targetsChange =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @1, @2 ]];
+
+ NSMutableDictionary<NSNumber *, FSTQueryData *> *listens = [NSMutableDictionary dictionary];
+ listens[@1] = [FSTQueryData alloc];
+ listens[@2] = [[FSTQueryData alloc] initWithQuery:[FSTQuery alloc]
+ targetID:2
+ listenSequenceNumber:1000
+ purpose:FSTQueryPurposeLimboResolution];
+ FSTWatchChangeAggregator *aggregator =
+ [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:FSTTestVersion(3)
+ listenTargets:listens
+ pendingTargetResponses:@{}];
+
+ [aggregator addWatchChanges:@[ docChange1, docChange2, docChange3, targetsChange ]];
+
+ FSTRemoteEvent *event = [aggregator remoteEvent];
+ FSTDocumentKeySet *limboDocChanges = event.limboDocumentChanges;
+ // Doc1 is in both limbo and non-limbo targets, therefore not tracked as limbo
+ XCTAssertFalse([limboDocChanges containsObject:doc1.key]);
+ // Doc2 is only in the limbo target, so is tracked as a limbo document
+ XCTAssertTrue([limboDocChanges containsObject:doc2.key]);
+ // Doc3 is only in the non-limbo target, therefore not tracked as limbo
+ XCTAssertFalse([limboDocChanges containsObject:doc3.key]);
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTSyncEngine.mm b/Firestore/Source/Core/FSTSyncEngine.mm
index 8c2aeb2..ca15e64 100644
--- a/Firestore/Source/Core/FSTSyncEngine.mm
+++ b/Firestore/Source/Core/FSTSyncEngine.mm
@@ -349,9 +349,13 @@ static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1;
[NSMutableDictionary dictionary];
FSTDeletedDocument *doc =
[FSTDeletedDocument documentWithKey:limboKey version:[FSTSnapshotVersion noVersion]];
+ FSTDocumentKeySet *limboDocuments = [[FSTDocumentKeySet keySet] setByAddingObject:doc.key];
FSTRemoteEvent *event = [[FSTRemoteEvent alloc] initWithSnapshotVersion:SnapshotVersion::None()
targetChanges:targetChanges
- documentUpdates:{{limboKey, doc}}];
+ documentUpdates:{
+ { limboKey, doc }
+ }
+ limboDocuments:limboDocuments];
[self applyRemoteEvent:event];
} else {
FSTQueryView *queryView = self.queryViewsByTarget[@(targetID)];
diff --git a/Firestore/Source/Remote/FSTRemoteEvent.h b/Firestore/Source/Remote/FSTRemoteEvent.h
index 0e25a2f..322cb63 100644
--- a/Firestore/Source/Remote/FSTRemoteEvent.h
+++ b/Firestore/Source/Remote/FSTRemoteEvent.h
@@ -157,7 +157,8 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) {
initWithSnapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion
targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
documentUpdates:
- (std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *>)documentUpdates;
+ (std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *>)documentUpdates
+ limboDocuments:(FSTDocumentKeySet *)limboDocuments;
/** The snapshot version this event brings us up to. */
- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion;
@@ -166,6 +167,8 @@ initWithSnapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVer
@property(nonatomic, strong, readonly)
NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *targetChanges;
+@property(nonatomic, strong, readonly) FSTDocumentKeySet *limboDocumentChanges;
+
/**
* A set of which documents have changed or been deleted, along with the doc's new values
* (if not deleted).
diff --git a/Firestore/Source/Remote/FSTRemoteEvent.mm b/Firestore/Source/Remote/FSTRemoteEvent.mm
index 99cb018..26c32a2 100644
--- a/Firestore/Source/Remote/FSTRemoteEvent.mm
+++ b/Firestore/Source/Remote/FSTRemoteEvent.mm
@@ -19,12 +19,12 @@
#include <map>
#include <utility>
+#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTClasses.h"
#import "Firestore/Source/Util/FSTLogger.h"
-
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
using firebase::firestore::model::DocumentKey;
@@ -262,7 +262,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)
initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
targetChanges:(NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges
- documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates;
+ documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates
+ limboDocuments:(FSTDocumentKeySet *)limboDocuments;
@end
@@ -274,12 +275,14 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
- (instancetype)initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
targetChanges:
(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
- documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates {
+ documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates
+ limboDocuments:(FSTDocumentKeySet *)limboDocuments {
self = [super init];
if (self) {
_snapshotVersion = std::move(snapshotVersion);
_targetChanges = targetChanges;
_documentUpdates = std::move(documentUpdates);
+ _limboDocumentChanges = limboDocuments;
}
return self;
}
@@ -322,11 +325,12 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
// TODO(dimond): Ideally we would have an explicit lookup query instead resulting in an
// explicit delete message and we could remove this special logic.
_documentUpdates[key] = [FSTDeletedDocument documentWithKey:key version:_snapshotVersion];
+ _limboDocumentChanges = [_limboDocumentChanges setByAddingObject:key];
}
}
- (NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges {
- return static_cast<NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *>(_targetChanges);
+ return _targetChanges;
}
- (const std::map<DocumentKey, FSTMaybeDocument *> &)documentUpdates {
@@ -385,6 +389,8 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
NSMutableDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *_existenceFilters;
/** Keeps track of document to update */
std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
+
+ FSTDocumentKeySet *_limboDocuments;
/** The snapshot version for every target change this creates. */
SnapshotVersion _snapshotVersion;
}
@@ -401,14 +407,14 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
_targetChanges = [NSMutableDictionary dictionary];
_listenTargets = listenTargets;
_pendingTargetResponses = [NSMutableDictionary dictionaryWithDictionary:pendingTargetResponses];
-
+ _limboDocuments = [FSTDocumentKeySet keySet];
_existenceFilters = [NSMutableDictionary dictionary];
}
return self;
}
- (NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *)existenceFilters {
- return static_cast<NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *>(_existenceFilters);
+ return _existenceFilters;
}
- (FSTTargetChange *)targetChangeForTargetID:(FSTBoxedTargetID *)targetID {
@@ -440,20 +446,66 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
}
}
+/**
+ * Updates limbo document tracking for a given target-document mapping change. If the target is a
+ * limbo target, and the change for the document has only seen limbo targets so far, and we are not
+ * already tracking a change for this document, then consider this document a limbo document update.
+ * Otherwise, ensure that we don't consider this document a limbo document. Returns true if the
+ * change still has only seen limbo resolution changes.
+ */
+- (BOOL)updateLimboDocumentsForKey:(const DocumentKey &)documentKey
+ queryData:(FSTQueryData *)queryData
+ isOnlyLimbo:(BOOL)isOnlyLimbo {
+ if (!isOnlyLimbo) {
+ // It wasn't a limbo doc before, so it definitely isn't now.
+ return NO;
+ }
+ if (_documentUpdates.find(documentKey) == _documentUpdates.end()) {
+ // We haven't seen a document update for this key yet.
+ if (queryData.purpose == FSTQueryPurposeLimboResolution) {
+ // We haven't seen this document before, and this target is a limbo target.
+ _limboDocuments = [_limboDocuments setByAddingObject:documentKey];
+ return YES;
+ } else {
+ // We haven't seen the document before, but this is a non-limbo target.
+ // Since we haven't seen it, we know it's not in our set of limbo docs. Return NO to ensure
+ // that this key is marked as non-limbo.
+ return NO;
+ }
+ } else if (queryData.purpose == FSTQueryPurposeLimboResolution) {
+ // We have only seen limbo targets so far for this document, and this is another limbo target.
+ return YES;
+ } else {
+ // We haven't marked this as non-limbo yet, but this target is not a limbo target.
+ // Mark the key as non-limbo and make sure it isn't in our set.
+ _limboDocuments = [_limboDocuments setByRemovingObject:documentKey];
+ return NO;
+ }
+}
+
- (void)addDocumentChange:(FSTDocumentWatchChange *)docChange {
BOOL relevant = NO;
+ BOOL isOnlyLimbo = YES;
for (FSTBoxedTargetID *targetID in docChange.updatedTargetIDs) {
- if ([self isActiveTarget:targetID]) {
+ FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
+ if (queryData) {
FSTTargetChange *change = [self targetChangeForTargetID:targetID];
+ isOnlyLimbo = [self updateLimboDocumentsForKey:docChange.documentKey
+ queryData:queryData
+ isOnlyLimbo:isOnlyLimbo];
[change.mapping addDocumentKey:docChange.documentKey];
relevant = YES;
}
}
for (FSTBoxedTargetID *targetID in docChange.removedTargetIDs) {
- if ([self isActiveTarget:targetID]) {
+ FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
+ if (queryData) {
FSTTargetChange *change = [self targetChangeForTargetID:targetID];
+ isOnlyLimbo = [self updateLimboDocumentsForKey:docChange.documentKey
+ queryData:queryData
+ isOnlyLimbo:isOnlyLimbo];
[change.mapping removeDocumentKey:docChange.documentKey];
relevant = YES;
}
@@ -478,7 +530,7 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
break;
case FSTWatchTargetChangeStateAdded:
[self recordResponseForTargetID:targetID];
- if (![self.pendingTargetResponses objectForKey:targetID]) {
+ if (!self.pendingTargetResponses[targetID]) {
// We have a freshly added target, so we need to reset any state that we had previously
// This can happen e.g. when remove and add back a target for existence filter
// mismatches.
@@ -519,12 +571,12 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
* responses that we have.
*/
- (void)recordResponseForTargetID:(FSTBoxedTargetID *)targetID {
- NSNumber *count = [self.pendingTargetResponses objectForKey:targetID];
+ NSNumber *count = self.pendingTargetResponses[targetID];
int newCount = count ? [count intValue] - 1 : -1;
if (newCount == 0) {
[self.pendingTargetResponses removeObjectForKey:targetID];
} else {
- [self.pendingTargetResponses setObject:[NSNumber numberWithInt:newCount] forKey:targetID];
+ self.pendingTargetResponses[targetID] = @(newCount);
}
}
@@ -537,8 +589,12 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
* yet acknowledged the intended change in state.
*/
- (BOOL)isActiveTarget:(FSTBoxedTargetID *)targetID {
- return [self.listenTargets objectForKey:targetID] &&
- ![self.pendingTargetResponses objectForKey:targetID];
+ return [self queryDataForActiveTarget:targetID] != nil;
+}
+
+- (FSTQueryData *_Nullable)queryDataForActiveTarget:(FSTBoxedTargetID *)targetID {
+ FSTQueryData *queryData = self.listenTargets[targetID];
+ return (queryData && !self.pendingTargetResponses[targetID]) ? queryData : nil;
}
- (void)addExistenceFilterChange:(FSTExistenceFilterWatchChange *)existenceFilterChange {
@@ -566,7 +622,8 @@ initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
self.frozen = YES;
return [[FSTRemoteEvent alloc] initWithSnapshotVersion:_snapshotVersion
targetChanges:targetChanges
- documentUpdates:_documentUpdates];
+ documentUpdates:_documentUpdates
+ limboDocuments:_limboDocuments];
}
@end