Skip to content

Commit 39d0238

Browse files
committed
Integrate Document Overlay with the SDK.
1 parent 09e6243 commit 39d0238

13 files changed

+1231
-225
lines changed

packages/firestore/src/local/local_documents_view.ts

Lines changed: 257 additions & 81 deletions
Large diffs are not rendered by default.

packages/firestore/src/local/local_store_impl.ts

Lines changed: 96 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { SortedMap } from '../util/sorted_map';
5555
import { BATCHID_UNKNOWN } from '../util/types';
5656

5757
import { BundleCache } from './bundle_cache';
58+
import { DocumentOverlayCache } from './document_overlay_cache';
5859
import { IndexManager } from './index_manager';
5960
import { IndexedDbMutationQueue } from './indexeddb_mutation_queue';
6061
import { IndexedDbPersistence } from './indexeddb_persistence';
@@ -125,6 +126,12 @@ class LocalStoreImpl implements LocalStore {
125126
*/
126127
mutationQueue!: MutationQueue;
127128

129+
/**
130+
* The overlays that can be used to short circuit applying all mutations from
131+
* mutation queue.
132+
*/
133+
documentOverlayCache!: DocumentOverlayCache;
134+
128135
/** The set of all cached remote documents. */
129136
remoteDocuments: RemoteDocumentCache;
130137

@@ -186,6 +193,7 @@ class LocalStoreImpl implements LocalStore {
186193
initializeUserComponents(user: User): void {
187194
// TODO(indexing): Add spec tests that test these components change after a
188195
// user change
196+
this.documentOverlayCache = this.persistence.getDocumentOverlayCache(user);
189197
this.indexManager = this.persistence.getIndexManager(user);
190198
this.mutationQueue = this.persistence.getMutationQueue(
191199
user,
@@ -194,6 +202,7 @@ class LocalStoreImpl implements LocalStore {
194202
this.localDocuments = new LocalDocumentsView(
195203
this.remoteDocuments,
196204
this.mutationQueue,
205+
this.documentOverlayCache,
197206
this.indexManager
198207
);
199208
this.remoteDocuments.setIndexManager(this.indexManager);
@@ -209,6 +218,13 @@ class LocalStoreImpl implements LocalStore {
209218
}
210219
}
211220

221+
class DocumentChangeResult {
222+
constructor(
223+
readonly changedDocuments: MutableDocumentMap,
224+
readonly existenceChangedKeys: DocumentKeySet
225+
) {}
226+
}
227+
212228
export function newLocalStore(
213229
/** Manages our in-memory or durable persistence. */
214230
persistence: Persistence,
@@ -296,6 +312,7 @@ export function localStoreWriteLocally(
296312
const keys = mutations.reduce((keys, m) => keys.add(m.key), documentKeySet());
297313

298314
let existingDocs: DocumentMap;
315+
let mutationBatch: MutationBatch;
299316

300317
return localStoreImpl.persistence
301318
.runTransaction('Locally write mutations', 'readwrite', txn => {
@@ -340,11 +357,19 @@ export function localStoreWriteLocally(
340357
baseMutations,
341358
mutations
342359
);
360+
})
361+
.next(batch => {
362+
mutationBatch = batch;
363+
const overlays = batch.applyToLocalDocumentSet(existingDocs);
364+
return localStoreImpl.documentOverlayCache.saveOverlays(
365+
txn,
366+
batch.batchId,
367+
overlays
368+
);
343369
});
344370
})
345-
.then(batch => {
346-
batch.applyToLocalDocumentSet(existingDocs);
347-
return { batchId: batch.batchId, changes: existingDocs };
371+
.then(() => {
372+
return { batchId: mutationBatch.batchId, changes: existingDocs };
348373
});
349374
}
350375

@@ -383,11 +408,38 @@ export function localStoreAcknowledgeBatch(
383408
)
384409
.next(() => documentBuffer.apply(txn))
385410
.next(() => localStoreImpl.mutationQueue.performConsistencyCheck(txn))
411+
.next(() =>
412+
localStoreImpl.documentOverlayCache.removeOverlaysForBatchId(
413+
txn,
414+
affected,
415+
batchResult.batch.batchId
416+
)
417+
)
418+
.next(() =>
419+
localStoreImpl.localDocuments.recalculateAndSaveOverlaysForDocumentKeys(
420+
txn,
421+
getKeysWithTransformResults(batchResult)
422+
)
423+
)
386424
.next(() => localStoreImpl.localDocuments.getDocuments(txn, affected));
387425
}
388426
);
389427
}
390428

429+
function getKeysWithTransformResults(
430+
batchResult: MutationBatchResult
431+
): DocumentKeySet {
432+
let result = documentKeySet();
433+
434+
for (let i = 0; i < batchResult.mutationResults.length; ++i) {
435+
const mutationResult = batchResult.mutationResults[i];
436+
if (mutationResult.transformResults.length > 0) {
437+
result = result.add(batchResult.batch.mutations[i].key);
438+
}
439+
}
440+
return result;
441+
}
442+
391443
/**
392444
* Removes mutations from the MutationQueue for the specified batch;
393445
* LocalDocuments will be recalculated.
@@ -412,6 +464,19 @@ export function localStoreRejectBatch(
412464
return localStoreImpl.mutationQueue.removeMutationBatch(txn, batch);
413465
})
414466
.next(() => localStoreImpl.mutationQueue.performConsistencyCheck(txn))
467+
.next(() =>
468+
localStoreImpl.documentOverlayCache.removeOverlaysForBatchId(
469+
txn,
470+
affectedKeys,
471+
batchId
472+
)
473+
)
474+
.next(() =>
475+
localStoreImpl.localDocuments.recalculateAndSaveOverlaysForDocumentKeys(
476+
txn,
477+
affectedKeys
478+
)
479+
)
415480
.next(() =>
416481
localStoreImpl.localDocuments.getDocuments(txn, affectedKeys)
417482
);
@@ -530,6 +595,7 @@ export function localStoreApplyRemoteEventToLocalCache(
530595
});
531596

532597
let changedDocs = mutableDocumentMap();
598+
let existenceChangedKeys = documentKeySet();
533599
remoteEvent.documentUpdates.forEach(key => {
534600
if (remoteEvent.resolvedLimboDocuments.has(key)) {
535601
promises.push(
@@ -541,15 +607,16 @@ export function localStoreApplyRemoteEventToLocalCache(
541607
}
542608
});
543609

544-
// Each loop iteration only affects its "own" doc, so it's safe to get all the remote
545-
// documents in advance in a single call.
610+
// Each loop iteration only affects its "own" doc, so it's safe to get all
611+
// the remote documents in advance in a single call.
546612
promises.push(
547613
populateDocumentChangeBuffer(
548614
txn,
549615
documentBuffer,
550616
remoteEvent.documentUpdates
551617
).next(result => {
552-
changedDocs = result;
618+
changedDocs = result.changedDocuments;
619+
existenceChangedKeys = result.existenceChangedKeys;
553620
})
554621
);
555622

@@ -580,9 +647,10 @@ export function localStoreApplyRemoteEventToLocalCache(
580647
return PersistencePromise.waitFor(promises)
581648
.next(() => documentBuffer.apply(txn))
582649
.next(() =>
583-
localStoreImpl.localDocuments.applyLocalViewToDocuments(
650+
localStoreImpl.localDocuments.getLocalViewOfDocuments(
584651
txn,
585-
changedDocs
652+
changedDocs,
653+
existenceChangedKeys
586654
)
587655
)
588656
.next(() => changedDocs);
@@ -595,7 +663,11 @@ export function localStoreApplyRemoteEventToLocalCache(
595663

596664
/**
597665
* Populates document change buffer with documents from backend or a bundle.
598-
* Returns the document changes resulting from applying those documents.
666+
* Returns the document changes resulting from applying those documents, and
667+
* also a set of documents whose existence state are changed as a result.
668+
*
669+
* Note: this function will use `documentVersions` if it is defined;
670+
* when it is not defined, resorts to `globalVersion`.
599671
*
600672
* @param txn - Transaction to use to read existing documents from storage.
601673
* @param documentBuffer - Document buffer to collect the resulted changes to be
@@ -605,22 +677,25 @@ export function localStoreApplyRemoteEventToLocalCache(
605677
* documents have the same read time.
606678
* @param documentVersions - A DocumentKey-to-SnapshotVersion map if documents
607679
* have their own read time.
608-
*
609-
* Note: this function will use `documentVersions` if it is defined;
610-
* when it is not defined, resorts to `globalVersion`.
611680
*/
612681
function populateDocumentChangeBuffer(
613682
txn: PersistenceTransaction,
614683
documentBuffer: RemoteDocumentChangeBuffer,
615684
documents: MutableDocumentMap
616-
): PersistencePromise<MutableDocumentMap> {
685+
): PersistencePromise<DocumentChangeResult> {
617686
let updatedKeys = documentKeySet();
687+
let conditionChanged = documentKeySet();
618688
documents.forEach(k => (updatedKeys = updatedKeys.add(k)));
619689
return documentBuffer.getEntries(txn, updatedKeys).next(existingDocs => {
620690
let changedDocs = mutableDocumentMap();
621691
documents.forEach((key, doc) => {
622692
const existingDoc = existingDocs.get(key)!;
623693

694+
// Check if see if there is a existence state change for this document.
695+
if (doc.isFoundDocument() !== existingDoc.isFoundDocument()) {
696+
conditionChanged = conditionChanged.add(key);
697+
}
698+
624699
// Note: The order of the steps below is important, since we want
625700
// to ensure that rejected limbo resolutions (which fabricate
626701
// NoDocuments with SnapshotVersion.min()) never add documents to
@@ -655,7 +730,7 @@ function populateDocumentChangeBuffer(
655730
);
656731
}
657732
});
658-
return changedDocs;
733+
return new DocumentChangeResult(changedDocs, conditionChanged);
659734
});
660735
}
661736

@@ -1225,11 +1300,11 @@ export async function localStoreApplyBundledDocuments(
12251300
'readwrite',
12261301
txn => {
12271302
return populateDocumentChangeBuffer(txn, documentBuffer, documentMap)
1228-
.next(changedDocs => {
1303+
.next(documentChangeResult => {
12291304
documentBuffer.apply(txn);
1230-
return changedDocs;
1305+
return documentChangeResult;
12311306
})
1232-
.next(changedDocs => {
1307+
.next(documentChangeResult => {
12331308
return localStoreImpl.targetCache
12341309
.removeMatchingKeysForTargetId(txn, umbrellaTargetData.targetId)
12351310
.next(() =>
@@ -1240,12 +1315,13 @@ export async function localStoreApplyBundledDocuments(
12401315
)
12411316
)
12421317
.next(() =>
1243-
localStoreImpl.localDocuments.applyLocalViewToDocuments(
1318+
localStoreImpl.localDocuments.getLocalViewOfDocuments(
12441319
txn,
1245-
changedDocs
1320+
documentChangeResult.changedDocuments,
1321+
documentChangeResult.existenceChangedKeys
12461322
)
12471323
)
1248-
.next(() => changedDocs);
1324+
.next(() => documentChangeResult.changedDocuments);
12491325
});
12501326
}
12511327
);

packages/firestore/src/local/query_engine.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import {
2828
import { SnapshotVersion } from '../core/snapshot_version';
2929
import { DocumentKeySet, DocumentMap } from '../model/collections';
3030
import { Document } from '../model/document';
31+
import {
32+
IndexOffset,
33+
newIndexOffsetSuccessorFromReadTime
34+
} from '../model/field_index';
3135
import { debugAssert } from '../util/assert';
3236
import { getLogLevel, LogLevel, logDebug } from '../util/log';
3337
import { SortedSet } from '../util/sorted_set';
@@ -117,7 +121,7 @@ export class QueryEngine {
117121
return this.localDocumentsView!.getDocumentsMatchingQuery(
118122
transaction,
119123
query,
120-
lastLimboFreeSnapshotVersion
124+
newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, -1)
121125
).next(updatedResults => {
122126
// We merge `previousResults` into `updateResults`, since
123127
// `updateResults` is already a DocumentMap. If a document is
@@ -207,7 +211,7 @@ export class QueryEngine {
207211
return this.localDocumentsView!.getDocumentsMatchingQuery(
208212
transaction,
209213
query,
210-
SnapshotVersion.min()
214+
IndexOffset.min()
211215
);
212216
}
213217
}

packages/firestore/src/model/collections.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ export function newMutationMap(): MutationMap {
6666
);
6767
}
6868

69+
export type DocumentKeyMap<T> = ObjectMap<DocumentKey, T>;
70+
export function newDocumentKeyMap<T>(): DocumentKeyMap<T> {
71+
return new ObjectMap<DocumentKey, T>(
72+
key => key.toString(),
73+
(l, r) => l.isEqual(r)
74+
);
75+
}
76+
6977
export type DocumentVersionMap = SortedMap<DocumentKey, SnapshotVersion>;
7078
const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap<DocumentKey, SnapshotVersion>(
7179
DocumentKey.comparator

packages/firestore/src/model/document.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,8 @@ export class MutableDocument implements Document {
285285
}
286286

287287
setHasLocalMutations(): MutableDocument {
288-
debugAssert(
289-
this.isFoundDocument(),
290-
'Only found documents can have local mutations'
291-
);
292288
this.documentState = DocumentState.HAS_LOCAL_MUTATIONS;
289+
this.version = SnapshotVersion.min();
293290
return this;
294291
}
295292

packages/firestore/src/model/field_mask.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export class FieldMask {
4242
);
4343
}
4444

45+
static empty(): FieldMask {
46+
return new FieldMask([]);
47+
}
48+
4549
/**
4650
* Verifies that `fieldPath` is included by at least one field in this field
4751
* mask.

0 commit comments

Comments
 (0)