diff --git a/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm index d3bee109b95..98fd8990f7f 100644 --- a/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm +++ b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm @@ -73,11 +73,11 @@ - (void)testEquals { } - (void)testIncludeMetadataChanges { - FSTDocument *doc1Old = FSTTestDoc("foo/bar", 1, @{@"a" : @"b"}, YES); - FSTDocument *doc1New = FSTTestDoc("foo/bar", 1, @{@"a" : @"b"}, NO); + FSTDocument *doc1Old = FSTTestDoc("foo/bar", 1, @{@"a" : @"b"}, FSTDocumentStateLocalMutations); + FSTDocument *doc1New = FSTTestDoc("foo/bar", 1, @{@"a" : @"b"}, FSTDocumentStateSynced); - FSTDocument *doc2Old = FSTTestDoc("foo/baz", 1, @{@"a" : @"b"}, NO); - FSTDocument *doc2New = FSTTestDoc("foo/baz", 1, @{@"a" : @"c"}, NO); + FSTDocument *doc2Old = FSTTestDoc("foo/baz", 1, @{@"a" : @"b"}, FSTDocumentStateSynced); + FSTDocument *doc2New = FSTTestDoc("foo/baz", 1, @{@"a" : @"c"}, FSTDocumentStateSynced); FSTDocumentSet *oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[ doc1Old, doc2Old ]); FSTDocumentSet *newDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[ doc2New, doc2New ]); diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.mm b/Firestore/Example/Tests/API/FSTAPIHelpers.mm index e9f1aea3e8d..860e3ff770a 100644 --- a/Firestore/Example/Tests/API/FSTAPIHelpers.mm +++ b/Firestore/Example/Tests/API/FSTAPIHelpers.mm @@ -63,7 +63,10 @@ NSDictionary *_Nullable data, BOOL hasMutations, BOOL fromCache) { - FSTDocument *doc = data ? FSTTestDoc(path, version, data, hasMutations) : nil; + FSTDocument *doc = + data ? FSTTestDoc(path, version, data, + hasMutations ? FSTDocumentStateLocalMutations : FSTDocumentStateSynced) + : nil; return [FIRDocumentSnapshot snapshotWithFirestore:FSTTestFirestore() documentKey:testutil::Key(path) document:doc @@ -91,15 +94,18 @@ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:hasPendingWrites fromCache:fromCache]; FSTDocumentSet *oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[]); for (NSString *key in oldDocs) { - oldDocuments = - [oldDocuments documentSetByAddingDocument:FSTTestDoc(util::StringFormat("%s/%s", path, key), - 1, oldDocs[key], hasPendingWrites)]; + oldDocuments = [oldDocuments + documentSetByAddingDocument:FSTTestDoc(util::StringFormat("%s/%s", path, key), 1, + oldDocs[key], + hasPendingWrites ? FSTDocumentStateLocalMutations + : FSTDocumentStateSynced)]; } FSTDocumentSet *newDocuments = oldDocuments; NSArray *documentChanges = [NSArray array]; for (NSString *key in docsToAdd) { FSTDocument *docToAdd = - FSTTestDoc(util::StringFormat("%s/%s", path, key), 1, docsToAdd[key], hasPendingWrites); + FSTTestDoc(util::StringFormat("%s/%s", path, key), 1, docsToAdd[key], + hasPendingWrites ? FSTDocumentStateLocalMutations : FSTDocumentStateSynced); newDocuments = [newDocuments documentSetByAddingDocument:docToAdd]; documentChanges = [documentChanges arrayByAddingObject:[FSTDocumentViewChange diff --git a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm index 63087c944f4..6aa9fe4ffdc 100644 --- a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm +++ b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm @@ -56,10 +56,10 @@ - (void)testRaisesCollectionEvents { NSMutableArray *otherAccum = [NSMutableArray array]; FSTQuery *query = FSTTestQuery("rooms"); - FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO); - FSTDocument *doc2prime = - FSTTestDoc("rooms/Hades", 3, @{@"name" : @"Hades", @"owner" : @"Jonny"}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); + FSTDocument *doc2prime = FSTTestDoc("rooms/Hades", 3, @{@"name" : @"Hades", @"owner" : @"Jonny"}, + FSTDocumentStateSynced); FSTQueryListener *listener = [self listenToQuery:query accumulatingSnapshots:accum]; FSTQueryListener *otherListener = [self listenToQuery:query accumulatingSnapshots:otherAccum]; @@ -133,8 +133,8 @@ - (void)testMutingAsyncListenerPreventsAllSubsequentEvents { NSMutableArray *accum = [NSMutableArray array]; FSTQuery *query = FSTTestQuery("rooms/Eros"); - FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 3, @{@"name" : @"Eros"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/Eros", 4, @{@"name" : @"Eros2"}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 3, @{@"name" : @"Eros"}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/Eros", 4, @{@"name" : @"Eros2"}, FSTDocumentStateSynced); __block FSTAsyncQueryListener *listener = [[FSTAsyncQueryListener alloc] initWithExecutor:_executor.get() @@ -171,8 +171,8 @@ - (void)testDoesNotRaiseEventsForMetadataChangesUnlessSpecified { NSMutableArray *fullAccum = [NSMutableArray array]; FSTQuery *query = FSTTestQuery("rooms"); - FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES includeDocumentMetadataChanges:NO @@ -207,10 +207,12 @@ - (void)testRaisesDocumentMetadataEventsOnlyWhenSpecified { NSMutableArray *fullAccum = [NSMutableArray array]; FSTQuery *query = FSTTestQuery("rooms"); - FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, YES); - FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO); - FSTDocument *doc1Prime = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateLocalMutations); + FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); + FSTDocument *doc1Prime = + FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, FSTDocumentStateSynced); FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO includeDocumentMetadataChanges:YES @@ -256,11 +258,15 @@ - (void)testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChanges { NSMutableArray *fullAccum = [NSMutableArray array]; FSTQuery *query = FSTTestQuery("rooms"); - FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, YES); - FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, YES); - FSTDocument *doc1Prime = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO); - FSTDocument *doc2Prime = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateLocalMutations); + FSTDocument *doc2 = + FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateLocalMutations); + FSTDocument *doc1Prime = + FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); + FSTDocument *doc2Prime = + FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, FSTDocumentStateSynced); FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES includeDocumentMetadataChanges:NO @@ -293,10 +299,12 @@ - (void)testMetadataOnlyDocumentChangesAreFilteredOutWhenIncludeDocumentMetadata NSMutableArray *filteredAccum = [NSMutableArray array]; FSTQuery *query = FSTTestQuery("rooms"); - FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, YES); - FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO); - FSTDocument *doc1Prime = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateLocalMutations); + FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); + FSTDocument *doc1Prime = + FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("rooms/Other", 3, @{@"name" : @"Other"}, FSTDocumentStateSynced); FSTQueryListener *filteredListener = [self listenToQuery:query accumulatingSnapshots:filteredAccum]; @@ -325,8 +333,8 @@ - (void)testWillWaitForSyncIfOnline { NSMutableArray *events = [NSMutableArray array]; FSTQuery *query = FSTTestQuery("rooms"); - FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); FSTQueryListener *listener = [self listenToQuery:query options:[[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO @@ -366,8 +374,8 @@ - (void)testWillRaiseInitialEventWhenGoingOffline { NSMutableArray *events = [NSMutableArray array]; FSTQuery *query = FSTTestQuery("rooms"); - FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 1, @{@"name" : @"Eros"}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/Hades", 2, @{@"name" : @"Hades"}, FSTDocumentStateSynced); FSTQueryListener *listener = [self listenToQuery:query options:[[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO diff --git a/Firestore/Example/Tests/Core/FSTQueryTests.mm b/Firestore/Example/Tests/Core/FSTQueryTests.mm index e3e391a6763..9ad357eb981 100644 --- a/Firestore/Example/Tests/Core/FSTQueryTests.mm +++ b/Firestore/Example/Tests/Core/FSTQueryTests.mm @@ -88,9 +88,12 @@ - (void)testOrderBy { } - (void)testMatchesBasedOnDocumentKey { - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced); + FSTDocument *doc2 = + FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced); + FSTDocument *doc3 = + FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced); // document query FSTQuery *query = FSTTestQuery("rooms/eros/messages/1"); @@ -100,10 +103,14 @@ - (void)testMatchesBasedOnDocumentKey { } - (void)testMatchesCorrectlyForShallowAncestorQuery { - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO); - FSTDocument *doc1Meta = FSTTestDoc("rooms/eros/messages/1/meta/1", 0, @{@"meta" : @"mv"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced); + FSTDocument *doc1Meta = + FSTTestDoc("rooms/eros/messages/1/meta/1", 0, @{@"meta" : @"mv"}, FSTDocumentStateSynced); + FSTDocument *doc2 = + FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced); + FSTDocument *doc3 = + FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced); // shallow ancestor query FSTQuery *query = FSTTestQuery("rooms/eros/messages"); @@ -114,8 +121,9 @@ - (void)testMatchesCorrectlyForShallowAncestorQuery { } - (void)testEmptyFieldsAreAllowedForQueries { - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateSynced); FSTQuery *query = [FSTTestQuery("rooms/eros/messages") queryByAddingFilter:FSTTestFilter("text", @"==", @"msg1")]; @@ -129,12 +137,12 @@ - (void)testMatchesPrimitiveValuesForFilters { FSTQuery *query2 = [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @"<=", @(2))]; - FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : @1}, NO); - FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @2}, NO); - FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{@"sort" : @3}, NO); - FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{@"sort" : @NO}, NO); - FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, NO); - FSTDocument *doc6 = FSTTestDoc("collection/6", 0, @{}, NO); + FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : @1}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @2}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{@"sort" : @3}, FSTDocumentStateSynced); + FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{@"sort" : @NO}, FSTDocumentStateSynced); + FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, FSTDocumentStateSynced); + FSTDocument *doc6 = FSTTestDoc("collection/6", 0, @{}, FSTDocumentStateSynced); XCTAssertFalse([query1 matchesDocument:doc1]); XCTAssertTrue([query1 matchesDocument:doc2]); @@ -156,11 +164,11 @@ - (void)testArrayContainsFilter { queryByAddingFilter:FSTTestFilter("array", @"array_contains", @42)]; // not an array. - FSTDocument *doc = FSTTestDoc("collection/1", 0, @{@"array" : @1}, NO); + FSTDocument *doc = FSTTestDoc("collection/1", 0, @{@"array" : @1}, FSTDocumentStateSynced); XCTAssertFalse([query matchesDocument:doc]); // empty array. - doc = FSTTestDoc("collection/1", 0, @{@"array" : @[]}, NO); + doc = FSTTestDoc("collection/1", 0, @{@"array" : @[]}, FSTDocumentStateSynced); XCTAssertFalse([query matchesDocument:doc]); // array without element (and make sure it doesn't match in a nested field or a different field). @@ -170,11 +178,14 @@ - (void)testArrayContainsFilter { @{@"a" : @42, @"b" : @[ @42 ]} ], @"different" : @[ @42 ]}, - NO); + FSTDocumentStateSynced); XCTAssertFalse([query matchesDocument:doc]); // array with element. - doc = FSTTestDoc("collection/1", 0, @{@"array" : @[ @1, @"2", @42, @{@"a" : @1} ]}, NO); + doc = FSTTestDoc("collection/1", 0, + @{@"array" : @[ @1, @"2", @42, + @{@"a" : @1} ]}, + FSTDocumentStateSynced); XCTAssertTrue([query matchesDocument:doc]); } @@ -194,22 +205,26 @@ - (void)testArrayContainsFilterWithObjectValue { @"b" : @42} ] }, - NO); + FSTDocumentStateSynced); XCTAssertFalse([query matchesDocument:doc]); // array with element. - doc = FSTTestDoc("collection/1", 0, @{@"array" : @[ @1, @"2", @42, @{@"a" : @[ @42 ]} ]}, NO); + doc = FSTTestDoc("collection/1", 0, + @{@"array" : @[ @1, @"2", @42, + @{@"a" : @[ @42 ]} ]}, + FSTDocumentStateSynced); XCTAssertTrue([query matchesDocument:doc]); } - (void)testNullFilter { FSTQuery *query = [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @"==", [NSNull null])]; - FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : [NSNull null]}, NO); - FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @2}, NO); - FSTDocument *doc3 = FSTTestDoc("collection/2", 0, @{@"sort" : @3.1}, NO); - FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{@"sort" : @NO}, NO); - FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, NO); + FSTDocument *doc1 = + FSTTestDoc("collection/1", 0, @{@"sort" : [NSNull null]}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @2}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("collection/2", 0, @{@"sort" : @3.1}, FSTDocumentStateSynced); + FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{@"sort" : @NO}, FSTDocumentStateSynced); + FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, FSTDocumentStateSynced); XCTAssertTrue([query matchesDocument:doc1]); XCTAssertFalse([query matchesDocument:doc2]); @@ -221,11 +236,11 @@ - (void)testNullFilter { - (void)testNanFilter { FSTQuery *query = [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @"==", @(NAN))]; - FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : @(NAN)}, NO); - FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @2}, NO); - FSTDocument *doc3 = FSTTestDoc("collection/2", 0, @{@"sort" : @3.1}, NO); - FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{@"sort" : @NO}, NO); - FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, NO); + FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : @(NAN)}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @2}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("collection/2", 0, @{@"sort" : @3.1}, FSTDocumentStateSynced); + FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{@"sort" : @NO}, FSTDocumentStateSynced); + FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @"string"}, FSTDocumentStateSynced); XCTAssertTrue([query matchesDocument:doc1]); XCTAssertFalse([query matchesDocument:doc2]); @@ -240,13 +255,17 @@ - (void)testDoesNotMatchComplexObjectsForFilters { FSTQuery *query2 = [FSTTestQuery("collection") queryByAddingFilter:FSTTestFilter("sort", @">=", @(2))]; - FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : @2}, NO); - FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @[]}, NO); - FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{@"sort" : @[ @1 ]}, NO); - FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{@"sort" : @{@"foo" : @2}}, NO); - FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @{@"foo" : @"bar"}}, NO); - FSTDocument *doc6 = FSTTestDoc("collection/6", 0, @{@"sort" : @{}}, NO); // no sort field - FSTDocument *doc7 = FSTTestDoc("collection/7", 0, @{@"sort" : @[ @3, @1 ]}, NO); + FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : @2}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @[]}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{@"sort" : @[ @1 ]}, FSTDocumentStateSynced); + FSTDocument *doc4 = FSTTestDoc("collection/4", 0, + @{@"sort" : @{@"foo" : @2}}, FSTDocumentStateSynced); + FSTDocument *doc5 = FSTTestDoc("collection/5", 0, + @{@"sort" : @{@"foo" : @"bar"}}, FSTDocumentStateSynced); + FSTDocument *doc6 = FSTTestDoc("collection/6", 0, + @{@"sort" : @{}}, FSTDocumentStateSynced); // no sort field + FSTDocument *doc7 = FSTTestDoc("collection/7", 0, + @{@"sort" : @[ @3, @1 ]}, FSTDocumentStateSynced); XCTAssertTrue([query1 matchesDocument:doc1]); XCTAssertFalse([query1 matchesDocument:doc2]); @@ -270,12 +289,14 @@ - (void)testDoesntRemoveComplexObjectsWithOrderBy { queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("sort") ascending:YES]]; - FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : @2}, NO); - FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @[]}, NO); - FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{@"sort" : @[ @1 ]}, NO); - FSTDocument *doc4 = FSTTestDoc("collection/4", 0, @{@"sort" : @{@"foo" : @2}}, NO); - FSTDocument *doc5 = FSTTestDoc("collection/5", 0, @{@"sort" : @{@"foo" : @"bar"}}, NO); - FSTDocument *doc6 = FSTTestDoc("collection/6", 0, @{}, NO); + FSTDocument *doc1 = FSTTestDoc("collection/1", 0, @{@"sort" : @2}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("collection/2", 0, @{@"sort" : @[]}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("collection/3", 0, @{@"sort" : @[ @1 ]}, FSTDocumentStateSynced); + FSTDocument *doc4 = FSTTestDoc("collection/4", 0, + @{@"sort" : @{@"foo" : @2}}, FSTDocumentStateSynced); + FSTDocument *doc5 = FSTTestDoc("collection/5", 0, + @{@"sort" : @{@"foo" : @"bar"}}, FSTDocumentStateSynced); + FSTDocument *doc6 = FSTTestDoc("collection/6", 0, @{}, FSTDocumentStateSynced); XCTAssertTrue([query1 matchesDocument:doc1]); XCTAssertTrue([query1 matchesDocument:doc2]); @@ -287,7 +308,8 @@ - (void)testDoesntRemoveComplexObjectsWithOrderBy { - (void)testFiltersBasedOnArrayValue { FSTQuery *baseQuery = FSTTestQuery("collection"); - FSTDocument *doc1 = FSTTestDoc("collection/doc", 0, @{@"tags" : @[ @"foo", @1, @YES ]}, NO); + FSTDocument *doc1 = FSTTestDoc("collection/doc", 0, + @{@"tags" : @[ @"foo", @1, @YES ]}, FSTDocumentStateSynced); NSArray *matchingFilters = @[ FSTTestFilter("tags", @"==", @[ @"foo", @1, @YES ]) ]; @@ -310,7 +332,8 @@ - (void)testFiltersBasedOnObjectValue { FSTQuery *baseQuery = FSTTestQuery("collection"); FSTDocument *doc1 = FSTTestDoc("collection/doc", 0, - @{@"tags" : @{@"foo" : @"foo", @"a" : @0, @"b" : @YES, @"c" : @(NAN)}}, NO); + @{@"tags" : @{@"foo" : @"foo", @"a" : @0, @"b" : @YES, @"c" : @(NAN)}}, + FSTDocumentStateSynced); NSArray *matchingFilters = @[ FSTTestFilter("tags", @"==", @@ -365,21 +388,21 @@ - (void)testSortsDocumentsInTheCorrectOrder { // clang-format off NSArray *docs = @[ - FSTTestDoc("collection/1", 0, @{@"sort": [NSNull null]}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @NO}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @YES}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @1}, NO), - FSTTestDoc("collection/2", 0, @{@"sort": @1}, NO), // by key - FSTTestDoc("collection/3", 0, @{@"sort": @1}, NO), // by key - FSTTestDoc("collection/1", 0, @{@"sort": @1.9}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @2}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @2.1}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @""}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @"a"}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @"ab"}, NO), - FSTTestDoc("collection/1", 0, @{@"sort": @"b"}, NO), + FSTTestDoc("collection/1", 0, @{@"sort": [NSNull null]}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @NO}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @YES}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @1}, FSTDocumentStateSynced), + FSTTestDoc("collection/2", 0, @{@"sort": @1}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/3", 0, @{@"sort": @1}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/1", 0, @{@"sort": @1.9}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @2}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @2.1}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @""}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @"a"}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @"ab"}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort": @"b"}, FSTDocumentStateSynced), FSTTestDoc("collection/1", 0, @{@"sort": - FSTTestRef("project", DatabaseId::kDefault, @"collection/id1")}, NO), + FSTTestRef("project", DatabaseId::kDefault, @"collection/id1")}, FSTDocumentStateSynced), ]; // clang-format on @@ -397,16 +420,16 @@ - (void)testSortsDocumentsUsingMultipleFields { // clang-format off NSArray *docs = - @[FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @1}, NO), - FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @2}, NO), - FSTTestDoc("collection/2", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key - FSTTestDoc("collection/3", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key - FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @3}, NO), - FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @1}, NO), - FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @2}, NO), - FSTTestDoc("collection/2", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key - FSTTestDoc("collection/3", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key - FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @3}, NO), + @[FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @1}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @2}, FSTDocumentStateSynced), + FSTTestDoc("collection/2", 0, @{@"sort1": @1, @"sort2": @2}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/3", 0, @{@"sort1": @1, @"sort2": @2}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @3}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @1}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @2}, FSTDocumentStateSynced), + FSTTestDoc("collection/2", 0, @{@"sort1": @2, @"sort2": @2}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/3", 0, @{@"sort1": @2, @"sort2": @2}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @3}, FSTDocumentStateSynced), ]; // clang-format on @@ -424,16 +447,16 @@ - (void)testSortsDocumentsWithDescendingToo { // clang-format off NSArray *docs = - @[FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @3}, NO), - FSTTestDoc("collection/3", 0, @{@"sort1": @2, @"sort2": @2}, NO), - FSTTestDoc("collection/2", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key - FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @2}, NO), // by key - FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @1}, NO), - FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @3}, NO), - FSTTestDoc("collection/3", 0, @{@"sort1": @1, @"sort2": @2}, NO), - FSTTestDoc("collection/2", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key - FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @2}, NO), // by key - FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @1}, NO), + @[FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @3}, FSTDocumentStateSynced), + FSTTestDoc("collection/3", 0, @{@"sort1": @2, @"sort2": @2}, FSTDocumentStateSynced), + FSTTestDoc("collection/2", 0, @{@"sort1": @2, @"sort2": @2}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @2}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/1", 0, @{@"sort1": @2, @"sort2": @1}, FSTDocumentStateSynced), + FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @3}, FSTDocumentStateSynced), + FSTTestDoc("collection/3", 0, @{@"sort1": @1, @"sort2": @2}, FSTDocumentStateSynced), + FSTTestDoc("collection/2", 0, @{@"sort1": @1, @"sort2": @2}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @2}, FSTDocumentStateSynced), // by key + FSTTestDoc("collection/1", 0, @{@"sort1": @1, @"sort2": @1}, FSTDocumentStateSynced), ]; // clang-format on diff --git a/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm b/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm index ca8954e95e1..21210b82b26 100644 --- a/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm +++ b/Firestore/Example/Tests/Core/FSTViewSnapshotTest.mm @@ -32,7 +32,7 @@ @interface FSTViewSnapshotTests : XCTestCase @implementation FSTViewSnapshotTests - (void)testDocumentChangeConstructor { - FSTDocument *doc = FSTTestDoc("a/b", 0, @{}, NO); + FSTDocument *doc = FSTTestDoc("a/b", 0, @{}, FSTDocumentStateSynced); FSTDocumentViewChangeType type = FSTDocumentViewChangeTypeModified; FSTDocumentViewChange *change = [FSTDocumentViewChange changeWithDocument:doc type:type]; XCTAssertEqual(change.document, doc); @@ -42,15 +42,15 @@ - (void)testDocumentChangeConstructor { - (void)testTrack { FSTDocumentViewChangeSet *set = [FSTDocumentViewChangeSet changeSet]; - FSTDocument *docAdded = FSTTestDoc("a/1", 0, @{}, NO); - FSTDocument *docRemoved = FSTTestDoc("a/2", 0, @{}, NO); - FSTDocument *docModified = FSTTestDoc("a/3", 0, @{}, NO); + FSTDocument *docAdded = FSTTestDoc("a/1", 0, @{}, FSTDocumentStateSynced); + FSTDocument *docRemoved = FSTTestDoc("a/2", 0, @{}, FSTDocumentStateSynced); + FSTDocument *docModified = FSTTestDoc("a/3", 0, @{}, FSTDocumentStateSynced); - FSTDocument *docAddedThenModified = FSTTestDoc("b/1", 0, @{}, NO); - FSTDocument *docAddedThenRemoved = FSTTestDoc("b/2", 0, @{}, NO); - FSTDocument *docRemovedThenAdded = FSTTestDoc("b/3", 0, @{}, NO); - FSTDocument *docModifiedThenRemoved = FSTTestDoc("b/4", 0, @{}, NO); - FSTDocument *docModifiedThenModified = FSTTestDoc("b/5", 0, @{}, NO); + FSTDocument *docAddedThenModified = FSTTestDoc("b/1", 0, @{}, FSTDocumentStateSynced); + FSTDocument *docAddedThenRemoved = FSTTestDoc("b/2", 0, @{}, FSTDocumentStateSynced); + FSTDocument *docRemovedThenAdded = FSTTestDoc("b/3", 0, @{}, FSTDocumentStateSynced); + FSTDocument *docModifiedThenRemoved = FSTTestDoc("b/4", 0, @{}, FSTDocumentStateSynced); + FSTDocument *docModifiedThenModified = FSTTestDoc("b/5", 0, @{}, FSTDocumentStateSynced); [set addChange:[FSTDocumentViewChange changeWithDocument:docAdded type:FSTDocumentViewChangeTypeAdded]]; @@ -109,9 +109,10 @@ - (void)testViewSnapshotConstructor { FSTQuery *query = FSTTestQuery("a"); FSTDocumentSet *documents = [FSTDocumentSet documentSetWithComparator:FSTDocumentComparatorByKey]; FSTDocumentSet *oldDocuments = documents; - documents = [documents documentSetByAddingDocument:FSTTestDoc("c/a", 1, @{}, NO)]; + documents = + [documents documentSetByAddingDocument:FSTTestDoc("c/a", 1, @{}, FSTDocumentStateSynced)]; NSArray *documentChanges = - @[ [FSTDocumentViewChange changeWithDocument:FSTTestDoc("c/a", 1, @{}, NO) + @[ [FSTDocumentViewChange changeWithDocument:FSTTestDoc("c/a", 1, @{}, FSTDocumentStateSynced) type:FSTDocumentViewChangeTypeAdded] ]; BOOL fromCache = YES; diff --git a/Firestore/Example/Tests/Core/FSTViewTests.mm b/Firestore/Example/Tests/Core/FSTViewTests.mm index 50c47068cfd..34c61a7d41e 100644 --- a/Firestore/Example/Tests/Core/FSTViewTests.mm +++ b/Firestore/Example/Tests/Core/FSTViewTests.mm @@ -52,9 +52,12 @@ - (void)testAddsDocumentsBasedOnQuery { FSTQuery *query = [self queryForMessages]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced); + FSTDocument *doc2 = + FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced); + FSTDocument *doc3 = + FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced); FSTViewSnapshot *_Nullable snapshot = FSTTestApplyChanges( view, @[ doc1, doc2, doc3 ], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key, doc3.key})); @@ -78,16 +81,19 @@ - (void)testRemovesDocuments { FSTQuery *query = [self queryForMessages]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced); + FSTDocument *doc2 = + FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced); + FSTDocument *doc3 = + FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced); // initial state FSTTestApplyChanges(view, @[ doc1, doc2 ], nil); // delete doc2, add doc3 FSTViewSnapshot *snapshot = - FSTTestApplyChanges(view, @[ FSTTestDeletedDoc("rooms/eros/messages/2", 0), doc3 ], + FSTTestApplyChanges(view, @[ FSTTestDeletedDoc("rooms/eros/messages/2", 0, NO), doc3 ], FSTTestTargetChangeAckDocuments({doc1.key, doc3.key})); XCTAssertEqual(snapshot.query, query); @@ -108,8 +114,10 @@ - (void)testReturnsNilIfThereAreNoChanges { FSTQuery *query = [self queryForMessages]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced); + FSTDocument *doc2 = + FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced); // initial state FSTTestApplyChanges(view, @[ doc1, doc2 ], nil); @@ -136,11 +144,16 @@ - (void)testFiltersDocumentsBasedOnQueryWithFilter { query = [query queryByAddingFilter:filter]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"sort" : @1}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"sort" : @2}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{@"sort" : @3}, NO); - FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{}, NO); // no sort, no match - FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/5", 0, @{@"sort" : @1}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, + @{@"sort" : @1}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, + @{@"sort" : @2}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, + @{@"sort" : @3}, FSTDocumentStateSynced); + FSTDocument *doc4 = + FSTTestDoc("rooms/eros/messages/4", 0, @{}, FSTDocumentStateSynced); // no sort, no match + FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/5", 0, + @{@"sort" : @1}, FSTDocumentStateSynced); FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2, doc3, doc4, doc5 ], nil); @@ -168,10 +181,13 @@ - (void)testUpdatesDocumentsBasedOnQueryWithFilter { query = [query queryByAddingFilter:filter]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"sort" : @1}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"sort" : @3}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{@"sort" : @2}, NO); - FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, + @{@"sort" : @1}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, + @{@"sort" : @3}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, + @{@"sort" : @2}, FSTDocumentStateSynced); + FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{}, FSTDocumentStateSynced); FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2, doc3, doc4 ], nil); @@ -179,9 +195,12 @@ - (void)testUpdatesDocumentsBasedOnQueryWithFilter { XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc3 ])); - FSTDocument *newDoc2 = FSTTestDoc("rooms/eros/messages/2", 1, @{@"sort" : @2}, NO); - FSTDocument *newDoc3 = FSTTestDoc("rooms/eros/messages/3", 1, @{@"sort" : @3}, NO); - FSTDocument *newDoc4 = FSTTestDoc("rooms/eros/messages/4", 1, @{@"sort" : @0}, NO); + FSTDocument *newDoc2 = FSTTestDoc("rooms/eros/messages/2", 1, + @{@"sort" : @2}, FSTDocumentStateSynced); + FSTDocument *newDoc3 = FSTTestDoc("rooms/eros/messages/3", 1, + @{@"sort" : @3}, FSTDocumentStateSynced); + FSTDocument *newDoc4 = FSTTestDoc("rooms/eros/messages/4", 1, + @{@"sort" : @0}, FSTDocumentStateSynced); snapshot = FSTTestApplyChanges(view, @[ newDoc2, newDoc3, newDoc4 ], nil); @@ -205,9 +224,12 @@ - (void)testRemovesDocumentsForQueryWithLimit { query = [query queryBySettingLimit:2]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, NO); + FSTDocument *doc1 = + FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced); + FSTDocument *doc2 = + FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced); + FSTDocument *doc3 = + FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced); // initial state FSTTestApplyChanges(view, @[ doc1, doc3 ], nil); @@ -237,10 +259,14 @@ - (void)testDoesntReportChangesForDocumentBeyondLimitOfQuery { query = [query queryBySettingLimit:2]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"num" : @1}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"num" : @2}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{@"num" : @3}, NO); - FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{@"num" : @4}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, + @{@"num" : @1}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, + @{@"num" : @2}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, + @{@"num" : @3}, FSTDocumentStateSynced); + FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, + @{@"num" : @4}, FSTDocumentStateSynced); // initial state FSTTestApplyChanges(view, @[ doc1, doc2 ], nil); @@ -249,7 +275,7 @@ - (void)testDoesntReportChangesForDocumentBeyondLimitOfQuery { // doc2 will be modified + removed = removed // doc3 will be added // doc4 will be added + removed = nothing - doc2 = FSTTestDoc("rooms/eros/messages/2", 1, @{@"num" : @5}, NO); + doc2 = FSTTestDoc("rooms/eros/messages/2", 1, @{@"num" : @5}, FSTDocumentStateSynced); FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2, doc3, doc4 ])]; XCTAssertTrue(viewDocChanges.needsRefill); @@ -280,9 +306,9 @@ - (void)testKeepsTrackOfLimboDocuments { FSTQuery *query = [self queryForMessages]; FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO); - FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateSynced); FSTViewChange *change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])]]; @@ -312,8 +338,7 @@ - (void)testKeepsTrackOfLimboDocuments { @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded key:doc3.key] ]); change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ - FSTTestDeletedDoc("rooms/eros/messages/2", - 1) + FSTTestDeletedDoc("rooms/eros/messages/2", 1, NO) ])]]; // remove XCTAssertEqualObjects( change.limboChanges, @@ -323,8 +348,8 @@ - (void)testKeepsTrackOfLimboDocuments { - (void)testResumingQueryCreatesNoLimbos { FSTQuery *query = [self queryForMessages]; - FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO); - FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO); + FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced); // Unlike other cases, here the view is initialized with a set of previously synced documents // which happens when listening to a previously listened-to query. @@ -346,8 +371,8 @@ - (void)assertDocSet:(FSTDocumentSet *)docSet containsDocs:(NSArray *initialData = diff --git a/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.mm b/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.mm index 999238d7c5b..e84dd974c0f 100644 --- a/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.mm +++ b/Firestore/Example/Tests/Local/FSTLRUGarbageCollectorTests.mm @@ -220,11 +220,10 @@ - (DocumentKey)nextTestDocKey { - (FSTDocument *)nextTestDocumentWithValue:(FSTObjectValue *)value { DocumentKey key = [self nextTestDocKey]; FSTTestSnapshotVersion version = 2; - BOOL hasMutations = NO; return [FSTDocument documentWithData:value key:key version:testutil::Version(version) - hasLocalMutations:hasMutations]; + state:FSTDocumentStateSynced]; } - (FSTDocument *)nextTestDocument { @@ -612,7 +611,7 @@ - (void)testRemoveTargetsThenGC { FSTDocument *doc = [FSTDocument documentWithData:_testValue key:middleDocToUpdate version:testutil::Version(version) - hasLocalMutations:NO]; + state:FSTDocumentStateSynced]; [_documentCache addEntry:doc]; [self updateTargetInTransaction:middleTarget]; }); diff --git a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm index 7c80f5fb2b9..284bd2d12af 100644 --- a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm +++ b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm @@ -130,7 +130,7 @@ - (void)testEncodesMutationBatch { } - (void)testEncodesDocumentAsMaybeDocument { - FSTDocument *doc = FSTTestDoc("some/path", 42, @{@"foo" : @"bar"}, NO); + FSTDocument *doc = FSTTestDoc("some/path", 42, @{@"foo" : @"bar"}, FSTDocumentStateSynced); FSTPBMaybeDocument *maybeDocProto = [FSTPBMaybeDocument message]; maybeDocProto.document = [GCFSDocument message]; @@ -146,8 +146,23 @@ - (void)testEncodesDocumentAsMaybeDocument { XCTAssertEqualObjects(decoded, doc); } +- (void)testEncodesUnknownDocumentAsMaybeDocument { + FSTUnknownDocument *doc = FSTTestUnknownDoc("some/path", 42); + + FSTPBMaybeDocument *maybeDocProto = [FSTPBMaybeDocument message]; + maybeDocProto.unknownDocument = [FSTPBUnknownDocument message]; + maybeDocProto.unknownDocument.name = @"projects/p/databases/d/documents/some/path"; + maybeDocProto.unknownDocument.version.seconds = 0; + maybeDocProto.unknownDocument.version.nanos = 42000; + maybeDocProto.hasCommittedMutations = true; + + XCTAssertEqualObjects([self.serializer encodedMaybeDocument:doc], maybeDocProto); + FSTMaybeDocument *decoded = [self.serializer decodedMaybeDocument:maybeDocProto]; + XCTAssertEqualObjects(decoded, doc); +} + - (void)testEncodesDeletedDocumentAsMaybeDocument { - FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc("some/path", 42); + FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc("some/path", 42, false); FSTPBMaybeDocument *maybeDocProto = [FSTPBMaybeDocument message]; maybeDocProto.noDocument = [FSTPBNoDocument message]; diff --git a/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm index 2ed0f621369..99267b4bc07 100644 --- a/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm +++ b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm @@ -221,16 +221,19 @@ - (void)testHandlesSetMutation { if ([self isTestBaseClass]) return; [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); + FSTAssertChanged( + @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); + FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); [self acknowledgeMutationWithVersion:0]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, NO) ]); + FSTAssertChanged( + @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateCommittedMutations) ]); if ([self gcIsEager]) { // Nothing is pinning this anymore, as it has been acknowledged and there are no targets active. FSTAssertNotContains(@"foo/bar"); } else { - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, NO)); + FSTAssertContains( + FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateCommittedMutations)); } } @@ -238,83 +241,98 @@ - (void)testHandlesSetMutationThenDocument { if ([self isTestBaseClass]) return; [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); + FSTAssertChanged( + @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); + FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); FSTQuery *query = FSTTestQuery("foo"); TargetId targetID = [self allocateQuery:query]; - [self - applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, NO), - @[ @(targetID) ], @[])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES)); -} - -- (void)testHandlesAckThenRejectThenRemoteEvent { - if ([self isTestBaseClass]) return; - - // Start a query that requires acks to be held. - FSTQuery *query = FSTTestQuery("foo"); - TargetId targetID = [self allocateQuery:query]; - - [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); - - // The last seen version is zero, so this ack must be held. - [self acknowledgeMutationWithVersion:1]; - FSTAssertChanged(@[]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); - - [self writeMutation:FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"})]; - FSTAssertChanged(@[ FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, YES) ]); - FSTAssertContains(FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, YES)); - - [self rejectMutation]; - FSTAssertRemoved(@[ @"bar/baz" ]); - FSTAssertNotContains(@"bar/baz"); - - [self - applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, NO), - @[ @(targetID) ])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, NO) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, NO)); - FSTAssertNotContains(@"bar/baz"); -} - -- (void)testHandlesDeletedDocumentThenSetMutationThenAck { - if ([self isTestBaseClass]) return; - - FSTQuery *query = FSTTestQuery("foo"); - TargetId targetID = [self allocateQuery:query]; - - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2), @[ @(targetID) ], - @[])]; - FSTAssertRemoved(@[ @"foo/bar" ]); - // Under eager GC, there is no longer a reference for the document, and it should be - // deleted. - if (![self gcIsEager]) { - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 2)); - } else { - FSTAssertNotContains(@"foo/bar"); - } - - [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); - // Can now remove the target, since we have a mutation pinning the document - [self.localStore releaseQuery:query]; - // Verify we didn't lose anything - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); - - [self acknowledgeMutationWithVersion:3]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, NO) ]); - // It has been acknowledged, and should no longer be retained as there is no target and mutation - if ([self gcIsEager]) { - FSTAssertNotContains(@"foo/bar"); - } -} + [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, + FSTDocumentStateSynced), + @[ @(targetID) ], @[])]; + FSTAssertChanged( + @[ FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); + FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); +} + +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesAckThenRejectThenRemoteEvent { +// if ([self isTestBaseClass]) return; +// +// // Start a query that requires acks to be held. +// FSTQuery *query = FSTTestQuery("foo"); +// TargetId targetID = [self allocateQuery:query]; +// +// [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; +// FSTAssertChanged( +// @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); +// +// // The last seen version is zero, so this ack must be held. +// [self acknowledgeMutationWithVersion:1]; +// FSTAssertChanged(@[]); +// +// // Under eager GC, there is no longer a reference for the document, and it should be +// // deleted. +// if ([self gcIsEager]) { +// FSTAssertNotContains(@"foo/bar"); +// } else { +// FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, +// FSTDocumentStateCommittedMutations)); +// } +// +// [self writeMutation:FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"})]; +// FSTAssertChanged( +// @[ FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, FSTDocumentStateLocalMutations) ]); +// FSTAssertContains(FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, FSTDocumentStateLocalMutations)); +// +// [self rejectMutation]; +// FSTAssertRemoved(@[ @"bar/baz" ]); +// FSTAssertNotContains(@"bar/baz"); +// +// [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, +// FSTDocumentStateSynced), +// @[ @(targetID) ])]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, FSTDocumentStateSynced) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"it" : @"changed"}, FSTDocumentStateSynced)); +// FSTAssertNotContains(@"bar/baz"); +//} + +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesDeletedDocumentThenSetMutationThenAck { +// if ([self isTestBaseClass]) return; +// +// FSTQuery *query = FSTTestQuery("foo"); +// TargetId targetID = [self allocateQuery:query]; +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2, NO), +// @[ @(targetID) ], @[])]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// // Under eager GC, there is no longer a reference for the document, and it should be +// // deleted. +// if (![self gcIsEager]) { +// FSTAssertContains(FSTTestDeletedDoc("foo/bar", 2, NO)); +// } else { +// FSTAssertNotContains(@"foo/bar"); +// } +// +// [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; +// FSTAssertChanged( +// @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); +// // Can now remove the target, since we have a mutation pinning the document +// [self.localStore releaseQuery:query]; +// // Verify we didn't lose anything +// FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); +// +// [self acknowledgeMutationWithVersion:3]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateSynced) ]); +// // It has been acknowledged, and should no longer be retained as there is no target and mutation +// if ([self gcIsEager]) { +// FSTAssertNotContains(@"foo/bar"); +// } +//} - (void)testHandlesSetMutationThenDeletedDocument { if ([self isTestBaseClass]) return; @@ -323,41 +341,47 @@ - (void)testHandlesSetMutationThenDeletedDocument { TargetId targetID = [self allocateQuery:query]; [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); - - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2), @[ @(targetID) ], - @[])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); -} - -- (void)testHandlesDocumentThenSetMutationThenAckThenDocument { - if ([self isTestBaseClass]) return; - - // Start a query that requires acks to be held. - FSTQuery *query = FSTTestQuery("foo"); - TargetId targetID = [self allocateQuery:query]; + FSTAssertChanged( + @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); - [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, NO), - @[ @(targetID) ])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, NO) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, NO)); - - [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES)); - - [self acknowledgeMutationWithVersion:3]; - // we haven't seen the remote event yet, so the write is still held. - FSTAssertChanged(@[]); - FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, YES)); - - [self - applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO), - @[ @(targetID) ], @[])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO)); -} + [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2, NO), + @[ @(targetID) ], @[])]; + FSTAssertChanged( + @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); + FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); +} + +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesDocumentThenSetMutationThenAckThenDocument { +// if ([self isTestBaseClass]) return; +// +// // Start a query that requires acks to be held. +// FSTQuery *query = FSTTestQuery("foo"); +// TargetId targetID = [self allocateQuery:query]; +// +// [self applyRemoteEvent:FSTTestAddedRemoteEvent( +// FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, FSTDocumentStateSynced), +// @[ @(targetID) ])]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, FSTDocumentStateSynced) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"it" : @"base"}, FSTDocumentStateSynced)); +// +// [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})]; +// FSTAssertChanged( +// @[ FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); +// +// [self acknowledgeMutationWithVersion:3]; +// // we haven't seen the remote event yet, so the write is still held. +// FSTAssertChanged(@[]); +// FSTAssertContains( +// FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, FSTDocumentStateCommittedMutations)); +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, +// FSTDocumentStateSynced), +// @[ @(targetID) ], @[])]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, FSTDocumentStateSynced) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, FSTDocumentStateSynced)); +//} - (void)testHandlesPatchWithoutPriorDocument { if ([self isTestBaseClass]) return; @@ -367,65 +391,80 @@ - (void)testHandlesPatchWithoutPriorDocument { FSTAssertNotContains(@"foo/bar"); [self acknowledgeMutationWithVersion:1]; - FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertNotContains(@"foo/bar"); -} - -- (void)testHandlesPatchMutationThenDocumentThenAck { - if ([self isTestBaseClass]) return; - - [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; - FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertNotContains(@"foo/bar"); - - FSTQuery *query = FSTTestQuery("foo"); - TargetId targetID = [self allocateQuery:query]; - - [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO), - @[ @(targetID) ])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES)); - - [self acknowledgeMutationWithVersion:2]; - // We still haven't seen the remote events for the patch, so the local changes remain, and there - // are no changes - FSTAssertChanged(@[]); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES)); - - [self applyRemoteEvent:FSTTestUpdateRemoteEvent( - FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar", @"it" : @"base"}, NO), @[], - @[])]; - - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO)); + FSTAssertChanged(@[ FSTTestUnknownDoc("foo/bar", 1) ]); + if ([self gcIsEager]) { + FSTAssertNotContains(@"foo/bar"); + } else { + FSTAssertContains(FSTTestUnknownDoc("foo/bar", 1)); + } } -- (void)testHandlesPatchMutationThenAckThenDocument { - if ([self isTestBaseClass]) return; - - [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; - FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertNotContains(@"foo/bar"); - - [self acknowledgeMutationWithVersion:1]; - FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertNotContains(@"foo/bar"); - - FSTQuery *query = FSTTestQuery("foo"); - TargetId targetID = [self allocateQuery:query]; - - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO), - @[ @(targetID) ], @[])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO)); -} +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesPatchMutationThenDocumentThenAck { +// if ([self isTestBaseClass]) return; +// +// [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// FSTAssertNotContains(@"foo/bar"); +// +// FSTQuery *query = FSTTestQuery("foo"); +// TargetId targetID = [self allocateQuery:query]; +// +// [self applyRemoteEvent:FSTTestAddedRemoteEvent( +// FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced), +// @[ @(targetID) ])]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, +// FSTDocumentStateLocalMutations) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, +// FSTDocumentStateLocalMutations)); +// +// [self acknowledgeMutationWithVersion:2]; +// // We still haven't seen the remote events for the patch, so the local changes remain, and there +// // are no changes +// FSTAssertChanged(@[FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar", @"it" : @"base"}, +// FSTDocumentStateCommittedMutations)]); +// FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar", @"it" : @"base"}, +// FSTDocumentStateCommittedMutations)); +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent( +// FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar", @"it" : @"base"}, +// FSTDocumentStateSynced), +// @[], @[])]; +// +// FSTAssertChanged( +// @[ FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar", @"it" : @"base"}, FSTDocumentStateSynced) ]); +// FSTAssertContains( +// FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar", @"it" : @"base"}, FSTDocumentStateSynced)); +//} + +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesPatchMutationThenAckThenDocument { +// if ([self isTestBaseClass]) return; +// +// [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// FSTAssertNotContains(@"foo/bar"); +// +// [self acknowledgeMutationWithVersion:1]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// FSTAssertNotContains(@"foo/bar"); +// +// FSTQuery *query = FSTTestQuery("foo"); +// TargetId targetID = [self allocateQuery:query]; +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent( +// FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced), +// @[ @(targetID) ], @[])]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced)); +//} - (void)testHandlesDeleteMutationThenAck { if ([self isTestBaseClass]) return; [self writeMutation:FSTTestDeleteMutation(@"foo/bar")]; FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0)); + FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0, NO)); [self acknowledgeMutationWithVersion:1]; FSTAssertRemoved(@[ @"foo/bar" ]); @@ -435,31 +474,33 @@ - (void)testHandlesDeleteMutationThenAck { } } -- (void)testHandlesDocumentThenDeleteMutationThenAck { - if ([self isTestBaseClass]) return; - - FSTQuery *query = FSTTestQuery("foo"); - TargetId targetID = [self allocateQuery:query]; - - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO), - @[ @(targetID) ], @[])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO)); - - [self writeMutation:FSTTestDeleteMutation(@"foo/bar")]; - FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0)); - - // Remove the target so only the mutation is pinning the document - [self.localStore releaseQuery:query]; - - [self acknowledgeMutationWithVersion:2]; - FSTAssertRemoved(@[ @"foo/bar" ]); - if ([self gcIsEager]) { - // Neither the target nor the mutation pin the document, it should be gone. - FSTAssertNotContains(@"foo/bar"); - } -} +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesDocumentThenDeleteMutationThenAck { +// if ([self isTestBaseClass]) return; +// +// FSTQuery *query = FSTTestQuery("foo"); +// TargetId targetID = [self allocateQuery:query]; +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent( +// FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced), +// @[ @(targetID) ], @[])]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced)); +// +// [self writeMutation:FSTTestDeleteMutation(@"foo/bar")]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0, YES)); +// +// // Remove the target so only the mutation is pinning the document +// [self.localStore releaseQuery:query]; +// +// [self acknowledgeMutationWithVersion:2]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// if ([self gcIsEager]) { +// // Neither the target nor the mutation pin the document, it should be gone. +// FSTAssertNotContains(@"foo/bar"); +// } +//} - (void)testHandlesDeleteMutationThenDocumentThenAck { if ([self isTestBaseClass]) return; @@ -469,13 +510,14 @@ - (void)testHandlesDeleteMutationThenDocumentThenAck { [self writeMutation:FSTTestDeleteMutation(@"foo/bar")]; FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0)); + FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0, NO)); // Add the document to a target so it will remain in persistence even when ack'd - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO), - @[ @(targetID) ], @[])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEvent( + FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced), + @[ @(targetID) ], @[])]; FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0)); + FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0, NO)); // Don't need to keep it pinned anymore [self.localStore releaseQuery:query]; @@ -489,62 +531,71 @@ - (void)testHandlesDeleteMutationThenDocumentThenAck { } } -- (void)testHandlesDocumentThenDeletedDocumentThenDocument { - if ([self isTestBaseClass]) return; - - FSTQuery *query = FSTTestQuery("foo"); - TargetId targetID = [self allocateQuery:query]; - - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO), - @[ @(targetID) ], @[])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO)); - - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2), @[ @(targetID) ], - @[])]; - FSTAssertRemoved(@[ @"foo/bar" ]); - if (![self gcIsEager]) { - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 2)); - } - - [self - applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO), - @[ @(targetID) ], @[])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, NO)); -} - -- (void)testHandlesSetMutationThenPatchMutationThenDocumentThenAckThenAck { - if ([self isTestBaseClass]) return; - - [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, YES)); - - [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); - - FSTQuery *query = FSTTestQuery("foo"); - TargetId targetID = [self allocateQuery:query]; - - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, NO), - @[ @(targetID) ], @[])]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, YES)); - - [self.localStore releaseQuery:query]; - [self acknowledgeMutationWithVersion:2]; // delete mutation - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, YES)); - - [self acknowledgeMutationWithVersion:3]; // patch mutation - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO) ]); - if ([self gcIsEager]) { - // we've ack'd all of the mutations, nothing is keeping this pinned anymore - FSTAssertNotContains(@"foo/bar"); - } -} +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesDocumentThenDeletedDocumentThenDocument { +// if ([self isTestBaseClass]) return; +// +// FSTQuery *query = FSTTestQuery("foo"); +// TargetId targetID = [self allocateQuery:query]; +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent( +// FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced), +// @[ @(targetID) ], @[])]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced)); +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc("foo/bar", 2, NO), +// @[ @(targetID) ], @[])]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// if (![self gcIsEager]) { +// FSTAssertContains(FSTTestDeletedDoc("foo/bar", 2, NO)); +// } +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, +// FSTDocumentStateSynced), +// @[ @(targetID) ], @[])]; +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, FSTDocumentStateSynced) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 3, @{@"it" : @"changed"}, FSTDocumentStateSynced)); +//} + +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesSetMutationThenPatchMutationThenDocumentThenAckThenAck { +// if ([self isTestBaseClass]) return; +// +// [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})]; +// FSTAssertChanged( +// @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, FSTDocumentStateLocalMutations) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, FSTDocumentStateLocalMutations)); +// +// [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; +// FSTAssertChanged( +// @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); +// +// FSTQuery *query = FSTTestQuery("foo"); +// TargetId targetID = [self allocateQuery:query]; +// +// [self applyRemoteEvent:FSTTestUpdateRemoteEvent( +// FSTTestDoc("foo/bar", 1, @{@"it" : @"base"}, FSTDocumentStateSynced), +// @[ @(targetID) ], @[])]; +// FSTAssertChanged( +// @[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); +// FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); +// +// [self.localStore releaseQuery:query]; +// [self acknowledgeMutationWithVersion:2]; // delete mutation +// FSTAssertChanged( +// @[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateCommittedMutations) ]); +// FSTAssertContains( +// FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateCommittedMutations)); +// +// [self acknowledgeMutationWithVersion:3]; // patch mutation +// FSTAssertChanged(@[ FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateSynced) ]); +// if ([self gcIsEager]) { +// // we've ack'd all of the mutations, nothing is keeping this pinned anymore +// FSTAssertNotContains(@"foo/bar"); +// } +//} - (void)testHandlesSetMutationAndPatchMutationTogether { if ([self isTestBaseClass]) return; @@ -554,8 +605,9 @@ - (void)testHandlesSetMutationAndPatchMutationTogether { FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {}) ]]; - FSTAssertChanged(@[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); + FSTAssertChanged( + @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ]); + FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); } - (void)testHandlesSetMutationThenPatchMutationThenReject { @@ -563,7 +615,7 @@ - (void)testHandlesSetMutationThenPatchMutationThenReject { if (![self gcIsEager]) return; [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})]; - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, YES)); + FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, FSTDocumentStateLocalMutations)); [self acknowledgeMutationWithVersion:1]; FSTAssertNotContains(@"foo/bar"); @@ -585,46 +637,48 @@ - (void)testHandlesSetMutationsAndPatchMutationOfJustOneTogether { ]]; FSTAssertChanged((@[ - FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, YES), - FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) + FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, FSTDocumentStateLocalMutations), + FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations) ])); - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); - FSTAssertContains(FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, YES)); -} - -- (void)testHandlesDeleteMutationThenPatchMutationThenAckThenAck { - if ([self isTestBaseClass]) return; - - [self writeMutation:FSTTestDeleteMutation(@"foo/bar")]; - FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0)); - - [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; - FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0)); - - [self acknowledgeMutationWithVersion:2]; // delete mutation - FSTAssertRemoved(@[ @"foo/bar" ]); - FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0)); - - [self acknowledgeMutationWithVersion:3]; // patch mutation - FSTAssertRemoved(@[ @"foo/bar" ]); - if ([self gcIsEager]) { - // There are no more pending mutations, the doc has been dropped - FSTAssertNotContains(@"foo/bar"); - } -} + FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); + FSTAssertContains(FSTTestDoc("bar/baz", 0, @{@"bar" : @"baz"}, FSTDocumentStateLocalMutations)); +} + +// TODO(heldwriteacks): Uncomment this test after LocalStore changes +//- (void)testHandlesDeleteMutationThenPatchMutationThenAckThenAck { +// if ([self isTestBaseClass]) return; +// +// [self writeMutation:FSTTestDeleteMutation(@"foo/bar")]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0, NO)); +// +// [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; +// FSTAssertRemoved(@[ @"foo/bar" ]); +// FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0, NO)); +// +// [self acknowledgeMutationWithVersion:2]; // delete mutation +// FSTAssertRemoved(@[ @"foo/bar" ]); +// FSTAssertContains(FSTTestDeletedDoc("foo/bar", 0, YES)); +// +// [self acknowledgeMutationWithVersion:3]; // patch mutation +// FSTAssertRemoved(@[ @"foo/bar" ]); +// if ([self gcIsEager]) { +// // There are no more pending mutations, the doc has been dropped +// FSTAssertNotContains(@"foo/bar"); +// } +//} - (void)testCollectsGarbageAfterChangeBatchWithNoTargetIDs { if ([self isTestBaseClass]) return; if (![self gcIsEager]) return; - [self applyRemoteEvent:FSTTestUpdateRemoteEventWithLimboTargets(FSTTestDeletedDoc("foo/bar", 2), - @[], @[], @[ @1 ])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEventWithLimboTargets( + FSTTestDeletedDoc("foo/bar", 2, NO), @[], @[], @[ @1 ])]; FSTAssertNotContains(@"foo/bar"); [self applyRemoteEvent:FSTTestUpdateRemoteEventWithLimboTargets( - FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO), @[], @[], @[ @1 ])]; + FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, FSTDocumentStateSynced), + @[], @[], @[ @1 ])]; FSTAssertNotContains(@"foo/bar"); } @@ -635,12 +689,14 @@ - (void)testCollectsGarbageAfterChangeBatch { FSTQuery *query = FSTTestQuery("foo"); TargetId targetID = [self allocateQuery:query]; - [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO), - @[ @(targetID) ])]; - FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, NO)); + [self applyRemoteEvent:FSTTestAddedRemoteEvent( + FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, FSTDocumentStateSynced), + @[ @(targetID) ])]; + FSTAssertContains(FSTTestDoc("foo/bar", 2, @{@"foo" : @"bar"}, FSTDocumentStateSynced)); - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 2, @{@"foo" : @"baz"}, NO), - @[], @[ @(targetID) ])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEvent( + FSTTestDoc("foo/bar", 2, @{@"foo" : @"baz"}, FSTDocumentStateSynced), + @[], @[ @(targetID) ])]; FSTAssertNotContains(@"foo/bar"); } @@ -652,27 +708,28 @@ - (void)testCollectsGarbageAfterAcknowledgedMutation { FSTQuery *query = FSTTestQuery("foo"); TargetId targetID = [self allocateQuery:query]; - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, NO), - @[ @(targetID) ], @[])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEvent( + FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, FSTDocumentStateSynced), + @[ @(targetID) ], @[])]; [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; // Release the query so that our target count goes back to 0 and we are considered up-to-date. [self.localStore releaseQuery:query]; [self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})]; [self writeMutation:FSTTestDeleteMutation(@"foo/baz")]; - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); - FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, YES)); - FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0)); + FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); + FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, FSTDocumentStateLocalMutations)); + FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0, NO)); [self acknowledgeMutationWithVersion:3]; FSTAssertNotContains(@"foo/bar"); - FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, YES)); - FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0)); + FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, FSTDocumentStateLocalMutations)); + FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0, NO)); [self acknowledgeMutationWithVersion:4]; FSTAssertNotContains(@"foo/bar"); FSTAssertNotContains(@"foo/bah"); - FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0)); + FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0, NO)); [self acknowledgeMutationWithVersion:5]; FSTAssertNotContains(@"foo/bar"); @@ -687,27 +744,28 @@ - (void)testCollectsGarbageAfterRejectedMutation { FSTQuery *query = FSTTestQuery("foo"); TargetId targetID = [self allocateQuery:query]; - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, NO), - @[ @(targetID) ], @[])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEvent( + FSTTestDoc("foo/bar", 0, @{@"foo" : @"old"}, FSTDocumentStateSynced), + @[ @(targetID) ], @[])]; [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})]; // Release the query so that our target count goes back to 0 and we are considered up-to-date. [self.localStore releaseQuery:query]; [self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})]; [self writeMutation:FSTTestDeleteMutation(@"foo/baz")]; - FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES)); - FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, YES)); - FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0)); + FSTAssertContains(FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations)); + FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, FSTDocumentStateLocalMutations)); + FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0, NO)); [self rejectMutation]; // patch mutation FSTAssertNotContains(@"foo/bar"); - FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, YES)); - FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0)); + FSTAssertContains(FSTTestDoc("foo/bah", 0, @{@"foo" : @"bah"}, FSTDocumentStateLocalMutations)); + FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0, NO)); [self rejectMutation]; // set mutation FSTAssertNotContains(@"foo/bar"); FSTAssertNotContains(@"foo/bah"); - FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0)); + FSTAssertContains(FSTTestDeletedDoc("foo/baz", 0, NO)); [self rejectMutation]; // delete mutation FSTAssertNotContains(@"foo/bar"); @@ -722,23 +780,26 @@ - (void)testPinsDocumentsInTheLocalView { FSTQuery *query = FSTTestQuery("foo"); TargetId targetID = [self allocateQuery:query]; - [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO), - @[ @(targetID) ])]; + [self applyRemoteEvent:FSTTestAddedRemoteEvent( + FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateSynced), + @[ @(targetID) ])]; [self writeMutation:FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"})]; - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO)); - FSTAssertContains(FSTTestDoc("foo/baz", 0, @{@"foo" : @"baz"}, YES)); + FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateSynced)); + FSTAssertContains(FSTTestDoc("foo/baz", 0, @{@"foo" : @"baz"}, FSTDocumentStateLocalMutations)); [self notifyLocalViewChanges:FSTTestViewChanges(targetID, @[ @"foo/bar", @"foo/baz" ], @[])]; - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO)); - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO), - @[], @[ @(targetID) ])]; - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, NO), - @[ @(targetID) ], @[])]; - FSTAssertContains(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, YES)); + FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateSynced)); + [self applyRemoteEvent:FSTTestUpdateRemoteEvent( + FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateSynced), + @[], @[ @(targetID) ])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEvent( + FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, FSTDocumentStateSynced), + @[ @(targetID) ], @[])]; + FSTAssertContains(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, FSTDocumentStateLocalMutations)); [self acknowledgeMutationWithVersion:2]; - FSTAssertContains(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, NO)); - FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, NO)); - FSTAssertContains(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, NO)); + FSTAssertContains(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, FSTDocumentStateSynced)); + FSTAssertContains(FSTTestDoc("foo/bar", 1, @{@"foo" : @"bar"}, FSTDocumentStateSynced)); + FSTAssertContains(FSTTestDoc("foo/baz", 2, @{@"foo" : @"baz"}, FSTDocumentStateSynced)); [self notifyLocalViewChanges:FSTTestViewChanges(targetID, @[], @[ @"foo/bar", @"foo/baz" ])]; [self.localStore releaseQuery:query]; @@ -752,8 +813,9 @@ - (void)testThrowsAwayDocumentsWithUnknownTargetIDsImmediately { if (![self gcIsEager]) return; TargetId targetID = 321; - [self applyRemoteEvent:FSTTestUpdateRemoteEventWithLimboTargets(FSTTestDoc("foo/bar", 1, @{}, NO), - @[], @[], @[ @(targetID) ])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEventWithLimboTargets( + FSTTestDoc("foo/bar", 1, @{}, FSTDocumentStateSynced), @[], @[], + @[ @(targetID) ])]; FSTAssertNotContains(@"foo/bar"); } @@ -768,7 +830,8 @@ - (void)testCanExecuteDocumentQueries { ]]; FSTQuery *query = FSTTestQuery("foo/bar"); FSTDocumentDictionary *docs = [self.localStore executeQuery:query]; - XCTAssertEqualObjects([docs values], @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES) ]); + XCTAssertEqualObjects([docs values], @[ FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, + FSTDocumentStateLocalMutations) ]); } - (void)testCanExecuteCollectionQueries { @@ -783,10 +846,11 @@ - (void)testCanExecuteCollectionQueries { ]]; FSTQuery *query = FSTTestQuery("foo"); FSTDocumentDictionary *docs = [self.localStore executeQuery:query]; - XCTAssertEqualObjects([docs values], (@[ - FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, YES), - FSTTestDoc("foo/baz", 0, @{@"foo" : @"baz"}, YES) - ])); + XCTAssertEqualObjects( + [docs values], (@[ + FSTTestDoc("foo/bar", 0, @{@"foo" : @"bar"}, FSTDocumentStateLocalMutations), + FSTTestDoc("foo/baz", 0, @{@"foo" : @"baz"}, FSTDocumentStateLocalMutations) + ])); } - (void)testCanExecuteMixedCollectionQueries { @@ -796,18 +860,20 @@ - (void)testCanExecuteMixedCollectionQueries { [self allocateQuery:query]; FSTAssertTargetID(2); - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, NO), - @[ @2 ], @[])]; - [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, NO), - @[ @2 ], @[])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEvent( + FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, FSTDocumentStateSynced), + @[ @2 ], @[])]; + [self applyRemoteEvent:FSTTestUpdateRemoteEvent( + FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, FSTDocumentStateSynced), + @[ @2 ], @[])]; [self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]]; FSTDocumentDictionary *docs = [self.localStore executeQuery:query]; XCTAssertEqualObjects([docs values], (@[ - FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, NO), - FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, NO), - FSTTestDoc("foo/bonk", 0, @{@"a" : @"b"}, YES) + FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, FSTDocumentStateSynced), + FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, FSTDocumentStateSynced), + FSTTestDoc("foo/bonk", 0, @{@"a" : @"b"}, FSTDocumentStateLocalMutations) ])); } @@ -856,10 +922,12 @@ - (void)testRemoteDocumentKeysForTarget { [self allocateQuery:query]; FSTAssertTargetID(2); - [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, NO), - @[ @2 ])]; - [self applyRemoteEvent:FSTTestAddedRemoteEvent(FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, NO), - @[ @2 ])]; + [self applyRemoteEvent:FSTTestAddedRemoteEvent( + FSTTestDoc("foo/baz", 10, @{@"a" : @"b"}, FSTDocumentStateSynced), + @[ @2 ])]; + [self applyRemoteEvent:FSTTestAddedRemoteEvent( + FSTTestDoc("foo/bar", 20, @{@"a" : @"b"}, FSTDocumentStateSynced), + @[ @2 ])]; [self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]]; diff --git a/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.mm b/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.mm index 6e2369fdcb0..4714aa4c8dd 100644 --- a/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.mm +++ b/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.mm @@ -85,7 +85,7 @@ - (void)testSetAndReadDeletedDocument { if (!self.remoteDocumentCache) return; self.persistence.run("testSetAndReadDeletedDocument", [&]() { - FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc(kDocPath, kVersion); + FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc(kDocPath, kVersion, NO); [self.remoteDocumentCache addEntry:deletedDoc]; XCTAssertEqualObjects([self.remoteDocumentCache entryForKey:testutil::Key(kDocPath)], @@ -98,7 +98,7 @@ - (void)testSetDocumentToNewValue { self.persistence.run("testSetDocumentToNewValue", [&]() { [self setTestDocumentAtPath:kDocPath]; - FSTDocument *newDoc = FSTTestDoc(kDocPath, kVersion, @{@"data" : @2}, NO); + FSTDocument *newDoc = FSTTestDoc(kDocPath, kVersion, @{@"data" : @2}, FSTDocumentStateSynced); [self.remoteDocumentCache addEntry:newDoc]; XCTAssertEqualObjects([self.remoteDocumentCache entryForKey:testutil::Key(kDocPath)], newDoc); }); @@ -138,8 +138,10 @@ - (void)testDocumentsMatchingQuery { FSTQuery *query = FSTTestQuery("b"); FSTDocumentDictionary *results = [self.remoteDocumentCache documentsMatchingQuery:query]; - NSArray *expected = - @[ FSTTestDoc("b/1", kVersion, _kDocData, NO), FSTTestDoc("b/2", kVersion, _kDocData, NO) ]; + NSArray *expected = @[ + FSTTestDoc("b/1", kVersion, _kDocData, FSTDocumentStateSynced), + FSTTestDoc("b/2", kVersion, _kDocData, FSTDocumentStateSynced) + ]; XCTAssertEqual([results count], [expected count]); for (FSTDocument *doc in expected) { XCTAssertEqualObjects([results objectForKey:doc.key], doc); @@ -151,7 +153,7 @@ - (void)testDocumentsMatchingQuery { // TODO(gsoltis): reevaluate if any of these helpers are still needed - (FSTDocument *)setTestDocumentAtPath:(const absl::string_view)path { - FSTDocument *doc = FSTTestDoc(path, kVersion, _kDocData, NO); + FSTDocument *doc = FSTTestDoc(path, kVersion, _kDocData, FSTDocumentStateSynced); [self.remoteDocumentCache addEntry:doc]; return doc; } diff --git a/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm b/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm index 566ef2e6be7..05be1b7eeab 100644 --- a/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm +++ b/Firestore/Example/Tests/Model/FSTDocumentSetTests.mm @@ -38,9 +38,9 @@ - (void)setUp { [super setUp]; _comp = FSTTestDocComparator("sort"); - _doc1 = FSTTestDoc("docs/1", 0, @{@"sort" : @2}, NO); - _doc2 = FSTTestDoc("docs/2", 0, @{@"sort" : @3}, NO); - _doc3 = FSTTestDoc("docs/3", 0, @{@"sort" : @1}, NO); + _doc1 = FSTTestDoc("docs/1", 0, @{@"sort" : @2}, FSTDocumentStateSynced); + _doc2 = FSTTestDoc("docs/2", 0, @{@"sort" : @3}, FSTDocumentStateSynced); + _doc3 = FSTTestDoc("docs/3", 0, @{@"sort" : @1}, FSTDocumentStateSynced); } - (void)testCount { @@ -97,7 +97,7 @@ - (void)testDeletes { - (void)testUpdates { FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, _doc2, _doc3 ]); - FSTDocument *doc2Prime = FSTTestDoc("docs/2", 0, @{@"sort" : @9}, NO); + FSTDocument *doc2Prime = FSTTestDoc("docs/2", 0, @{@"sort" : @9}, FSTDocumentStateSynced); set = [set documentSetByAddingDocument:doc2Prime]; XCTAssertEqual([set count], 3); @@ -106,7 +106,7 @@ - (void)testUpdates { } - (void)testAddsDocsWithEqualComparisonValues { - FSTDocument *doc4 = FSTTestDoc("docs/4", 0, @{@"sort" : @2}, NO); + FSTDocument *doc4 = FSTTestDoc("docs/4", 0, @{@"sort" : @2}, FSTDocumentStateSynced); FSTDocumentSet *set = FSTTestDocSet(_comp, @[ _doc1, doc4 ]); XCTAssertEqualObjects([[set documentEnumerator] allObjects], (@[ _doc1, doc4 ])); diff --git a/Firestore/Example/Tests/Model/FSTDocumentTests.mm b/Firestore/Example/Tests/Model/FSTDocumentTests.mm index db00fd3cfbc..7867396c3a1 100644 --- a/Firestore/Example/Tests/Model/FSTDocumentTests.mm +++ b/Firestore/Example/Tests/Model/FSTDocumentTests.mm @@ -41,7 +41,7 @@ - (void)testConstructor { SnapshotVersion version = testutil::Version(1); FSTObjectValue *data = FSTTestObjectValue(@{@"a" : @1}); FSTDocument *doc = - [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO]; + [FSTDocument documentWithData:data key:key version:version state:FSTDocumentStateSynced]; XCTAssertEqualObjects(doc.key, FSTTestDocKey(@"messages/first")); XCTAssertEqual(doc.version, version); @@ -57,7 +57,7 @@ - (void)testExtractsFields { @"owner" : @{@"name" : @"Jonny", @"title" : @"scallywag"} }); FSTDocument *doc = - [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO]; + [FSTDocument documentWithData:data key:key version:version state:FSTDocumentStateSynced]; XCTAssertEqualObjects([doc fieldForPath:testutil::Field("desc")], [FSTStringValue stringValue:@"Discuss all the project related stuff"]); @@ -67,30 +67,30 @@ - (void)testExtractsFields { - (void)testIsEqual { XCTAssertEqualObjects(FSTTestDoc("messages/first", 1, - @{@"a" : @1}, NO), + @{@"a" : @1}, FSTDocumentStateSynced), FSTTestDoc("messages/first", 1, - @{@"a" : @1}, NO)); + @{@"a" : @1}, FSTDocumentStateSynced)); XCTAssertNotEqualObjects(FSTTestDoc("messages/first", 1, - @{@"a" : @1}, NO), + @{@"a" : @1}, FSTDocumentStateSynced), FSTTestDoc("messages/first", 1, - @{@"b" : @1}, NO)); + @{@"b" : @1}, FSTDocumentStateSynced)); XCTAssertNotEqualObjects(FSTTestDoc("messages/first", 1, - @{@"a" : @1}, NO), + @{@"a" : @1}, FSTDocumentStateSynced), FSTTestDoc("messages/second", 1, - @{@"b" : @1}, NO)); + @{@"b" : @1}, FSTDocumentStateSynced)); XCTAssertNotEqualObjects(FSTTestDoc("messages/first", 1, - @{@"a" : @1}, NO), + @{@"a" : @1}, FSTDocumentStateSynced), FSTTestDoc("messages/first", 2, - @{@"a" : @1}, NO)); + @{@"a" : @1}, FSTDocumentStateSynced)); XCTAssertNotEqualObjects(FSTTestDoc("messages/first", 1, - @{@"a" : @1}, NO), + @{@"a" : @1}, FSTDocumentStateSynced), FSTTestDoc("messages/first", 1, - @{@"a" : @1}, YES)); + @{@"a" : @1}, FSTDocumentStateLocalMutations)); XCTAssertEqualObjects(FSTTestDoc("messages/first", 1, - @{@"a" : @1}, YES), + @{@"a" : @1}, FSTDocumentStateLocalMutations), FSTTestDoc("messages/first", 1, - @{@"a" : @1}, 5)); + @{@"a" : @1}, FSTDocumentStateLocalMutations)); } @end diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.mm b/Firestore/Example/Tests/Model/FSTMutationTests.mm index 4d00c16ef5d..878668fe817 100644 --- a/Firestore/Example/Tests/Model/FSTMutationTests.mm +++ b/Firestore/Example/Tests/Model/FSTMutationTests.mm @@ -57,31 +57,33 @@ - (void)setUp { - (void)testAppliesSetsToDocuments { NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"}); FSTMaybeDocument *setDoc = [set applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{@"bar" : @"bar-value"}; - XCTAssertEqualObjects(setDoc, FSTTestDoc("collection/key", 0, expectedData, YES)); + XCTAssertEqualObjects( + setDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations)); } - (void)testAppliesPatchesToDocuments { NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {}); FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{@"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value"}; - XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES)); + XCTAssertEqualObjects( + patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations)); } - (void)testDeletesValuesFromTheFieldMask { NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"}}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); DocumentKey key = testutil::Key("collection/key"); FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key @@ -92,23 +94,25 @@ - (void)testDeletesValuesFromTheFieldMask { [patch applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{@"foo" : @{@"baz" : @"baz-value"}}; - XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES)); + XCTAssertEqualObjects( + patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations)); } - (void)testPatchesPrimitiveValue { NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {}); FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; NSDictionary *expectedData = @{@"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value"}; - XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES)); + XCTAssertEqualObjects( + patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations)); } - (void)testPatchingDeletedDocumentsDoesNothing { - FSTMaybeDocument *baseDoc = FSTTestDeletedDoc("collection/key", 0); + FSTMaybeDocument *baseDoc = FSTTestDeletedDoc("collection/key", 0, NO); FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"bar"}, {}); FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; @@ -117,7 +121,7 @@ - (void)testPatchingDeletedDocumentsDoesNothing { - (void)testAppliesLocalServerTimestampTransformToDocuments { NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *transform = FSTTestTransformMutation( @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]}); @@ -137,7 +141,7 @@ - (void)testAppliesLocalServerTimestampTransformToDocuments { FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData key:FSTTestDocKey(@"collection/key") version:testutil::Version(0) - hasLocalMutations:YES]; + state:FSTDocumentStateLocalMutations]; XCTAssertEqualObjects(transformedDoc, expectedDoc); } @@ -293,7 +297,7 @@ - (void)testAppliesLocalArrayRemoveTransformWithNonPrimitiveElements { - (void)transformBaseDoc:(NSDictionary *)baseData with:(NSDictionary *)transformData expecting:(NSDictionary *)expectedData { - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, baseData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, baseData, FSTDocumentStateSynced); FSTMutation *transform = FSTTestTransformMutation(@"collection/key", transformData); @@ -303,14 +307,14 @@ - (void)transformBaseDoc:(NSDictionary *)baseData FSTDocument *expectedDoc = [FSTDocument documentWithData:FSTTestObjectValue(expectedData) key:FSTTestDocKey(@"collection/key") version:testutil::Version(0) - hasLocalMutations:YES]; + state:FSTDocumentStateLocalMutations]; XCTAssertEqualObjects(transformedDoc, expectedDoc); } - (void)testAppliesServerAckedServerTimestampTransformToDocuments { NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *transform = FSTTestTransformMutation( @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]}); @@ -323,12 +327,13 @@ - (void)testAppliesServerAckedServerTimestampTransformToDocuments { [transform applyToRemoteDocument:baseDoc mutationResult:mutationResult]; NSDictionary *expectedData = @{@"foo" : @{@"bar" : _timestamp.dateValue}, @"baz" : @"baz-value"}; - XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 0, expectedData, NO)); + XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 1, expectedData, + FSTDocumentStateCommittedMutations)); } - (void)testAppliesServerAckedArrayTransformsToDocuments { NSDictionary *docData = @{@"array_1" : @[ @1, @2 ], @"array_2" : @[ @"a", @"b" ]}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @{ @"array_1" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @3 ]], @@ -344,22 +349,23 @@ - (void)testAppliesServerAckedArrayTransformsToDocuments { [transform applyToRemoteDocument:baseDoc mutationResult:mutationResult]; NSDictionary *expectedData = @{@"array_1" : @[ @1, @2, @3 ], @"array_2" : @[ @"b" ]}; - XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 0, expectedData, NO)); + XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 1, expectedData, + FSTDocumentStateCommittedMutations)); } - (void)testDeleteDeletes { NSDictionary *docData = @{@"foo" : @"bar"}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key"); FSTMaybeDocument *result = [mutation applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; - XCTAssertEqualObjects(result, FSTTestDeletedDoc("collection/key", 0)); + XCTAssertEqualObjects(result, FSTTestDeletedDoc("collection/key", 0, NO)); } - (void)testSetWithMutationResult { NSDictionary *docData = @{@"foo" : @"bar"}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"}); FSTMutationResult *mutationResult = @@ -367,12 +373,13 @@ - (void)testSetWithMutationResult { FSTMaybeDocument *setDoc = [set applyToRemoteDocument:baseDoc mutationResult:mutationResult]; NSDictionary *expectedData = @{@"foo" : @"new-bar"}; - XCTAssertEqualObjects(setDoc, FSTTestDoc("collection/key", 0, expectedData, NO)); + XCTAssertEqualObjects( + setDoc, FSTTestDoc("collection/key", 4, expectedData, FSTDocumentStateCommittedMutations)); } - (void)testPatchWithMutationResult { NSDictionary *docData = @{@"foo" : @"bar"}; - FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); + FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced); FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"new-bar"}, {}); FSTMutationResult *mutationResult = @@ -381,43 +388,53 @@ - (void)testPatchWithMutationResult { [patch applyToRemoteDocument:baseDoc mutationResult:mutationResult]; NSDictionary *expectedData = @{@"foo" : @"new-bar"}; - XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, NO)); + XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 4, expectedData, + FSTDocumentStateCommittedMutations)); } -#define ASSERT_VERSION_TRANSITION(mutation, base, expected) \ - do { \ - FSTMutationResult *mutationResult = \ - [[FSTMutationResult alloc] initWithVersion:testutil::Version(0) transformResults:nil]; \ - FSTMaybeDocument *actual = \ - [mutation applyToRemoteDocument:base mutationResult:mutationResult]; \ - XCTAssertEqualObjects(actual, expected); \ +#define ASSERT_VERSION_TRANSITION(mutation, base, result, expected) \ + do { \ + FSTMaybeDocument *actual = [mutation applyToRemoteDocument:base mutationResult:result]; \ + XCTAssertEqualObjects(actual, expected); \ } while (0); /** * Tests the transition table documented in FSTMutation.h. */ - (void)testTransitions { - FSTDocument *docV0 = FSTTestDoc("collection/key", 0, @{}, NO); - FSTDeletedDocument *deletedV0 = FSTTestDeletedDoc("collection/key", 0); - - FSTDocument *docV3 = FSTTestDoc("collection/key", 3, @{}, NO); - FSTDeletedDocument *deletedV3 = FSTTestDeletedDoc("collection/key", 3); + FSTDocument *docV3 = FSTTestDoc("collection/key", 3, @{}, FSTDocumentStateSynced); + FSTDeletedDocument *deletedV3 = FSTTestDeletedDoc("collection/key", 3, NO); FSTMutation *setMutation = FSTTestSetMutation(@"collection/key", @{}); - FSTMutation *patchMutation = FSTTestPatchMutation("collection/key", {}, {}); + FSTMutation *patchMutation = FSTTestPatchMutation("collection/key", @{}, {}); + FSTMutation *transformMutation = FSTTestTransformMutation(@"collection/key", @{}); FSTMutation *deleteMutation = FSTTestDeleteMutation(@"collection/key"); - ASSERT_VERSION_TRANSITION(setMutation, docV3, docV3); - ASSERT_VERSION_TRANSITION(setMutation, deletedV3, docV0); - ASSERT_VERSION_TRANSITION(setMutation, nil, docV0); + FSTDeletedDocument *docV7Deleted = FSTTestDeletedDoc("collection/key", 7, YES); + FSTDocument *docV7Committed = + FSTTestDoc("collection/key", 7, @{}, FSTDocumentStateCommittedMutations); + FSTUnknownDocument *docV7Unknown = FSTTestUnknownDoc("collection/key", 7); + + FSTMutationResult *mutationResult = + [[FSTMutationResult alloc] initWithVersion:testutil::Version(7) transformResults:nil]; + FSTMutationResult *transformResult = + [[FSTMutationResult alloc] initWithVersion:testutil::Version(7) transformResults:@[]]; + + ASSERT_VERSION_TRANSITION(setMutation, docV3, mutationResult, docV7Committed); + ASSERT_VERSION_TRANSITION(setMutation, deletedV3, mutationResult, docV7Committed); + ASSERT_VERSION_TRANSITION(setMutation, nil, mutationResult, docV7Committed); + + ASSERT_VERSION_TRANSITION(patchMutation, docV3, mutationResult, docV7Committed); + ASSERT_VERSION_TRANSITION(patchMutation, deletedV3, mutationResult, docV7Unknown); + ASSERT_VERSION_TRANSITION(patchMutation, nil, mutationResult, docV7Unknown); - ASSERT_VERSION_TRANSITION(patchMutation, docV3, docV3); - ASSERT_VERSION_TRANSITION(patchMutation, deletedV3, deletedV3); - ASSERT_VERSION_TRANSITION(patchMutation, nil, nil); + ASSERT_VERSION_TRANSITION(transformMutation, docV3, transformResult, docV7Committed); + ASSERT_VERSION_TRANSITION(transformMutation, deletedV3, transformResult, docV7Unknown); + ASSERT_VERSION_TRANSITION(transformMutation, nil, transformResult, docV7Unknown); - ASSERT_VERSION_TRANSITION(deleteMutation, docV3, deletedV0); - ASSERT_VERSION_TRANSITION(deleteMutation, deletedV3, deletedV0); - ASSERT_VERSION_TRANSITION(deleteMutation, nil, deletedV0); + ASSERT_VERSION_TRANSITION(deleteMutation, docV3, mutationResult, docV7Deleted); + ASSERT_VERSION_TRANSITION(deleteMutation, deletedV3, mutationResult, docV7Deleted); + ASSERT_VERSION_TRANSITION(deleteMutation, nil, mutationResult, docV7Deleted); } #undef ASSERT_TRANSITION diff --git a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm index 8c9d1333e72..948cafb8fd6 100644 --- a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm +++ b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm @@ -179,13 +179,13 @@ - (void)testWillAccumulateDocumentAddedAndRemovedEvents { NSDictionary *targetMap = [self queryDataForTargets:@[ @1, @2, @3, @4, @5, @6 ]]; - FSTDocument *existingDoc = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *existingDoc = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2, @3 ] removedTargetIDs:@[ @4, @5, @6 ] documentKey:existingDoc.key document:existingDoc]; - FSTDocument *newDoc = FSTTestDoc("docs/2", 2, @{@"value" : @2}, NO); + FSTDocument *newDoc = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced); FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @4 ] removedTargetIDs:@[ @2, @6 ] documentKey:newDoc.key @@ -238,7 +238,7 @@ - (void)testWillAccumulateDocumentAddedAndRemovedEvents { - (void)testWillIgnoreEventsForPendingTargets { NSDictionary *targetMap = [self queryDataForTargets:@[ @1 ]]; - FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc1.key @@ -252,7 +252,7 @@ - (void)testWillIgnoreEventsForPendingTargets { targetIDs:@[ @1 ] cause:nil]; - FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, NO); + FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced); FSTWatchChange *change4 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc2.key @@ -279,7 +279,7 @@ - (void)testWillIgnoreEventsForPendingTargets { - (void)testWillIgnoreEventsForRemovedTargets { NSDictionary *targetMap = [self queryDataForTargets:@[]]; - FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc1.key @@ -308,7 +308,7 @@ - (void)testWillIgnoreEventsForRemovedTargets { - (void)testWillKeepResetMappingEvenWithUpdates { NSDictionary *targetMap = [self queryDataForTargets:@[ @1 ]]; - FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc1.key @@ -319,13 +319,13 @@ - (void)testWillKeepResetMappingEvenWithUpdates { cause:nil]; // Add doc2, doc3 - FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, NO); + FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced); FSTWatchChange *change3 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc2.key document:doc2]; - FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{@"value" : @3}, NO); + FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{@"value" : @3}, FSTDocumentStateSynced); FSTWatchChange *change4 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc3.key @@ -387,13 +387,13 @@ - (void)testWillHandleTargetAddAndRemovalInSameBatch { NSDictionary *targetMap = [self queryDataForTargets:@[ @1, @2 ]]; - FSTDocument *doc1a = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *doc1a = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[ @2 ] documentKey:doc1a.key document:doc1a]; - FSTDocument *doc1b = FSTTestDoc("docs/1", 1, @{@"value" : @2}, NO); + FSTDocument *doc1b = FSTTestDoc("docs/1", 1, @{@"value" : @2}, FSTDocumentStateSynced); FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @2 ] removedTargetIDs:@[ @1 ] documentKey:doc1b.key @@ -444,7 +444,7 @@ - (void)testTargetAddedChangeWillResetPreviousState { NSDictionary *targetMap = [self queryDataForTargets:@[ @1, @3 ]]; - FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @3 ] removedTargetIDs:@[ @2 ] documentKey:doc1.key @@ -466,7 +466,7 @@ - (void)testTargetAddedChangeWillResetPreviousState { targetIDs:@[ @1 ] cause:nil]; - FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, NO); + FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced); FSTWatchChange *change6 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[ @3 ] documentKey:doc2.key @@ -531,13 +531,13 @@ - (void)testExistenceFilterMismatchClearsTarget { NSDictionary *targetMap = [self queryDataForTargets:@[ @1, @2 ]]; - FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc1.key document:doc1]; - FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, NO); + FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced); FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc2.key @@ -602,7 +602,7 @@ - (void)testExistenceFilterMismatchRemovesCurrentChanges { resumeToken:_resumeToken1]; [aggregator handleTargetChange:markCurrent]; - FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTDocumentWatchChange *addDoc = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc1.key @@ -633,13 +633,13 @@ - (void)testExistenceFilterMismatchRemovesCurrentChanges { - (void)testDocumentUpdate { NSDictionary *targetMap = [self queryDataForTargets:@[ @1 ]]; - FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, NO); + FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced); FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc1.key document:doc1]; - FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, NO); + FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced); FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:doc2.key @@ -660,8 +660,9 @@ - (void)testDocumentUpdate { [_targetMetadataProvider setSyncedKeys:DocumentKeySet{doc1.key, doc2.key} forQueryData:targetMap[@1]]; - FSTDeletedDocument *deletedDoc1 = - [FSTDeletedDocument documentWithKey:doc1.key version:testutil::Version(3)]; + FSTDeletedDocument *deletedDoc1 = [FSTDeletedDocument documentWithKey:doc1.key + version:testutil::Version(3) + hasCommittedMutations:NO]; FSTDocumentWatchChange *change3 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[] removedTargetIDs:@[ @1 ] @@ -669,7 +670,7 @@ - (void)testDocumentUpdate { document:deletedDoc1]; [aggregator handleDocumentChange:change3]; - FSTDocument *updatedDoc2 = FSTTestDoc("docs/2", 3, @{@"value" : @2}, NO); + FSTDocument *updatedDoc2 = FSTTestDoc("docs/2", 3, @{@"value" : @2}, FSTDocumentStateSynced); FSTDocumentWatchChange *change4 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] @@ -677,7 +678,7 @@ - (void)testDocumentUpdate { document:updatedDoc2]; [aggregator handleDocumentChange:change4]; - FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{@"value" : @3}, NO); + FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{@"value" : @3}, FSTDocumentStateSynced); FSTDocumentWatchChange *change5 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] @@ -795,8 +796,9 @@ - (void)testSynthesizeDeletes { existingKeys:DocumentKeySet {} changes:@[ resolveLimboTarget ]]; - FSTDeletedDocument *expected = - [FSTDeletedDocument documentWithKey:limboKey version:event.snapshotVersion]; + FSTDeletedDocument *expected = [FSTDeletedDocument documentWithKey:limboKey + version:event.snapshotVersion + hasCommittedMutations:NO]; XCTAssertEqualObjects(event.documentUpdates.at(limboKey), expected); XCTAssertTrue(event.limboDocumentChanges.contains(limboKey)); } @@ -840,27 +842,28 @@ - (void)testSeparatesDocumentUpdates { NSDictionary *targetMap = [self queryDataForLimboTargets:@[ @1 ]]; - FSTDocument *newDoc = FSTTestDoc("docs/new", 1, @{@"key" : @"value"}, NO); + FSTDocument *newDoc = FSTTestDoc("docs/new", 1, @{@"key" : @"value"}, FSTDocumentStateSynced); FSTWatchChange *newDocChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:newDoc.key document:newDoc]; - FSTDocument *existingDoc = FSTTestDoc("docs/existing", 1, @{@"some" : @"data"}, NO); + FSTDocument *existingDoc = + FSTTestDoc("docs/existing", 1, @{@"some" : @"data"}, FSTDocumentStateSynced); FSTWatchChange *existingDocChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ] removedTargetIDs:@[] documentKey:existingDoc.key document:existingDoc]; - FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc("docs/deleted", 1); + FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc("docs/deleted", 1, NO); FSTWatchChange *deletedDocChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[] removedTargetIDs:@[ @1 ] documentKey:deletedDoc.key document:deletedDoc]; - FSTDeletedDocument *missingDoc = FSTTestDeletedDoc("docs/missing", 1); + FSTDeletedDocument *missingDoc = FSTTestDeletedDoc("docs/missing", 1, NO); FSTWatchChange *missingDocChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[] removedTargetIDs:@[ @1 ] @@ -890,9 +893,9 @@ - (void)testTracksLimboDocuments { [targetMap addEntriesFromDictionary:[self queryDataForLimboTargets:@[ @2 ]]]; // 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); + FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"key" : @"value"}, FSTDocumentStateSynced); + FSTDocument *doc2 = FSTTestDoc("docs/2", 1, @{@"key" : @"value"}, FSTDocumentStateSynced); + FSTDocument *doc3 = FSTTestDoc("docs/3", 1, @{@"key" : @"value"}, FSTDocumentStateSynced); // Target 2 is a limbo target FSTWatchChange *docChange1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2 ] diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm index a705d8b88ed..017e5857b08 100644 --- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm +++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm @@ -832,7 +832,7 @@ - (void)testConvertsDocumentChangeWithTargetIds { initWithUpdatedTargetIDs:@[ @1, @2 ] removedTargetIDs:@[] documentKey:FSTTestDocKey(@"coll/1") - document:FSTTestDoc("coll/1", 5, @{@"foo" : @"bar"}, NO)]; + document:FSTTestDoc("coll/1", 5, @{@"foo" : @"bar"}, FSTDocumentStateSynced)]; GCFSListenResponse *listenResponse = [GCFSListenResponse message]; listenResponse.documentChange.document.name = @"projects/p/databases/d/documents/coll/1"; listenResponse.documentChange.document.updateTime.nanos = 5000; @@ -851,7 +851,7 @@ - (void)testConvertsDocumentChangeWithRemovedTargetIds { initWithUpdatedTargetIDs:@[ @2 ] removedTargetIDs:@[ @1 ] documentKey:FSTTestDocKey(@"coll/1") - document:FSTTestDoc("coll/1", 5, @{@"foo" : @"bar"}, NO)]; + document:FSTTestDoc("coll/1", 5, @{@"foo" : @"bar"}, FSTDocumentStateSynced)]; GCFSListenResponse *listenResponse = [GCFSListenResponse message]; listenResponse.documentChange.document.name = @"projects/p/databases/d/documents/coll/1"; listenResponse.documentChange.document.updateTime.nanos = 5000; @@ -870,7 +870,7 @@ - (void)testConvertsDocumentChangeWithDeletions { [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[] removedTargetIDs:@[ @1, @2 ] documentKey:FSTTestDocKey(@"coll/1") - document:FSTTestDeletedDoc("coll/1", 5)]; + document:FSTTestDeletedDoc("coll/1", 5, NO)]; GCFSListenResponse *listenResponse = [GCFSListenResponse message]; listenResponse.documentDelete.document = @"projects/p/databases/d/documents/coll/1"; listenResponse.documentDelete.readTime.nanos = 5000; diff --git a/Firestore/Example/Tests/Remote/FSTWatchChangeTests.mm b/Firestore/Example/Tests/Remote/FSTWatchChangeTests.mm index d707e3cc987..99017ffb354 100644 --- a/Firestore/Example/Tests/Remote/FSTWatchChangeTests.mm +++ b/Firestore/Example/Tests/Remote/FSTWatchChangeTests.mm @@ -32,7 +32,7 @@ @interface FSTWatchChangeTests : XCTestCase @implementation FSTWatchChangeTests - (void)testDocumentChange { - FSTMaybeDocument *doc = FSTTestDoc("a/b", 1, @{}, NO); + FSTMaybeDocument *doc = FSTTestDoc("a/b", 1, @{}, FSTDocumentStateSynced); FSTDocumentWatchChange *change = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2, @3 ] removedTargetIDs:@[ @4, @5 ] diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm index 6100ef1b305..1d31fa8b6b4 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm @@ -183,8 +183,9 @@ - (FSTDocumentViewChange *)parseChange:(NSArray *)change ofType:(FSTDocumentView } NSNumber *version = change[1]; XCTAssert([change[0] isKindOfClass:[NSString class]]); - FSTDocument *doc = FSTTestDoc(util::MakeString((NSString *)change[0]), version.longLongValue, - change[2], hasMutations); + FSTDocument *doc = + FSTTestDoc(util::MakeString((NSString *)change[0]), version.longLongValue, change[2], + hasMutations ? FSTDocumentStateLocalMutations : FSTDocumentStateSynced); return [FSTDocumentViewChange changeWithDocument:doc type:type]; } @@ -274,12 +275,13 @@ - (void)doWatchEntity:(NSDictionary *)watchEntity { FSTObjectValue *_Nullable value = [docSpec[2] isKindOfClass:[NSNull class]] ? nil : FSTTestObjectValue(docSpec[2]); SnapshotVersion version = [self parseVersion:docSpec[1]]; - FSTMaybeDocument *doc = - value ? [FSTDocument documentWithData:value - key:key - version:std::move(version) - hasLocalMutations:NO] - : [FSTDeletedDocument documentWithKey:key version:std::move(version)]; + FSTMaybeDocument *doc = value ? [FSTDocument documentWithData:value + key:key + version:std::move(version) + state:FSTDocumentStateSynced] + : [FSTDeletedDocument documentWithKey:key + version:std::move(version) + hasCommittedMutations:NO]; FSTWatchChange *change = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:watchEntity[@"targets"] removedTargetIDs:watchEntity[@"removedTargets"] diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h index 70eccfbf8a5..faeafe8a352 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.h +++ b/Firestore/Example/Tests/Util/FSTHelpers.h @@ -19,6 +19,7 @@ #include #include +#import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTDocumentDictionary.h" #import "Firestore/Source/Remote/FSTRemoteEvent.h" @@ -228,11 +229,15 @@ typedef int64_t FSTTestSnapshotVersion; FSTDocument *FSTTestDoc(const absl::string_view path, FSTTestSnapshotVersion version, NSDictionary *data, - BOOL hasMutations); + FSTDocumentState documentState); /** A convenience method for creating deleted docs for tests. */ -FSTDeletedDocument *FSTTestDeletedDoc(const absl::string_view path, FSTTestSnapshotVersion version); +FSTDeletedDocument *FSTTestDeletedDoc(const absl::string_view path, + FSTTestSnapshotVersion version, + BOOL hasCommittedMutations); +/** A convenience method for creating unknown docs for tests. */ +FSTUnknownDocument *FSTTestUnknownDoc(const absl::string_view path, FSTTestSnapshotVersion version); /** * A convenience method for creating a document reference from a path string. */ diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm index 69cf9937916..965dc395aef 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.mm +++ b/Firestore/Example/Tests/Util/FSTHelpers.mm @@ -153,18 +153,27 @@ FSTDocument *FSTTestDoc(const absl::string_view path, FSTTestSnapshotVersion version, NSDictionary *data, - BOOL hasMutations) { + FSTDocumentState documentState) { DocumentKey key = testutil::Key(path); return [FSTDocument documentWithData:FSTTestObjectValue(data) key:key version:testutil::Version(version) - hasLocalMutations:hasMutations]; + state:documentState]; } FSTDeletedDocument *FSTTestDeletedDoc(const absl::string_view path, + FSTTestSnapshotVersion version, + BOOL hasCommittedMutations) { + DocumentKey key = testutil::Key(path); + return [FSTDeletedDocument documentWithKey:key + version:testutil::Version(version) + hasCommittedMutations:hasCommittedMutations]; +} + +FSTUnknownDocument *FSTTestUnknownDoc(const absl::string_view path, FSTTestSnapshotVersion version) { DocumentKey key = testutil::Key(path); - return [FSTDeletedDocument documentWithKey:key version:testutil::Version(version)]; + return [FSTUnknownDocument documentWithKey:key version:testutil::Version(version)]; } FSTDocumentKeyReference *FSTTestRef(std::string projectID, std::string database, NSString *path) { diff --git a/Firestore/Source/API/FIRTransaction.mm b/Firestore/Source/API/FIRTransaction.mm index d33a114fee7..e8c75979dfb 100644 --- a/Firestore/Source/API/FIRTransaction.mm +++ b/Firestore/Source/API/FIRTransaction.mm @@ -119,16 +119,19 @@ - (void)getDocument:(FIRDocumentReference *)document HARD_ASSERT(documents.count == 1, "Mismatch in docs returned from document lookup."); FSTMaybeDocument *internalDoc = documents.firstObject; - if ([internalDoc isKindOfClass:[FSTDeletedDocument class]]) { + if ([internalDoc isKindOfClass:[FSTDocument class]]) { + FIRDocumentSnapshot *doc = + [FIRDocumentSnapshot snapshotWithFirestore:self.firestore + documentKey:internalDoc.key + document:(FSTDocument *)internalDoc + fromCache:NO]; + completion(doc, nil); + } else if ([internalDoc isKindOfClass:[FSTDeletedDocument class]]) { completion(nil, nil); - return; + } else { + HARD_FAIL("BatchGetDocumentsRequest returned unexpected document type: %s", + NSStringFromClass([internalDoc class])); } - FIRDocumentSnapshot *doc = - [FIRDocumentSnapshot snapshotWithFirestore:self.firestore - documentKey:internalDoc.key - document:(FSTDocument *)internalDoc - fromCache:NO]; - completion(doc, nil); }]; } diff --git a/Firestore/Source/Core/FSTSyncEngine.mm b/Firestore/Source/Core/FSTSyncEngine.mm index 0bf10880963..a843238df43 100644 --- a/Firestore/Source/Core/FSTSyncEngine.mm +++ b/Firestore/Source/Core/FSTSyncEngine.mm @@ -378,8 +378,9 @@ - (void)rejectListenWithTargetID:(const TargetId)targetID error:(NSError *)error // It's a limbo doc. Create a synthetic event saying it was deleted. This is kind of a hack. // Ideally, we would have a method in the local store to purge a document. However, it would // be tricky to keep all of the local store's invariants with another method. - FSTDeletedDocument *doc = - [FSTDeletedDocument documentWithKey:limboKey version:SnapshotVersion::None()]; + FSTDeletedDocument *doc = [FSTDeletedDocument documentWithKey:limboKey + version:SnapshotVersion::None() + hasCommittedMutations:NO]; DocumentKeySet limboDocuments = DocumentKeySet{doc.key}; FSTRemoteEvent *event = [[FSTRemoteEvent alloc] initWithSnapshotVersion:SnapshotVersion::None() targetChanges:{} diff --git a/Firestore/Source/Core/FSTTransaction.mm b/Firestore/Source/Core/FSTTransaction.mm index 84166632c52..1f30ae0bc9d 100644 --- a/Firestore/Source/Core/FSTTransaction.mm +++ b/Firestore/Source/Core/FSTTransaction.mm @@ -84,12 +84,17 @@ - (instancetype)initWithDatastore:(FSTDatastore *)datastore { - (BOOL)recordVersionForDocument:(FSTMaybeDocument *)doc error:(NSError **)error { HARD_ASSERT(error != nil, "nil error parameter"); *error = nil; - SnapshotVersion docVersion = doc.version; - if ([doc isKindOfClass:[FSTDeletedDocument class]]) { + SnapshotVersion docVersion; + if ([doc isKindOfClass:[FSTDocument class]]) { + docVersion = doc.version; + } else if ([doc isKindOfClass:[FSTDeletedDocument class]]) { // For deleted docs, we must record an explicit no version to build the right precondition // when writing. docVersion = SnapshotVersion::None(); + } else { + HARD_FAIL("Unexpected document type in transaction: %s", NSStringFromClass([doc class])); } + if (_readVersions.find(doc.key) == _readVersions.end()) { _readVersions[doc.key] = docVersion; return YES; diff --git a/Firestore/Source/Core/FSTView.mm b/Firestore/Source/Core/FSTView.mm index 1889c7cf3f6..c02f221a1cc 100644 --- a/Firestore/Source/Core/FSTView.mm +++ b/Firestore/Source/Core/FSTView.mm @@ -295,6 +295,7 @@ - (FSTViewDocumentChanges *)computeChangesWithDocuments:(FSTMaybeDocumentDiction while (newDocumentSet.count > self.query.limit) { FSTDocument *oldDoc = [newDocumentSet lastDocument]; newDocumentSet = [newDocumentSet documentSetByRemovingKey:oldDoc.key]; + newMutatedKeys = newMutatedKeys.erase(oldDoc.key); [changeSet addChange:[FSTDocumentViewChange changeWithDocument:oldDoc type:FSTDocumentViewChangeTypeRemoved]]; diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.mm b/Firestore/Source/Local/FSTLocalDocumentsView.mm index 170095bda5d..3c745461642 100644 --- a/Firestore/Source/Local/FSTLocalDocumentsView.mm +++ b/Firestore/Source/Local/FSTLocalDocumentsView.mm @@ -87,7 +87,9 @@ - (FSTMaybeDocumentDictionary *)documentsForKeys:(const DocumentKeySet &)keys { FSTMaybeDocument *maybeDoc = [self documentForKey:key inBatches:batches]; // TODO(http://b/32275378): Don't conflate missing / deleted. if (!maybeDoc) { - maybeDoc = [FSTDeletedDocument documentWithKey:key version:SnapshotVersion::None()]; + maybeDoc = [FSTDeletedDocument documentWithKey:key + version:SnapshotVersion::None() + hasCommittedMutations:NO]; } results = [results dictionaryBySettingObject:maybeDoc forKey:key]; } diff --git a/Firestore/Source/Local/FSTLocalSerializer.mm b/Firestore/Source/Local/FSTLocalSerializer.mm index 3b4fec05160..ed5bdab9a00 100644 --- a/Firestore/Source/Local/FSTLocalSerializer.mm +++ b/Firestore/Source/Local/FSTLocalSerializer.mm @@ -62,9 +62,17 @@ - (FSTPBMaybeDocument *)encodedMaybeDocument:(FSTMaybeDocument *)document { FSTPBMaybeDocument *proto = [FSTPBMaybeDocument message]; if ([document isKindOfClass:[FSTDeletedDocument class]]) { - proto.noDocument = [self encodedDeletedDocument:(FSTDeletedDocument *)document]; + FSTDeletedDocument *deletedDocument = (FSTDeletedDocument *)document; + proto.noDocument = [self encodedDeletedDocument:deletedDocument]; + proto.hasCommittedMutations = deletedDocument.hasCommittedMutations; } else if ([document isKindOfClass:[FSTDocument class]]) { - proto.document = [self encodedDocument:(FSTDocument *)document]; + FSTDocument *existingDocument = (FSTDocument *)document; + proto.document = [self encodedDocument:existingDocument]; + proto.hasCommittedMutations = existingDocument.hasCommittedMutations; + } else if ([document isKindOfClass:[FSTUnknownDocument class]]) { + FSTUnknownDocument *unknownDocument = (FSTUnknownDocument *)document; + proto.unknownDocument = [self encodedUnknownDocument:unknownDocument]; + proto.hasCommittedMutations = YES; } else { HARD_FAIL("Unknown document type %s", NSStringFromClass([document class])); } @@ -75,10 +83,15 @@ - (FSTPBMaybeDocument *)encodedMaybeDocument:(FSTMaybeDocument *)document { - (FSTMaybeDocument *)decodedMaybeDocument:(FSTPBMaybeDocument *)proto { switch (proto.documentTypeOneOfCase) { case FSTPBMaybeDocument_DocumentType_OneOfCase_Document: - return [self decodedDocument:proto.document]; + return + [self decodedDocument:proto.document withCommittedMutations:proto.hasCommittedMutations]; case FSTPBMaybeDocument_DocumentType_OneOfCase_NoDocument: - return [self decodedDeletedDocument:proto.noDocument]; + return [self decodedDeletedDocument:proto.noDocument + withCommittedMutations:proto.hasCommittedMutations]; + + case FSTPBMaybeDocument_DocumentType_OneOfCase_UnknownDocument: + return [self decodedUnknownDocument:proto.unknownDocument]; default: HARD_FAIL("Unknown MaybeDocument %s", proto); @@ -102,13 +115,18 @@ - (GCFSDocument *)encodedDocument:(FSTDocument *)document { } /** Decodes a Document proto to the equivalent model. */ -- (FSTDocument *)decodedDocument:(GCFSDocument *)document { +- (FSTDocument *)decodedDocument:(GCFSDocument *)document + withCommittedMutations:(BOOL)committedMutations { FSTSerializerBeta *remoteSerializer = self.remoteSerializer; FSTObjectValue *data = [remoteSerializer decodedFields:document.fields]; DocumentKey key = [remoteSerializer decodedDocumentKey:document.name]; SnapshotVersion version = [remoteSerializer decodedVersion:document.updateTime]; - return [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO]; + return [FSTDocument documentWithData:data + key:key + version:version + state:committedMutations ? FSTDocumentStateCommittedMutations + : FSTDocumentStateSynced]; } /** Encodes a NoDocument value to the equivalent proto. */ @@ -122,12 +140,34 @@ - (FSTPBNoDocument *)encodedDeletedDocument:(FSTDeletedDocument *)document { } /** Decodes a NoDocument proto to the equivalent model. */ -- (FSTDeletedDocument *)decodedDeletedDocument:(FSTPBNoDocument *)proto { +- (FSTDeletedDocument *)decodedDeletedDocument:(FSTPBNoDocument *)proto + withCommittedMutations:(BOOL)committedMutations { FSTSerializerBeta *remoteSerializer = self.remoteSerializer; DocumentKey key = [remoteSerializer decodedDocumentKey:proto.name]; SnapshotVersion version = [remoteSerializer decodedVersion:proto.readTime]; - return [FSTDeletedDocument documentWithKey:key version:version]; + return [FSTDeletedDocument documentWithKey:key + version:version + hasCommittedMutations:committedMutations]; +} + +/** Encodes an UnknownDocument value to the equivalent proto. */ +- (FSTPBUnknownDocument *)encodedUnknownDocument:(FSTUnknownDocument *)document { + FSTSerializerBeta *remoteSerializer = self.remoteSerializer; + + FSTPBUnknownDocument *proto = [FSTPBUnknownDocument message]; + proto.name = [remoteSerializer encodedDocumentKey:document.key]; + proto.version = [remoteSerializer encodedVersion:document.version]; + return proto; +} + +/** Decodes an UnknownDocument proto to the equivalent model. */ +- (FSTUnknownDocument *)decodedUnknownDocument:(FSTPBUnknownDocument *)proto { + FSTSerializerBeta *remoteSerializer = self.remoteSerializer; + + DocumentKey key = [remoteSerializer decodedDocumentKey:proto.name]; + SnapshotVersion version = [remoteSerializer decodedVersion:proto.version]; + return [FSTUnknownDocument documentWithKey:key version:version]; } - (FSTPBWriteBatch *)encodedMutationBatch:(FSTMutationBatch *)batch { diff --git a/Firestore/Source/Local/FSTLocalStore.mm b/Firestore/Source/Local/FSTLocalStore.mm index e193dad6591..252807a81a8 100644 --- a/Firestore/Source/Local/FSTLocalStore.mm +++ b/Firestore/Source/Local/FSTLocalStore.mm @@ -312,7 +312,8 @@ - (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent { // 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) { + authoritativeUpdates.contains(doc.key) || + (doc.version >= existingDoc.version && existingDoc.hasPendingWrites)) { [self.remoteDocumentCache addEntry:doc]; } else { LOG_DEBUG( diff --git a/Firestore/Source/Model/FSTDocument.h b/Firestore/Source/Model/FSTDocument.h index 0f8d4b3db39..6d9cc8681ba 100644 --- a/Firestore/Source/Model/FSTDocument.h +++ b/Firestore/Source/Model/FSTDocument.h @@ -25,6 +25,16 @@ NS_ASSUME_NONNULL_BEGIN +/** Describes the `hasPendingWrites` state of a document. */ +typedef NS_ENUM(NSInteger, FSTDocumentState) { + /** Local mutations applied via the mutation queue. Document is potentially inconsistent. */ + FSTDocumentStateLocalMutations, + /** Mutations applied based on a write acknowledgment. Document is potentially inconsistent. */ + FSTDocumentStateCommittedMutations, + /** No mutations applied. Document was sent to us by Watch. */ + FSTDocumentStateSynced +}; + /** * The result of a lookup for a given path may be an existing document or a tombstone that marks * the path deleted. @@ -33,22 +43,38 @@ NS_ASSUME_NONNULL_BEGIN - (id)init __attribute__((unavailable("Abstract base class"))); - (const firebase::firestore::model::DocumentKey &)key; - (const firebase::firestore::model::SnapshotVersion &)version; + +/** + * Whether this document has a local mutation applied that has not yet been acknowledged by Watch. + */ +- (BOOL)hasPendingWrites; + @end @interface FSTDocument : FSTMaybeDocument + (instancetype)documentWithData:(FSTObjectValue *)data key:(firebase::firestore::model::DocumentKey)key version:(firebase::firestore::model::SnapshotVersion)version - hasLocalMutations:(BOOL)mutations; + state:(FSTDocumentState)state; - (nullable FSTFieldValue *)fieldForPath:(const firebase::firestore::model::FieldPath &)path; +- (BOOL)hasLocalMutations; +- (BOOL)hasCommittedMutations; @property(nonatomic, strong, readonly) FSTObjectValue *data; -@property(nonatomic, readonly, getter=hasLocalMutations) BOOL localMutations; @end @interface FSTDeletedDocument : FSTMaybeDocument ++ (instancetype)documentWithKey:(firebase::firestore::model::DocumentKey)key + version:(firebase::firestore::model::SnapshotVersion)version + hasCommittedMutations:(BOOL)committedMutations; + +- (BOOL)hasCommittedMutations; + +@end + +@interface FSTUnknownDocument : FSTMaybeDocument + (instancetype)documentWithKey:(firebase::firestore::model::DocumentKey)key version:(firebase::firestore::model::SnapshotVersion)version; @end diff --git a/Firestore/Source/Model/FSTDocument.mm b/Firestore/Source/Model/FSTDocument.mm index f96552c13e4..3962207a991 100644 --- a/Firestore/Source/Model/FSTDocument.mm +++ b/Firestore/Source/Model/FSTDocument.mm @@ -19,6 +19,7 @@ #include #import "Firestore/Source/Model/FSTFieldValue.h" +#import "Firestore/Source/Util/FSTClasses.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_path.h" @@ -54,6 +55,10 @@ - (instancetype)initWithKey:(DocumentKey)key version:(SnapshotVersion)version { return self; } +- (BOOL)hasPendingWrites { + @throw FSTAbstractMethodException(); // NOLINT +} + - (id)copyWithZone:(NSZone *_Nullable)zone { // All document types are immutable return self; @@ -69,30 +74,44 @@ - (id)copyWithZone:(NSZone *_Nullable)zone { @end -@implementation FSTDocument +@implementation FSTDocument { + FSTDocumentState _documentState; +} + (instancetype)documentWithData:(FSTObjectValue *)data key:(DocumentKey)key version:(SnapshotVersion)version - hasLocalMutations:(BOOL)mutations { + state:(FSTDocumentState)state { return [[FSTDocument alloc] initWithData:data key:std::move(key) version:std::move(version) - hasLocalMutations:mutations]; + state:state]; } - (instancetype)initWithData:(FSTObjectValue *)data key:(DocumentKey)key version:(SnapshotVersion)version - hasLocalMutations:(BOOL)mutations { + state:(FSTDocumentState)state { self = [super initWithKey:std::move(key) version:std::move(version)]; if (self) { _data = data; - _localMutations = mutations; + _documentState = state; } return self; } +- (BOOL)hasLocalMutations { + return _documentState == FSTDocumentStateLocalMutations; +} + +- (BOOL)hasCommittedMutations { + return _documentState == FSTDocumentStateCommittedMutations; +} + +- (BOOL)hasPendingWrites { + return self.hasLocalMutations || self.hasCommittedMutations; +} + - (BOOL)isEqual:(id)other { if (other == self) { return YES; @@ -103,22 +122,22 @@ - (BOOL)isEqual:(id)other { FSTDocument *otherDoc = other; return self.key == otherDoc.key && self.version == otherDoc.version && - [self.data isEqual:otherDoc.data] && self.hasLocalMutations == otherDoc.hasLocalMutations; + _documentState == otherDoc->_documentState && [self.data isEqual:otherDoc.data]; } - (NSUInteger)hash { NSUInteger result = [self.key hash]; result = result * 31 + self.version.Hash(); result = result * 31 + [self.data hash]; - result = result * 31 + (self.hasLocalMutations ? 1 : 0); + result = result * 31 + _documentState; return result; } - (NSString *)description { - return [NSString stringWithFormat:@"", + return [NSString stringWithFormat:@"", self.key.ToString().c_str(), self.version.timestamp().ToString().c_str(), - self.localMutations ? @"YES" : @"NO", self.data]; + (long)_documentState, self.data]; } - (nullable FSTFieldValue *)fieldForPath:(const FieldPath &)path { @@ -127,10 +146,30 @@ - (nullable FSTFieldValue *)fieldForPath:(const FieldPath &)path { @end -@implementation FSTDeletedDocument +@implementation FSTDeletedDocument { + BOOL _hasCommittedMutations; +} + ++ (instancetype)documentWithKey:(DocumentKey)key + version:(SnapshotVersion)version + hasCommittedMutations:(BOOL)committedMutations { + FSTDeletedDocument *deletedDocument = + [[FSTDeletedDocument alloc] initWithKey:std::move(key) version:std::move(version)]; -+ (instancetype)documentWithKey:(DocumentKey)key version:(SnapshotVersion)version { - return [[FSTDeletedDocument alloc] initWithKey:std::move(key) version:std::move(version)]; + if (deletedDocument) { + deletedDocument->_hasCommittedMutations = committedMutations; + } + + return deletedDocument; +} + + +- (BOOL)hasCommittedMutations { + return _hasCommittedMutations; +} + +- (BOOL)hasPendingWrites { + return self.hasCommittedMutations; } - (BOOL)isEqual:(id)other { @@ -141,6 +180,45 @@ - (BOOL)isEqual:(id)other { return NO; } + FSTDeletedDocument *otherDoc = other; + return self.key == otherDoc.key && self.version == otherDoc.version && + _hasCommittedMutations == otherDoc->_hasCommittedMutations; +} + +- (NSUInteger)hash { + NSUInteger result = [self.key hash]; + result = result * 31 + self.version.Hash(); + result = result * 31 + _hasCommittedMutations ? 1 : 0; + return result; +} + +- (NSString *)description { + return [NSString + stringWithFormat:@"", + self.key.ToString().c_str(), self.version.timestamp().ToString().c_str(), + _hasCommittedMutations]; +} + +@end + +@implementation FSTUnknownDocument + ++ (instancetype)documentWithKey:(DocumentKey)key version:(SnapshotVersion)version { + return [[FSTUnknownDocument alloc] initWithKey:std::move(key) version:std::move(version)]; +} + +- (BOOL)hasPendingWrites { + return YES; +} + +- (BOOL)isEqual:(id)other { + if (other == self) { + return YES; + } + if (![other isKindOfClass:[FSTUnknownDocument class]]) { + return NO; + } + FSTDocument *otherDoc = other; return self.key == otherDoc.key && self.version == otherDoc.version; } @@ -151,6 +229,12 @@ - (NSUInteger)hash { return result; } +- (NSString *)description { + return [NSString stringWithFormat:@"", + self.key.ToString().c_str(), + self.version.timestamp().ToString().c_str()]; +} + @end const NSComparator FSTDocumentComparatorByKey = diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h index 569dc0caf4f..4eb22bc754a 100644 --- a/Firestore/Source/Model/FSTMutation.h +++ b/Firestore/Source/Model/FSTMutation.h @@ -46,7 +46,16 @@ NS_ASSUME_NONNULL_BEGIN transformResults:(NSArray *_Nullable)transformResults NS_DESIGNATED_INITIALIZER; -/** The version at which the mutation was committed. */ +/** + * The version at which the mutation was committed. + * + * - For most operations, this is the updateTime in the WriteResult. + * - For deletes, it is the commitTime of the WriteResponse (because deletes are not stored + * and have no updateTime). + * + * Note that these versions can be different: No-op writes will not change the updateTime even + * though the commitTime advances. + */ - (const firebase::firestore::model::SnapshotVersion &)version; /** @@ -62,42 +71,21 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTMutation /** - * A mutation describes a self-contained change to a document. Mutations can create, replace, - * delete, and update subsets of documents. - * - * ## Subclassing Notes - * - * Subclasses of FSTMutation need to implement -applyTo:hasLocalMutations: to implement the - * actual the behavior of mutation as applied to some source document. - */ -@interface FSTMutation : NSObject - -- (id)init NS_UNAVAILABLE; - -- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key - precondition:(firebase::firestore::model::Precondition)precondition - NS_DESIGNATED_INITIALIZER; - -/** - * Applies this mutation to the given FSTDocument, FSTDeletedDocument or nil for the purposes of - * computing a new remote document. Both the input and returned documents can be nil. - * - * @param maybeDoc The current state of the document to mutate. The input document should be nil if - * it does not currently exist. - * @param mutationResult Optional result info from the backend. If omitted, it's assumed that - * this is merely a local (latency-compensated) application, and the resulting document will - * have its hasLocalMutations flag set. + * Represents a Mutation of a document. Different subclasses of Mutation will perform different + * kinds of changes to a base document. For example, an FSTSetMutation replaces the value of a + * document and an FSTDeleteMutation deletes a document. * - * @return The mutated document. The returned document may be nil, but only if maybeDoc was nil - * and the mutation would not create a new document. + * Subclasses of FSTMutation need to implement `applyToRemoteDocument:mutationResult:` and + * `applyToLocalDocument:baseDocument:localWriteTime:` to implement the actual the behavior of + * mutations as applied to some source document. * - * NOTE: We preserve the version of the base document only in case of Set or Patch mutation to - * denote what version of original document we've changed. In case of DeleteMutation we always reset - * the version. + * In addition to the value of the document mutations also operate on the version. For local + * mutations (mutations that haven't been committed yet), we preserve the existing version for Set, + * Patch, and Transform mutations. For local deletes, we reset the version to 0. * * Here's the expected transition table. * - * MUTATION APPLIED TO RESULTS IN + * MUTATION APPLIED TO RESULTS IN * * SetMutation Document(v3) Document(v3) * SetMutation DeletedDocument(v3) Document(v0) @@ -112,25 +100,53 @@ NS_ASSUME_NONNULL_BEGIN * DeleteMutation DeletedDocument(v3) DeletedDocument(v0) * DeleteMutation nil DeletedDocument(v0) * - * Note that FSTTransformMutations don't create FSTDocuments (in the case of being applied to an + * For acknowledged mutations, we use the updateTime of the WriteResponse as the resulting version + * for Set, Patch, and Transform mutations. As deletes have no explicit update time, we use the + * commitTime of the WriteResponse for acknowledged deletes. + * + * If a mutation is acknowledged by the backend but fails the precondition check locally, we + * return an `FSTUnknownDocument` and rely on Watch to send us the updated version. + * + * Note that FSTTransformMutations don't create Documents (in the case of being applied to an * FSTDeletedDocument), even though they would on the backend. This is because the client always - * combines the FSTTransformMutation with a FSTSetMutation or FSTPatchMutation and we only want to + * combines the FSTTransformMutations with a FSTSetMutation or FSTPatchMutation and we only want to * apply the transform if the prior mutation resulted in an FSTDocument (always true for an * FSTSetMutation, but not necessarily for an FSTPatchMutation). */ -- (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc - mutationResult:(FSTMutationResult *)mutationResult; +@interface FSTMutation : NSObject + +- (id)init NS_UNAVAILABLE; + +- (instancetype)initWithKey:(firebase::firestore::model::DocumentKey)key + precondition:(firebase::firestore::model::Precondition)precondition + NS_DESIGNATED_INITIALIZER; + +/** + * Applies this mutation to the given FSTMaybeDocument for the purposes of computing a new remote + * document. If the input document doesn't match the expected state (e.g. it is nil or outdated), + * an `FSTUnknownDocument` can be returned. + * + * @param maybeDoc The document to mutate. The input document can be nil if the client has no + * knowledge of the pre-mutation state of the document. + * @param mutationResult The result of applying the mutation from the backend. + * @return The mutated document. The returned document may be an FSTUnknownDocument if the mutation + * could not be applied to the locally cached base document. + */ +- (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc + mutationResult:(FSTMutationResult *)mutationResult; /** - * Applies this mutation to the given MaybeDocument for the purposes of computing the new local view - * of a document. Both the input and returned documents can be null. + * Applies this mutation to the given FSTMaybeDocument for the purposes of computing the new local + * view of a document. Both the input and returned documents can be nil. * - * @param maybeDoc The current state of the document to mutate. The input document should be nil if - * it does not currently exist. - * @param baseDoc The state of the document prior to this mutation batch. The input document should - * be nil if it the document did not exist. + * @param maybeDoc The document to mutate. The input document can be nil if the client has no + * knowledge of the pre-mutation state of the document. + * @param baseDoc The state of the document prior to this mutation batch. The input document can + * be nil if the client has no knowledge of the pre-mutation state of the document. * @param localWriteTime A timestamp indicating the local write time of the batch this mutation is * a part of. + * @return The mutated document. The returned document may be nil, but only if maybeDoc was nil + * and the mutation would not create a new document. */ - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc baseDocument:(nullable FSTMaybeDocument *)baseDoc diff --git a/Firestore/Source/Model/FSTMutation.mm b/Firestore/Source/Model/FSTMutation.mm index a5b925278cf..fb74a828450 100644 --- a/Firestore/Source/Model/FSTMutation.mm +++ b/Firestore/Source/Model/FSTMutation.mm @@ -84,8 +84,8 @@ - (instancetype)initWithKey:(DocumentKey)key precondition:(Precondition)precondi return self; } -- (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc - mutationResult:(FSTMutationResult *)mutationResult { +- (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc + mutationResult:(FSTMutationResult *)mutationResult { @throw FSTAbstractMethodException(); // NOLINT } @@ -103,6 +103,20 @@ - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument * return _precondition; } +- (void)verifyKeyMatches:(nullable FSTMaybeDocument *)maybeDoc { + if (maybeDoc) { + HARD_ASSERT(maybeDoc.key == self.key, "Can only set a document with the same key"); + } +} + +/** + * Returns the version from the given document for use as the result of a mutation. Mutations are + * defined to return the version of the base document only if it is an existing document. Deleted + * and unknown documents have a post-mutation version of {@code SnapshotVersion::None()}. + */ +- (const SnapshotVersion &)postMutationVersionForDocument:(FSTMaybeDocument *)maybeDoc { + return [maybeDoc isKindOfClass:[FSTDocument class]] ? maybeDoc.version : SnapshotVersion::None(); +} @end #pragma mark - FSTSetMutation @@ -144,56 +158,32 @@ - (NSUInteger)hash { - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FIRTimestamp *)localWriteTime { + [self verifyKeyMatches:maybeDoc]; + if (!self.precondition.IsValidFor(maybeDoc)) { return maybeDoc; } - if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) { - // If the document didn't exist before, create it. - return [FSTDocument documentWithData:self.value - key:self.key - version:SnapshotVersion::None() - hasLocalMutations:YES]; - } - - HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s", - [maybeDoc class]); - FSTDocument *doc = (FSTDocument *)maybeDoc; - - HARD_ASSERT(doc.key == self.key, "Can only set a document with the same key"); + SnapshotVersion version = [self postMutationVersionForDocument:maybeDoc]; return [FSTDocument documentWithData:self.value - key:doc.key - version:doc.version - hasLocalMutations:YES]; + key:self.key + version:version + state:FSTDocumentStateLocalMutations]; } -- (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc - mutationResult:(FSTMutationResult *)mutationResult { - if (mutationResult) { - HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTSetMutation."); - } +- (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc + mutationResult:(FSTMutationResult *)mutationResult { + [self verifyKeyMatches:maybeDoc]; - if (!self.precondition.IsValidFor(maybeDoc)) { - return maybeDoc; - } + HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTSetMutation."); - if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) { - // If the document didn't exist before, create it. - return [FSTDocument documentWithData:self.value - key:self.key - version:SnapshotVersion::None() - hasLocalMutations:NO]; - } + // Unlike applyToLocalView, if we're applying a mutation to a remote document the server has + // accepted the mutation so the precondition must have held. - HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s", - [maybeDoc class]); - FSTDocument *doc = (FSTDocument *)maybeDoc; - - HARD_ASSERT(doc.key == self.key, "Can only set a document with the same key"); return [FSTDocument documentWithData:self.value - key:doc.key - version:doc.version - hasLocalMutations:NO]; + key:self.key + version:mutationResult.version + state:FSTDocumentStateCommittedMutations]; } @end @@ -243,64 +233,57 @@ - (NSString *)description { self.value, self.precondition.description()]; } +/** + * Patches the data of document if available or creates a new document. Note that this does not + * check whether or not the precondition of this patch holds. + */ +- (FSTObjectValue *)patchDocument:(nullable FSTMaybeDocument *)maybeDoc { + FSTObjectValue *data; + if ([maybeDoc isKindOfClass:[FSTDocument class]]) { + data = ((FSTDocument *)maybeDoc).data; + } else { + data = [FSTObjectValue objectValue]; + } + return [self patchObjectValue:data]; +} + - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FIRTimestamp *)localWriteTime { + [self verifyKeyMatches:maybeDoc]; + if (!self.precondition.IsValidFor(maybeDoc)) { return maybeDoc; } - if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) { - // Precondition applied, so create the document if necessary - const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key; - SnapshotVersion version = maybeDoc ? maybeDoc.version : SnapshotVersion::None(); - maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue] - key:key - version:std::move(version) - hasLocalMutations:YES]; - } - - HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s", - [maybeDoc class]); - FSTDocument *doc = (FSTDocument *)maybeDoc; - - HARD_ASSERT(doc.key == self.key, "Can only patch a document with the same key"); + FSTObjectValue *newData = [self patchDocument:maybeDoc]; + SnapshotVersion version = [self postMutationVersionForDocument:maybeDoc]; - FSTObjectValue *newData = [self patchObjectValue:doc.data]; - return - [FSTDocument documentWithData:newData key:doc.key version:doc.version hasLocalMutations:YES]; + return [FSTDocument documentWithData:newData + key:self.key + version:version + state:FSTDocumentStateLocalMutations]; } -- (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc - mutationResult:(FSTMutationResult *)mutationResult { +- (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc + mutationResult:(FSTMutationResult *)mutationResult { + [self verifyKeyMatches:maybeDoc]; + HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTPatchMutation."); if (!self.precondition.IsValidFor(maybeDoc)) { - return maybeDoc; + // Since the mutation was not rejected, we know that the precondition matched on the backend. + // We therefore must not have the expected version of the document in our cache and return a + // FSTUnknownDocument with the known updateTime. + return [FSTUnknownDocument documentWithKey:self.key version:mutationResult.version]; } - BOOL hasLocalMutations = (mutationResult == nil); - if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) { - // Precondition applied, so create the document if necessary - const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key; - SnapshotVersion version = maybeDoc ? maybeDoc.version : SnapshotVersion::None(); - maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue] - key:key - version:std::move(version) - hasLocalMutations:hasLocalMutations]; - } + FSTObjectValue *newData = [self patchDocument:maybeDoc]; - HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s", - [maybeDoc class]); - FSTDocument *doc = (FSTDocument *)maybeDoc; - - HARD_ASSERT(doc.key == self.key, "Can only patch a document with the same key"); - - FSTObjectValue *newData = [self patchObjectValue:doc.data]; return [FSTDocument documentWithData:newData - key:doc.key - version:doc.version - hasLocalMutations:hasLocalMutations]; + key:self.key + version:mutationResult.version + state:FSTDocumentStateCommittedMutations]; } - (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue { @@ -375,6 +358,8 @@ - (NSString *)description { - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FIRTimestamp *)localWriteTime { + [self verifyKeyMatches:maybeDoc]; + if (!self.precondition.IsValidFor(maybeDoc)) { return maybeDoc; } @@ -385,22 +370,28 @@ - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument * [maybeDoc class]); FSTDocument *doc = (FSTDocument *)maybeDoc; - HARD_ASSERT(doc.key == self.key, "Can only transform a document with the same key"); - NSArray *transformResults = [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime]; FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults]; - return - [FSTDocument documentWithData:newData key:doc.key version:doc.version hasLocalMutations:YES]; + + return [FSTDocument documentWithData:newData + key:doc.key + version:doc.version + state:FSTDocumentStateLocalMutations]; } -- (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc - mutationResult:(FSTMutationResult *)mutationResult { +- (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc + mutationResult:(FSTMutationResult *)mutationResult { + [self verifyKeyMatches:maybeDoc]; + HARD_ASSERT(mutationResult.transformResults, "Transform results missing for FSTTransformMutation."); if (!self.precondition.IsValidFor(maybeDoc)) { - return maybeDoc; + // Since the mutation was not rejected, we know that the precondition matched on the backend. + // We therefore must not have the expected version of the document in our cache and return an + // FSTUnknownDocument with the known updateTime. + return [FSTUnknownDocument documentWithKey:self.key version:mutationResult.version]; } // We only support transforms with precondition exists, so we can only apply it to an existing @@ -408,16 +399,16 @@ - (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s", [maybeDoc class]); FSTDocument *doc = (FSTDocument *)maybeDoc; - - HARD_ASSERT(doc.key == self.key, "Can only transform a document with the same key"); - NSArray *transformResults = [self serverTransformResultsWithBaseDocument:maybeDoc serverTransformResults:mutationResult.transformResults]; FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults]; - return - [FSTDocument documentWithData:newData key:doc.key version:doc.version hasLocalMutations:NO]; + + return [FSTDocument documentWithData:newData + key:self.key + version:mutationResult.version + state:FSTDocumentStateCommittedMutations]; } /** @@ -521,33 +512,34 @@ - (NSString *)description { - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc baseDocument:(nullable FSTMaybeDocument *)baseDoc localWriteTime:(FIRTimestamp *)localWriteTime { + [self verifyKeyMatches:maybeDoc]; + if (!self.precondition.IsValidFor(maybeDoc)) { return maybeDoc; } - if (maybeDoc) { - HARD_ASSERT(maybeDoc.key == self.key, "Can only delete a document with the same key"); - } - - return [FSTDeletedDocument documentWithKey:self.key version:SnapshotVersion::None()]; + return [FSTDeletedDocument documentWithKey:self.key + version:SnapshotVersion::None() + hasCommittedMutations:NO]; } -- (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc - mutationResult:(FSTMutationResult *)mutationResult { +- (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc + mutationResult:(FSTMutationResult *)mutationResult { + [self verifyKeyMatches:maybeDoc]; + if (mutationResult) { HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTDeleteMutation."); } - if (!self.precondition.IsValidFor(maybeDoc)) { - return maybeDoc; - } - - if (maybeDoc) { - HARD_ASSERT(maybeDoc.key == self.key, "Can only delete a document with the same key"); - } + // Unlike applyToLocalView, if we're applying a mutation to a remote document the server has + // accepted the mutation so the precondition must have held. - return [FSTDeletedDocument documentWithKey:self.key version:SnapshotVersion::None()]; + // We store the deleted document at the commit version of the delete. Any document version + // that the server sends us before the delete was applied is discarded + return [FSTDeletedDocument documentWithKey:self.key + version:mutationResult.version + hasCommittedMutations:YES]; } @end diff --git a/Firestore/Source/Remote/FSTRemoteEvent.mm b/Firestore/Source/Remote/FSTRemoteEvent.mm index ee674b372b5..3b64fabb3f0 100644 --- a/Firestore/Source/Remote/FSTRemoteEvent.mm +++ b/Firestore/Source/Remote/FSTRemoteEvent.mm @@ -427,10 +427,11 @@ - (void)handleExistenceFilter:(FSTExistenceFilterWatchChange *)existenceFilter { // document there might be another query that will raise this document as part of a snapshot // until it is resolved, essentially exposing inconsistency between queries. FSTDocumentKey *key = [FSTDocumentKey keyWithPath:query.path]; - [self - removeDocument:[FSTDeletedDocument documentWithKey:key version:SnapshotVersion::None()] - withKey:key - fromTarget:targetID]; + [self removeDocument:[FSTDeletedDocument documentWithKey:key + version:SnapshotVersion::None() + hasCommittedMutations:NO] + withKey:key + fromTarget:targetID]; } else { HARD_ASSERT(expectedCount == 1, "Single document existence filter with count: %s", expectedCount); @@ -575,7 +576,9 @@ - (FSTRemoteEvent *)remoteEventAtSnapshotVersion:(const SnapshotVersion &)snapsh FSTDocumentKey *key = [FSTDocumentKey keyWithPath:queryData.query.path]; if (_pendingDocumentUpdates.find(key) == _pendingDocumentUpdates.end() && ![self containsDocument:key inTarget:targetID]) { - [self removeDocument:[FSTDeletedDocument documentWithKey:key version:snapshotVersion] + [self removeDocument:[FSTDeletedDocument documentWithKey:key + version:snapshotVersion + hasCommittedMutations:NO] withKey:key fromTarget:targetID]; } diff --git a/Firestore/Source/Remote/FSTSerializerBeta.mm b/Firestore/Source/Remote/FSTSerializerBeta.mm index 05cd80ba220..f561f03fad2 100644 --- a/Firestore/Source/Remote/FSTSerializerBeta.mm +++ b/Firestore/Source/Remote/FSTSerializerBeta.mm @@ -439,7 +439,7 @@ - (FSTDocument *)decodedFoundDocument:(GCFSBatchGetDocumentsResponse *)response HARD_ASSERT(version != SnapshotVersion::None(), "Got a document response with no snapshot version"); - return [FSTDocument documentWithData:value key:key version:version hasLocalMutations:NO]; + return [FSTDocument documentWithData:value key:key version:version state:FSTDocumentStateSynced]; } - (FSTDeletedDocument *)decodedDeletedDocument:(GCFSBatchGetDocumentsResponse *)response { @@ -448,7 +448,7 @@ - (FSTDeletedDocument *)decodedDeletedDocument:(GCFSBatchGetDocumentsResponse *) SnapshotVersion version = [self decodedVersion:response.readTime]; HARD_ASSERT(version != SnapshotVersion::None(), "Got a no document response with no snapshot version"); - return [FSTDeletedDocument documentWithKey:key version:version]; + return [FSTDeletedDocument documentWithKey:key version:version hasCommittedMutations:NO]; } #pragma mark - FSTMutation => GCFSWrite proto @@ -1146,7 +1146,7 @@ - (FSTDocumentWatchChange *)decodedDocumentChange:(GCFSDocumentChange *)change { SnapshotVersion version = [self decodedVersion:change.document.updateTime]; HARD_ASSERT(version != SnapshotVersion::None(), "Got a document change with no snapshot version"); FSTMaybeDocument *document = - [FSTDocument documentWithData:value key:key version:version hasLocalMutations:NO]; + [FSTDocument documentWithData:value key:key version:version state:FSTDocumentStateSynced]; NSArray *updatedTargetIds = [self decodedIntegerArray:change.targetIdsArray]; NSArray *removedTargetIds = [self decodedIntegerArray:change.removedTargetIdsArray]; @@ -1161,7 +1161,8 @@ - (FSTDocumentWatchChange *)decodedDocumentDelete:(GCFSDocumentDelete *)change { const DocumentKey key = [self decodedDocumentKey:change.document]; // Note that version might be unset in which case we use SnapshotVersion::None() SnapshotVersion version = [self decodedVersion:change.readTime]; - FSTMaybeDocument *document = [FSTDeletedDocument documentWithKey:key version:version]; + FSTMaybeDocument *document = + [FSTDeletedDocument documentWithKey:key version:version hasCommittedMutations:NO]; NSArray *removedTargetIds = [self decodedIntegerArray:change.removedTargetIdsArray]; diff --git a/Firestore/core/src/firebase/firestore/model/precondition.cc b/Firestore/core/src/firebase/firestore/model/precondition.cc index 61024db9cc0..4d22eaa5724 100644 --- a/Firestore/core/src/firebase/firestore/model/precondition.cc +++ b/Firestore/core/src/firebase/firestore/model/precondition.cc @@ -52,13 +52,8 @@ bool Precondition::IsValidFor(const MaybeDocument* maybe_doc) const { maybe_doc->type() == MaybeDocument::Type::Document && maybe_doc->version() == update_time_; case Type::Exists: - if (exists_) { - return maybe_doc != nullptr && - maybe_doc->type() == MaybeDocument::Type::Document; - } else { - return maybe_doc == nullptr || - maybe_doc->type() == MaybeDocument::Type::NoDocument; - } + return (exists_ == (maybe_doc != nullptr && + maybe_doc->type() == MaybeDocument::Type::Document)); case Type::None: return true; }