Skip to content

Commit df21954

Browse files
authored
Fix overlay bug when offline (#3537)
* Fix: #3528 * Fix test failure * Simpler fix * Ugly fix fixes
1 parent f5845b2 commit df21954

File tree

6 files changed

+73
-5
lines changed

6 files changed

+73
-5
lines changed

firebase-firestore/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ Android changes are not released automatically. Ensure that changes are released
22
by opting into a release at
33
[go/firebase-android-release](http:go/firebase-android-release) (Googlers only).
44

5-
# Unreleased
5+
# 24.1.0
66
- [feature] Added experimental support for indexed query execution. Indexes can
77
be enabled by invoking `FirebaseFirestore.setIndexConfiguration()` with the
88
JSON index definition exported by the Firestore CLI. Queries against the
99
cache are executed using an index once the asynchronous operation to generate
1010
the index entries completes.
11+
- [fixed] Fixed missing document fields issue with offline overlays (#3528)
1112

1213
# 24.0.2
1314
- [fixed] Fixed an issue of long grpc reconnection period, when App moves to

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,4 +884,40 @@ public void testCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIds() {
884884
.get());
885885
assertEquals(asList("cg-doc2"), querySnapshotToIds(querySnapshot));
886886
}
887+
888+
// See: https://github.com/firebase/firebase-android-sdk/issues/3528
889+
// TODO(Overlay): These two tests should be part of local store tests instead.
890+
@Test
891+
public void testAddThenUpdatesWhileOffline() {
892+
CollectionReference collection = testCollection();
893+
collection.getFirestore().disableNetwork();
894+
895+
collection.add(map("foo", "zzyzx", "bar", "1"));
896+
897+
QuerySnapshot snapshot1 = waitFor(collection.get(Source.CACHE));
898+
assertEquals(asList(map("foo", "zzyzx", "bar", "1")), querySnapshotToValues(snapshot1));
899+
DocumentReference doc = snapshot1.getDocuments().get(0).getReference();
900+
901+
doc.update(map("bar", "2"));
902+
903+
QuerySnapshot snapshot2 = waitFor(collection.get(Source.CACHE));
904+
assertEquals(asList(map("foo", "zzyzx", "bar", "2")), querySnapshotToValues(snapshot2));
905+
}
906+
907+
@Test
908+
public void testMultipleUpdatesWhileOffline() {
909+
CollectionReference collection = testCollection();
910+
collection.getFirestore().disableNetwork();
911+
912+
DocumentReference doc = collection.document();
913+
doc.set(map("foo", "zzyzx", "bar", "1"), SetOptions.mergeFields("foo", "bar"));
914+
915+
QuerySnapshot snapshot1 = waitFor(collection.get(Source.CACHE));
916+
assertEquals(asList(map("foo", "zzyzx", "bar", "1")), querySnapshotToValues(snapshot1));
917+
918+
doc.update(map("bar", "2"));
919+
920+
QuerySnapshot snapshot2 = waitFor(collection.get(Source.CACHE));
921+
assertEquals(asList(map("foo", "zzyzx", "bar", "2")), querySnapshotToValues(snapshot2));
922+
}
887923
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalDocumentsView.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,19 @@ ImmutableSortedMap<DocumentKey, Document> getLocalViewOfDocuments(
122122
return computeViews(docs, overlays, existenceStateChanged);
123123
}
124124

125+
/**
126+
* Similar to {@link #getDocuments}, but creates the local view from the given {@code baseDocs}
127+
* without retrieving documents from the local store.
128+
*
129+
* @param docs The documents to apply local mutations to get the local views.
130+
*/
131+
ImmutableSortedMap<DocumentKey, Document> getLocalViewOfDocuments(
132+
Map<DocumentKey, MutableDocument> docs) {
133+
Map<DocumentKey, Overlay> overlays = new HashMap<>();
134+
populateOverlays(overlays, docs.keySet());
135+
return computeViews(docs, overlays, new HashSet<>());
136+
}
137+
125138
/*Computes the local view for doc */
126139
private ImmutableSortedMap<DocumentKey, Document> computeViews(
127140
Map<DocumentKey, MutableDocument> docs,

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalStore.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,22 @@ public LocalDocumentsResult writeLocally(List<Mutation> mutations) {
244244
return persistence.runTransaction(
245245
"Locally write mutations",
246246
() -> {
247+
// Figure out which keys do not have a remote version in the cache, this is needed to
248+
// create the right overlay mutation: if no remote version presents, we do not need to
249+
// create overlays as patch mutations.
250+
// TODO(Overlay): Is there a better way to determine this? Document version does not work
251+
// because local mutations set them back to 0.
252+
Map<DocumentKey, MutableDocument> remoteDocs = remoteDocuments.getAll(keys);
253+
Set<DocumentKey> docsWithoutRemoteVersion = new HashSet<>();
254+
for (Map.Entry<DocumentKey, MutableDocument> entry : remoteDocs.entrySet()) {
255+
if (!entry.getValue().isValidDocument()) {
256+
docsWithoutRemoteVersion.add(entry.getKey());
257+
}
258+
}
247259
// Load and apply all existing mutations. This lets us compute the current base state for
248260
// all non-idempotent transforms before applying any additional user-provided writes.
249-
ImmutableSortedMap<DocumentKey, Document> documents = localDocuments.getDocuments(keys);
261+
ImmutableSortedMap<DocumentKey, Document> documents =
262+
localDocuments.getLocalViewOfDocuments(remoteDocs);
250263

251264
// For non-idempotent mutations (such as `FieldValue.increment()`), we record the base
252265
// state in a separate patch mutation. This is later used to guarantee consistent values
@@ -270,7 +283,8 @@ public LocalDocumentsResult writeLocally(List<Mutation> mutations) {
270283

271284
MutationBatch batch =
272285
mutationQueue.addMutationBatch(localWriteTime, baseMutations, mutations);
273-
Map<DocumentKey, Mutation> overlays = batch.applyToLocalDocumentSet(documents);
286+
Map<DocumentKey, Mutation> overlays =
287+
batch.applyToLocalDocumentSet(documents, docsWithoutRemoteVersion);
274288
documentOverlayCache.saveOverlays(batch.getBatchId(), overlays);
275289
return new LocalDocumentsResult(batch.getBatchId(), documents);
276290
});

firebase-firestore/src/main/java/com/google/firebase/firestore/model/mutation/MutationBatch.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ public FieldMask applyToLocalView(MutableDocument document, @Nullable FieldMask
136136
* applications.
137137
*/
138138
public Map<DocumentKey, Mutation> applyToLocalDocumentSet(
139-
ImmutableSortedMap<DocumentKey, Document> documentMap) {
139+
ImmutableSortedMap<DocumentKey, Document> documentMap,
140+
Set<DocumentKey> documentsWithoutRemoteVersion) {
140141
// TODO(mrschmidt): This implementation is O(n^2). If we iterate through the mutations first
141142
// (as done in `applyToLocalView(MutableDocument d)`), we can reduce the complexity to
142143
// O(n).
@@ -146,6 +147,9 @@ public Map<DocumentKey, Mutation> applyToLocalDocumentSet(
146147
// remove this cast.
147148
MutableDocument document = (MutableDocument) documentMap.get(key);
148149
FieldMask mutatedFields = applyToLocalView(document);
150+
// Set mutationFields to null if the document is only from local mutations, this creates
151+
// a Set(or Delete) mutation, instead of trying to create a patch mutation as the overlay.
152+
mutatedFields = documentsWithoutRemoteVersion.contains(key) ? null : mutatedFields;
149153
Mutation overlay = Mutation.calculateOverlayMutation(document, mutatedFields);
150154
overlays.put(key, overlay);
151155
if (!document.isValidDocument()) {

firebase-firestore/src/test/java/com/google/firebase/firestore/local/LocalStoreTestCase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ public void testHandlesDeleteMutationThenPatchMutationThenAckThenAck() {
759759

760760
acknowledgeMutation(2); // delete mutation
761761
assertRemoved("foo/bar");
762-
assertContains(deletedDoc("foo/bar", 2).setHasCommittedMutations());
762+
assertContains(deletedDoc("foo/bar", 0).setHasLocalMutations());
763763

764764
acknowledgeMutation(3); // patch mutation
765765
assertChanged(unknownDoc("foo/bar", 3));

0 commit comments

Comments
 (0)