aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalSerializerTests.m2
-rw-r--r--Firestore/Example/Tests/Local/FSTQueryCacheTests.m80
-rw-r--r--Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m15
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSpecTests.m1
-rw-r--r--Firestore/Source/Core/FSTListenSequence.h37
-rw-r--r--Firestore/Source/Core/FSTListenSequence.m34
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.m5
-rw-r--r--Firestore/Source/Core/FSTTypes.h2
-rw-r--r--Firestore/Source/Local/FSTLevelDBQueryCache.mm15
-rw-r--r--Firestore/Source/Local/FSTLocalSerializer.m3
-rw-r--r--Firestore/Source/Local/FSTLocalStore.m12
-rw-r--r--Firestore/Source/Local/FSTMemoryQueryCache.m9
-rw-r--r--Firestore/Source/Local/FSTQueryCache.h5
-rw-r--r--Firestore/Source/Local/FSTQueryData.h4
-rw-r--r--Firestore/Source/Local/FSTQueryData.m5
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.m7
16 files changed, 217 insertions, 19 deletions
diff --git a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m
index 27c3dc3..95b9b11 100644
--- a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m
+++ b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m
@@ -157,6 +157,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:10
purpose:FSTQueryPurposeListen
snapshotVersion:version
resumeToken:resumeToken];
@@ -166,6 +167,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTPBTarget *expected = [FSTPBTarget message];
expected.targetId = targetID;
+ expected.lastListenSequenceNumber = 10;
expected.snapshotVersion.nanos = 1039000;
expected.resumeToken = [resumeToken copy];
expected.query.parent = queryTarget.parent;
diff --git a/Firestore/Example/Tests/Local/FSTQueryCacheTests.m b/Firestore/Example/Tests/Local/FSTQueryCacheTests.m
index 0b80bd9..0c6a2a4 100644
--- a/Firestore/Example/Tests/Local/FSTQueryCacheTests.m
+++ b/Firestore/Example/Tests/Local/FSTQueryCacheTests.m
@@ -31,12 +31,18 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTQueryCacheTests {
FSTQuery *_queryRooms;
+ FSTListenSequenceNumber _previousSequenceNumber;
+ FSTTargetID _previousTargetID;
+ FSTTestSnapshotVersion _previousSnapshotVersion;
}
- (void)setUp {
[super setUp];
_queryRooms = FSTTestQuery(@"rooms");
+ _previousSequenceNumber = 1000;
+ _previousTargetID = 500;
+ _previousSnapshotVersion = 100;
}
/**
@@ -56,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testSetAndReadAQuery {
if ([self isTestBaseClass]) return;
- FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
+ FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms];
[self addQueryData:queryData];
FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
@@ -74,14 +80,14 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *q2 = [FSTTestQuery(@"a") queryByAddingFilter:FSTTestFilter(@"foo", @"==", @"1")];
XCTAssertEqualObjects(q1.canonicalID, q2.canonicalID);
- FSTQueryData *data1 = [self queryDataWithQuery:q1 targetID:1 version:1];
+ FSTQueryData *data1 = [self queryDataWithQuery:q1];
[self addQueryData:data1];
// Using the other query should not return the query cache entry despite equal canonicalIDs.
XCTAssertNil([self.queryCache queryDataForQuery:q2]);
XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1);
- FSTQueryData *data2 = [self queryDataWithQuery:q2 targetID:2 version:1];
+ FSTQueryData *data2 = [self queryDataWithQuery:q2];
[self addQueryData:data2];
XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1);
@@ -99,10 +105,12 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testSetQueryToNewValue {
if ([self isTestBaseClass]) return;
- FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
+ FSTQueryData *queryData1 =
+ [self queryDataWithQuery:_queryRooms targetID:1 listenSequenceNumber:10 version:1];
[self addQueryData:queryData1];
- FSTQueryData *queryData2 = [self queryDataWithQuery:_queryRooms targetID:1 version:2];
+ FSTQueryData *queryData2 =
+ [self queryDataWithQuery:_queryRooms targetID:1 listenSequenceNumber:10 version:2];
[self addQueryData:queryData2];
FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
@@ -115,7 +123,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRemoveQuery {
if ([self isTestBaseClass]) return;
- FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
+ FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms];
[self addQueryData:queryData1];
[self removeQueryData:queryData1];
@@ -127,7 +135,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRemoveNonExistentQuery {
if ([self isTestBaseClass]) return;
- FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
+ FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms];
// no-op, but make sure it doesn't throw.
XCTAssertNoThrow([self removeQueryData:queryData]);
@@ -136,7 +144,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRemoveQueryRemovesMatchingKeysToo {
if ([self isTestBaseClass]) return;
- FSTQueryData *rooms = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
+ FSTQueryData *rooms = [self queryDataWithQuery:_queryRooms];
[self addQueryData:rooms];
FSTDocumentKey *key1 = FSTTestDocKey(@"rooms/foo");
@@ -204,14 +212,14 @@ NS_ASSUME_NONNULL_BEGIN
[garbageCollector addGarbageSource:self.queryCache];
FSTAssertEqualSets([garbageCollector collectGarbage], @[]);
- FSTQueryData *rooms = [self queryDataWithQuery:FSTTestQuery(@"rooms") targetID:1 version:1];
+ FSTQueryData *rooms = [self queryDataWithQuery:FSTTestQuery(@"rooms")];
FSTDocumentKey *room1 = FSTTestDocKey(@"rooms/bar");
FSTDocumentKey *room2 = FSTTestDocKey(@"rooms/foo");
[self addQueryData:rooms];
[self addMatchingKey:room1 forTargetID:rooms.targetID];
[self addMatchingKey:room2 forTargetID:rooms.targetID];
- FSTQueryData *halls = [self queryDataWithQuery:FSTTestQuery(@"halls") targetID:2 version:1];
+ FSTQueryData *halls = [self queryDataWithQuery:FSTTestQuery(@"halls")];
FSTDocumentKey *hall1 = FSTTestDocKey(@"halls/bar");
FSTDocumentKey *hall2 = FSTTestDocKey(@"halls/foo");
[self addQueryData:halls];
@@ -249,6 +257,46 @@ NS_ASSUME_NONNULL_BEGIN
FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:2], (@[ key1, key3 ]));
}
+- (void)testHighestListenSequenceNumber {
+ if ([self isTestBaseClass]) return;
+
+ FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"rooms")
+ targetID:1
+ listenSequenceNumber:10
+ purpose:FSTQueryPurposeListen];
+ [self addQueryData:query1];
+ FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"halls")
+ targetID:2
+ listenSequenceNumber:20
+ purpose:FSTQueryPurposeListen];
+ [self addQueryData:query2];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 20);
+
+ // TargetIDs never come down.
+ [self removeQueryData:query2];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 20);
+
+ // A query with an empty result set still counts.
+ FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"garages")
+ targetID:42
+ listenSequenceNumber:100
+ purpose:FSTQueryPurposeListen];
+ [self addQueryData:query3];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
+
+ [self removeQueryData:query1];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
+
+ [self removeQueryData:query3];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
+
+ // Verify that the highestTargetID even survives restarts.
+ [self.queryCache shutdown];
+ self.queryCache = [self.persistence queryCache];
+ [self.queryCache start];
+ XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100);
+}
+
- (void)testHighestTargetID {
if ([self isTestBaseClass]) return;
@@ -256,6 +304,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"rooms")
targetID:1
+ listenSequenceNumber:10
purpose:FSTQueryPurposeListen];
FSTDocumentKey *key1 = FSTTestDocKey(@"rooms/bar");
FSTDocumentKey *key2 = FSTTestDocKey(@"rooms/foo");
@@ -265,6 +314,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"halls")
targetID:2
+ listenSequenceNumber:20
purpose:FSTQueryPurposeListen];
FSTDocumentKey *key3 = FSTTestDocKey(@"halls/foo");
[self addQueryData:query2];
@@ -278,6 +328,7 @@ NS_ASSUME_NONNULL_BEGIN
// A query with an empty result set still counts.
FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"garages")
targetID:42
+ listenSequenceNumber:100
purpose:FSTQueryPurposeListen];
[self addQueryData:query3];
XCTAssertEqual([self.queryCache highestTargetID], 42);
@@ -319,12 +370,21 @@ NS_ASSUME_NONNULL_BEGIN
* Creates a new FSTQueryData object from the given parameters, synthesizing a resume token from
* the snapshot version.
*/
+- (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query {
+ return [self queryDataWithQuery:query
+ targetID:++_previousTargetID
+ listenSequenceNumber:++_previousSequenceNumber
+ version:++_previousSnapshotVersion];
+}
+
- (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
version:(FSTTestSnapshotVersion)version {
NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(version);
return [[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:sequenceNumber
purpose:FSTQueryPurposeListen
snapshotVersion:FSTTestVersion(version)
resumeToken:resumeToken];
diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m
index 61847b0..3357078 100644
--- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m
+++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m
@@ -396,20 +396,25 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testEncodesListenRequestLabels {
FSTQuery *query = FSTTestQuery(@"collection/key");
- FSTQueryData *queryData =
- [[FSTQueryData alloc] initWithQuery:query targetID:2 purpose:FSTQueryPurposeListen];
+ FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
+ targetID:2
+ listenSequenceNumber:3
+ purpose:FSTQueryPurposeListen];
NSDictionary<NSString *, NSString *> *result =
[self.serializer encodedListenRequestLabelsForQueryData:queryData];
XCTAssertNil(result);
- queryData =
- [[FSTQueryData alloc] initWithQuery:query targetID:2 purpose:FSTQueryPurposeLimboResolution];
+ queryData = [[FSTQueryData alloc] initWithQuery:query
+ targetID:2
+ listenSequenceNumber:3
+ purpose:FSTQueryPurposeLimboResolution];
result = [self.serializer encodedListenRequestLabelsForQueryData:queryData];
XCTAssertEqualObjects(result, @{@"goog-listen-tags" : @"limbo-document"});
queryData = [[FSTQueryData alloc] initWithQuery:query
targetID:2
+ listenSequenceNumber:3
purpose:FSTQueryPurposeExistenceFilterMismatch];
result = [self.serializer encodedListenRequestLabelsForQueryData:queryData];
XCTAssertEqualObjects(result, @{@"goog-listen-tags" : @"existence-filter-mismatch"});
@@ -627,6 +632,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *q = FSTTestQuery(@"docs");
FSTQueryData *model = [[FSTQueryData alloc] initWithQuery:q
targetID:1
+ listenSequenceNumber:0
purpose:FSTQueryPurposeListen
snapshotVersion:[FSTSnapshotVersion noVersion]
resumeToken:FSTTestData(1, 2, 3, -1)];
@@ -647,6 +653,7 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTQueryData *)queryDataForQuery:(FSTQuery *)query {
return [[FSTQueryData alloc] initWithQuery:query
targetID:1
+ listenSequenceNumber:0
purpose:FSTQueryPurposeListen
snapshotVersion:[FSTSnapshotVersion noVersion]
resumeToken:[NSData data]];
diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.m b/Firestore/Example/Tests/SpecTests/FSTSpecTests.m
index 3abcb48..7fed64a 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.m
+++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.m
@@ -503,6 +503,7 @@ static NSString *const kNoIOSTag = @"no-ios";
expectedActiveTargets[@(targetID)] =
[[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:0
purpose:FSTQueryPurposeListen
snapshotVersion:[FSTSnapshotVersion noVersion]
resumeToken:resumeToken];
diff --git a/Firestore/Source/Core/FSTListenSequence.h b/Firestore/Source/Core/FSTListenSequence.h
new file mode 100644
index 0000000..56d0e78
--- /dev/null
+++ b/Firestore/Source/Core/FSTListenSequence.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "FSTTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FSTListenSequence is a monotonic sequence. It is initialized with a minimum value to
+ * exceed. All subsequent calls to next will return increasing values.
+ */
+@interface FSTListenSequence : NSObject
+
+- (instancetype)initStartingAfter:(FSTListenSequenceNumber)after NS_DESIGNATED_INITIALIZER;
+
+- (id)init NS_UNAVAILABLE;
+
+- (FSTListenSequenceNumber)next;
+
+@end
+
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/Firestore/Source/Core/FSTListenSequence.m b/Firestore/Source/Core/FSTListenSequence.m
new file mode 100644
index 0000000..27ade7c
--- /dev/null
+++ b/Firestore/Source/Core/FSTListenSequence.m
@@ -0,0 +1,34 @@
+#import "FSTListenSequence.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+#pragma mark - FSTListenSequence
+
+@interface FSTListenSequence () {
+ FSTListenSequenceNumber _previousSequenceNumber;
+}
+
+@end
+
+@implementation FSTListenSequence
+
+#pragma mark - Constructors
+
+- (instancetype)initStartingAfter:(FSTListenSequenceNumber)after {
+ self = [super init];
+ if (self) {
+ _previousSequenceNumber = after;
+ }
+ return self;
+}
+
+#pragma mark - Public methods
+
+- (FSTListenSequenceNumber)next {
+ _previousSequenceNumber++;
+ return _previousSequenceNumber;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/Firestore/Source/Core/FSTSyncEngine.m b/Firestore/Source/Core/FSTSyncEngine.m
index 27ab73e..f90c5dd 100644
--- a/Firestore/Source/Core/FSTSyncEngine.m
+++ b/Firestore/Source/Core/FSTSyncEngine.m
@@ -43,6 +43,10 @@
NS_ASSUME_NONNULL_BEGIN
+// Limbo documents don't use persistence, and are eagerly GC'd. So, listens for them don't need
+// real sequence numbers.
+static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1;
+
#pragma mark - FSTQueryView
/**
@@ -490,6 +494,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [FSTQuery queryWithPath:key.path];
FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
targetID:limboTargetID
+ listenSequenceNumber:kIrrelevantSequenceNumber
purpose:FSTQueryPurposeLimboResolution];
self.limboKeysByTarget[@(limboTargetID)] = key;
[self.remoteStore listenToTargetWithQueryData:queryData];
diff --git a/Firestore/Source/Core/FSTTypes.h b/Firestore/Source/Core/FSTTypes.h
index b47bd0b..877ec94 100644
--- a/Firestore/Source/Core/FSTTypes.h
+++ b/Firestore/Source/Core/FSTTypes.h
@@ -26,6 +26,8 @@ typedef int32_t FSTBatchID;
typedef int32_t FSTTargetID;
+typedef int64_t FSTListenSequenceNumber;
+
typedef NSNumber FSTBoxedTargetID;
/**
diff --git a/Firestore/Source/Local/FSTLevelDBQueryCache.mm b/Firestore/Source/Local/FSTLevelDBQueryCache.mm
index 13d15ee..8388b96 100644
--- a/Firestore/Source/Local/FSTLevelDBQueryCache.mm
+++ b/Firestore/Source/Local/FSTLevelDBQueryCache.mm
@@ -100,6 +100,10 @@ static ReadOptions GetStandardReadOptions() {
return self.metadata.highestTargetId;
}
+- (FSTListenSequenceNumber)highestListenSequenceNumber {
+ return self.metadata.highestListenSequenceNumber;
+}
+
- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
return _lastRemoteSnapshotVersion;
}
@@ -116,7 +120,6 @@ static ReadOptions GetStandardReadOptions() {
}
- (void)addQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group {
- // TODO(mcg): actually populate listen sequence number
FSTTargetID targetID = queryData.targetID;
std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID];
[group setMessage:[self.serializer encodedQueryData:queryData] forKey:key];
@@ -127,9 +130,19 @@ static ReadOptions GetStandardReadOptions() {
std::string emptyBuffer;
[group setData:emptyBuffer forKey:indexKey];
+ BOOL saveMetadata = NO;
FSTPBTargetGlobal *metadata = self.metadata;
if (targetID > metadata.highestTargetId) {
metadata.highestTargetId = targetID;
+ saveMetadata = YES;
+ }
+
+ if (queryData.sequenceNumber > metadata.highestListenSequenceNumber) {
+ metadata.highestListenSequenceNumber = queryData.sequenceNumber;
+ saveMetadata = YES;
+ }
+
+ if (saveMetadata) {
[group setMessage:metadata forKey:[FSTLevelDBTargetGlobalKey key]];
}
}
diff --git a/Firestore/Source/Local/FSTLocalSerializer.m b/Firestore/Source/Local/FSTLocalSerializer.m
index c71e9dd..82aec4d 100644
--- a/Firestore/Source/Local/FSTLocalSerializer.m
+++ b/Firestore/Source/Local/FSTLocalSerializer.m
@@ -156,6 +156,7 @@
FSTPBTarget *proto = [FSTPBTarget message];
proto.targetId = queryData.targetID;
+ proto.lastListenSequenceNumber = queryData.sequenceNumber;
proto.snapshotVersion = [remoteSerializer encodedVersion:queryData.snapshotVersion];
proto.resumeToken = queryData.resumeToken;
@@ -173,6 +174,7 @@
FSTSerializerBeta *remoteSerializer = self.remoteSerializer;
FSTTargetID targetID = target.targetId;
+ FSTListenSequenceNumber sequenceNumber = target.lastListenSequenceNumber;
FSTSnapshotVersion *version = [remoteSerializer decodedVersion:target.snapshotVersion];
NSData *resumeToken = target.resumeToken;
@@ -192,6 +194,7 @@
return [[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:sequenceNumber
purpose:FSTQueryPurposeListen
snapshotVersion:version
resumeToken:resumeToken];
diff --git a/Firestore/Source/Local/FSTLocalStore.m b/Firestore/Source/Local/FSTLocalStore.m
index cde7104..3a5b0b4 100644
--- a/Firestore/Source/Local/FSTLocalStore.m
+++ b/Firestore/Source/Local/FSTLocalStore.m
@@ -17,6 +17,7 @@
#import "Firestore/Source/Local/FSTLocalStore.h"
#import "Firestore/Source/Auth/FSTUser.h"
+#import "Firestore/Source/Core/FSTListenSequence.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Core/FSTTargetIDGenerator.h"
@@ -76,6 +77,8 @@ NS_ASSUME_NONNULL_BEGIN
/** Used to generate targetIDs for queries tracked locally. */
@property(nonatomic, strong) FSTTargetIDGenerator *targetIDGenerator;
+@property(nonatomic, strong) FSTListenSequence *listenSequence;
+
/**
* A heldBatchResult is a mutation batch result (from a write acknowledgement) that arrived before
* the watch stream got notified of a snapshot that includes the write.  So we "hold" it until
@@ -148,6 +151,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTTargetID targetID = [self.queryCache highestTargetID];
self.targetIDGenerator = [FSTTargetIDGenerator generatorForLocalStoreStartingAfterID:targetID];
+ FSTListenSequenceNumber sequenceNumber = [self.queryCache highestListenSequenceNumber];
+ self.listenSequence = [[FSTListenSequence alloc] initStartingAfter:sequenceNumber];
}
- (void)shutdown {
@@ -380,6 +385,7 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTQueryData *)allocateQuery:(FSTQuery *)query {
FSTQueryData *cached = [self.queryCache queryDataForQuery:query];
FSTTargetID targetID;
+ FSTListenSequenceNumber sequenceNumber = [self.listenSequence next];
if (cached) {
// This query has been listened to previously, so reuse the previous targetID.
// TODO(mcg): freshen last accessed date?
@@ -388,8 +394,10 @@ NS_ASSUME_NONNULL_BEGIN
FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Allocate query"];
targetID = [self.targetIDGenerator nextID];
- cached =
- [[FSTQueryData alloc] initWithQuery:query targetID:targetID purpose:FSTQueryPurposeListen];
+ cached = [[FSTQueryData alloc] initWithQuery:query
+ targetID:targetID
+ listenSequenceNumber:sequenceNumber
+ purpose:FSTQueryPurposeListen];
[self.queryCache addQueryData:cached group:group];
[self.persistence commitGroup:group];
diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.m b/Firestore/Source/Local/FSTMemoryQueryCache.m
index 8d37bcb..bcab174 100644
--- a/Firestore/Source/Local/FSTMemoryQueryCache.m
+++ b/Firestore/Source/Local/FSTMemoryQueryCache.m
@@ -34,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN
/** The highest numbered target ID encountered. */
@property(nonatomic, assign) FSTTargetID highestTargetID;
+@property(nonatomic, assign) FSTListenSequenceNumber highestListenSequenceNumber;
+
@end
@implementation FSTMemoryQueryCache {
@@ -65,6 +67,10 @@ NS_ASSUME_NONNULL_BEGIN
return _highestTargetID;
}
+- (FSTListenSequenceNumber)highestListenSequenceNumber {
+ return _highestListenSequenceNumber;
+}
+
- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
return _lastRemoteSnapshotVersion;
}
@@ -79,6 +85,9 @@ NS_ASSUME_NONNULL_BEGIN
if (queryData.targetID > self.highestTargetID) {
self.highestTargetID = queryData.targetID;
}
+ if (queryData.sequenceNumber > self.highestListenSequenceNumber) {
+ self.highestListenSequenceNumber = queryData.sequenceNumber;
+ }
}
- (void)removeQueryData:(FSTQueryData *)queryData group:(__unused FSTWriteGroup *)group {
diff --git a/Firestore/Source/Local/FSTQueryCache.h b/Firestore/Source/Local/FSTQueryCache.h
index e0cf4c8..88c9df9 100644
--- a/Firestore/Source/Local/FSTQueryCache.h
+++ b/Firestore/Source/Local/FSTQueryCache.h
@@ -53,6 +53,11 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTTargetID)highestTargetID;
/**
+ * Returns the highest listen sequence number of any query seen by the cache.
+ */
+- (FSTListenSequenceNumber)highestListenSequenceNumber;
+
+/**
* A global snapshot version representing the last consistent snapshot we received from the
* backend. This is monotonically increasing and any snapshots received from the backend prior to
* this version (e.g. for targets resumed with a resume_token) should be suppressed (buffered)
diff --git a/Firestore/Source/Local/FSTQueryData.h b/Firestore/Source/Local/FSTQueryData.h
index 048bfad..5db2de6 100644
--- a/Firestore/Source/Local/FSTQueryData.h
+++ b/Firestore/Source/Local/FSTQueryData.h
@@ -40,6 +40,7 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose
snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
resumeToken:(NSData *)resumeToken NS_DESIGNATED_INITIALIZER;
@@ -47,6 +48,7 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
/** Convenience initializer for use when creating an FSTQueryData for the first time. */
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose;
- (instancetype)init NS_UNAVAILABLE;
@@ -64,6 +66,8 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
*/
@property(nonatomic, assign, readonly) FSTTargetID targetID;
+@property(nonatomic, assign, readonly) FSTListenSequenceNumber sequenceNumber;
+
/** The purpose of the query. */
@property(nonatomic, assign, readonly) FSTQueryPurpose purpose;
diff --git a/Firestore/Source/Local/FSTQueryData.m b/Firestore/Source/Local/FSTQueryData.m
index 080f136..6bb716a 100644
--- a/Firestore/Source/Local/FSTQueryData.m
+++ b/Firestore/Source/Local/FSTQueryData.m
@@ -25,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose
snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
resumeToken:(NSData *)resumeToken {
@@ -32,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
if (self) {
_query = query;
_targetID = targetID;
+ _sequenceNumber = sequenceNumber;
_purpose = purpose;
_snapshotVersion = snapshotVersion;
_resumeToken = [resumeToken copy];
@@ -41,9 +43,11 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
+ listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose {
return [self initWithQuery:query
targetID:targetID
+ listenSequenceNumber:sequenceNumber
purpose:purpose
snapshotVersion:[FSTSnapshotVersion noVersion]
resumeToken:[NSData data]];
@@ -83,6 +87,7 @@ NS_ASSUME_NONNULL_BEGIN
resumeToken:(NSData *)resumeToken {
return [[FSTQueryData alloc] initWithQuery:self.query
targetID:self.targetID
+ listenSequenceNumber:self.sequenceNumber
purpose:self.purpose
snapshotVersion:snapshotVersion
resumeToken:resumeToken];
diff --git a/Firestore/Source/Remote/FSTRemoteStore.m b/Firestore/Source/Remote/FSTRemoteStore.m
index a0c5059..1201049 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.m
+++ b/Firestore/Source/Remote/FSTRemoteStore.m
@@ -468,8 +468,10 @@ static const int kOnlineAttemptsBeforeFailure = 2;
[remoteEvent handleExistenceFilterMismatchForTargetID:target];
// Clear the resume token for the query, since we're in a known mismatch state.
- queryData =
- [[FSTQueryData alloc] initWithQuery:query targetID:targetID purpose:queryData.purpose];
+ queryData = [[FSTQueryData alloc] initWithQuery:query
+ targetID:targetID
+ listenSequenceNumber:queryData.sequenceNumber
+ purpose:queryData.purpose];
self.listenTargets[target] = queryData;
// Cause a hard reset by unwatching and rewatching immediately, but deliberately don't
@@ -483,6 +485,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
FSTQueryData *requestQueryData =
[[FSTQueryData alloc] initWithQuery:query
targetID:targetID
+ listenSequenceNumber:queryData.sequenceNumber
purpose:FSTQueryPurposeExistenceFilterMismatch];
[self sendWatchRequestWithQueryData:requestQueryData];
}