Skip to content

Commit 2fc215e

Browse files
committed
Fix overlay bug when offline (Port Android SDK PR #3537).
1 parent fcf8bdb commit 2fc215e

File tree

4 files changed

+64
-10
lines changed

4 files changed

+64
-10
lines changed

packages/firestore/src/local/local_documents_view.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export class LocalDocumentsView {
132132
getLocalViewOfDocuments(
133133
transaction: PersistenceTransaction,
134134
docs: MutableDocumentMap,
135-
existenceStateChanged: DocumentKeySet
135+
existenceStateChanged: DocumentKeySet = documentKeySet()
136136
): PersistencePromise<DocumentMap> {
137137
const overlays = newOverlayMap();
138138
return this.populateOverlays(transaction, overlays, docs).next(() => {

packages/firestore/src/local/local_store_impl.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,32 @@ export function localStoreWriteLocally(
314314

315315
return localStoreImpl.persistence
316316
.runTransaction('Locally write mutations', 'readwrite', txn => {
317-
// Load and apply all existing mutations. This lets us compute the
318-
// current base state for all non-idempotent transforms before applying
319-
// any additional user-provided writes.
320-
return localStoreImpl.localDocuments
321-
.getDocuments(txn, keys)
317+
// Figure out which keys do not have a remote version in the cache, this
318+
// is needed to create the right overlay mutation: if no remote version
319+
// presents, we do not need to create overlays as patch mutations.
320+
// TODO(Overlay): Is there a better way to determine this? Document
321+
// version does not work because local mutations set them back to 0.
322+
let remoteDocs = mutableDocumentMap();
323+
let docsWithoutRemoteVersion = documentKeySet();
324+
return localStoreImpl.remoteDocuments
325+
.getEntries(txn, keys)
326+
.next(docs => {
327+
remoteDocs = docs;
328+
remoteDocs.forEach((key, doc) => {
329+
if (!doc.isValidDocument()) {
330+
docsWithoutRemoteVersion = docsWithoutRemoteVersion.add(key);
331+
}
332+
});
333+
})
334+
.next(() => {
335+
// Load and apply all existing mutations. This lets us compute the
336+
// current base state for all non-idempotent transforms before applying
337+
// any additional user-provided writes.
338+
return localStoreImpl.localDocuments.getLocalViewOfDocuments(
339+
txn,
340+
remoteDocs
341+
);
342+
})
322343
.next(docs => {
323344
existingDocs = docs;
324345

@@ -358,7 +379,10 @@ export function localStoreWriteLocally(
358379
})
359380
.next(batch => {
360381
mutationBatch = batch;
361-
const overlays = batch.applyToLocalDocumentSet(existingDocs);
382+
const overlays = batch.applyToLocalDocumentSet(
383+
existingDocs,
384+
docsWithoutRemoteVersion
385+
);
362386
return localStoreImpl.documentOverlayCache.saveOverlays(
363387
txn,
364388
batch.batchId,

packages/firestore/src/model/mutation_batch.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ export class MutationBatch {
138138
* this batch. Returns a `DocumentKey` to `Mutation` map which can be used to
139139
* replace all the mutation applications.
140140
*/
141-
applyToLocalDocumentSet(documentMap: DocumentMap): MutationMap {
141+
applyToLocalDocumentSet(
142+
documentMap: DocumentMap,
143+
documentsWithoutRemoteVersion: DocumentKeySet
144+
): MutationMap {
142145
// TODO(mrschmidt): This implementation is O(n^2). If we apply the mutations
143146
// directly (as done in `applyToLocalView()`), we can reduce the complexity
144147
// to O(n).
@@ -148,7 +151,13 @@ export class MutationBatch {
148151
// TODO(mutabledocuments): This method should take a MutableDocumentMap
149152
// and we should remove this cast.
150153
const mutableDocument = document as MutableDocument;
151-
const mutatedFields = this.applyToLocalView(mutableDocument);
154+
let mutatedFields = this.applyToLocalView(mutableDocument);
155+
// Set mutatedFields to null if the document is only from local mutations.
156+
// This creates a Set or Delete mutation, instead of trying to create a
157+
// patch mutation as the overlay.
158+
mutatedFields = documentsWithoutRemoteVersion.has(m.key)
159+
? null
160+
: mutatedFields;
152161
const overlay = calculateOverlayMutation(mutableDocument, mutatedFields);
153162
if (overlay !== null) {
154163
overlays.set(m.key, overlay);

packages/firestore/test/unit/local/local_store.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@ function genericLocalStoreTests(
949949
.toContain(deletedDoc('foo/bar', 0).setHasLocalMutations())
950950
.afterAcknowledgingMutation({ documentVersion: 2 }) // delete mutation
951951
.toReturnRemoved('foo/bar')
952-
.toContain(deletedDoc('foo/bar', 2).setHasCommittedMutations())
952+
.toContain(deletedDoc('foo/bar', 2).setHasLocalMutations())
953953
.afterAcknowledgingMutation({ documentVersion: 3 }) // patch mutation
954954
.toReturnChanged(unknownDoc('foo/bar', 3))
955955
.toNotContainIfEager(unknownDoc('foo/bar', 3))
@@ -1656,6 +1656,27 @@ function genericLocalStoreTests(
16561656
.finish();
16571657
});
16581658

1659+
it('add then update while offline', () => {
1660+
return expectLocalStore()
1661+
.afterMutations([
1662+
setMutation('foo/bar', { 'foo': 'foo-value', 'bar': 1 })
1663+
])
1664+
.toContain(
1665+
doc('foo/bar', 0, {
1666+
'foo': 'foo-value',
1667+
'bar': 1
1668+
}).setHasLocalMutations()
1669+
)
1670+
.afterMutations([patchMutation('foo/bar', { 'bar': 2 })])
1671+
.toContain(
1672+
doc('foo/bar', 0, {
1673+
'foo': 'foo-value',
1674+
'bar': 2
1675+
}).setHasLocalMutations()
1676+
)
1677+
.finish();
1678+
});
1679+
16591680
it('handles saving and loading named queries', async () => {
16601681
return expectLocalStore()
16611682
.after(

0 commit comments

Comments
 (0)