Skip to content

Commit bba9406

Browse files
committed
Fix: #3528
1 parent 9b2c2d3 commit bba9406

File tree

4 files changed

+67
-3
lines changed

4 files changed

+67
-3
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,4 +884,39 @@ 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+
@Test
890+
public void testAddThenUpdatesWhileOffline() {
891+
CollectionReference collection = testCollection();
892+
collection.getFirestore().disableNetwork();
893+
894+
collection.add(map("foo", "zzyzx", "bar", "1"));
895+
896+
QuerySnapshot snapshot1 = waitFor(collection.get(Source.CACHE));
897+
assertEquals(asList(map("foo", "zzyzx", "bar", "1")), querySnapshotToValues(snapshot1));
898+
DocumentReference doc = snapshot1.getDocuments().get(0).getReference();
899+
900+
doc.update(map("bar", "2"));
901+
902+
QuerySnapshot snapshot2 = waitFor(collection.get(Source.CACHE));
903+
assertEquals(asList(map("foo", "zzyzx", "bar", "2")), querySnapshotToValues(snapshot2));
904+
}
905+
906+
@Test
907+
public void testMultipleUpdatesWhileOffline() {
908+
CollectionReference collection = testCollection();
909+
collection.getFirestore().disableNetwork();
910+
911+
DocumentReference doc = collection.document();
912+
doc.set(map("foo", "zzyzx", "bar", "1"), SetOptions.mergeFields("foo", "bar"));
913+
914+
QuerySnapshot snapshot1 = waitFor(collection.get(Source.CACHE));
915+
assertEquals(asList(map("foo", "zzyzx", "bar", "1")), querySnapshotToValues(snapshot1));
916+
917+
doc.update(map("bar", "2"));
918+
919+
QuerySnapshot snapshot2 = waitFor(collection.get(Source.CACHE));
920+
assertEquals(asList(map("foo", "zzyzx", "bar", "2")), querySnapshotToValues(snapshot2));
921+
}
887922
}

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: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,20 @@ 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+
Map<DocumentKey, MutableDocument> remoteDocs = remoteDocuments.getAll(keys);
251+
Set<DocumentKey> docsWithoutRemoteVersion = new HashSet<>();
252+
for (Map.Entry<DocumentKey, MutableDocument> entry : remoteDocs.entrySet()) {
253+
if (!entry.getValue().isValidDocument()) {
254+
docsWithoutRemoteVersion.add(entry.getKey());
255+
}
256+
}
247257
// Load and apply all existing mutations. This lets us compute the current base state for
248258
// all non-idempotent transforms before applying any additional user-provided writes.
249-
ImmutableSortedMap<DocumentKey, Document> documents = localDocuments.getDocuments(keys);
259+
ImmutableSortedMap<DocumentKey, Document> documents =
260+
localDocuments.getLocalViewOfDocuments(remoteDocs);
250261

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

271282
MutationBatch batch =
272283
mutationQueue.addMutationBatch(localWriteTime, baseMutations, mutations);
273-
Map<DocumentKey, Mutation> overlays = batch.applyToLocalDocumentSet(documents);
284+
Map<DocumentKey, Mutation> overlays =
285+
batch.applyToLocalDocumentSet(documents, docsWithoutRemoteVersion);
274286
documentOverlayCache.saveOverlays(batch.getBatchId(), overlays);
275287
return new LocalDocumentsResult(batch.getBatchId(), documents);
276288
});

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()) {

0 commit comments

Comments
 (0)