Skip to content

Commit 5c0f138

Browse files
authored
Merge 1723723 into 736385e
2 parents 736385e + 1723723 commit 5c0f138

File tree

7 files changed

+291
-45
lines changed

7 files changed

+291
-45
lines changed

packages/firestore/src/local/indexeddb_schema_converter.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@
1818
import { User } from '../auth/user';
1919
import { ListenSequence } from '../core/listen_sequence';
2020
import { SnapshotVersion } from '../core/snapshot_version';
21-
import { DocumentKeySet, documentKeySet } from '../model/collections';
21+
import {
22+
DocumentKeyMap,
23+
DocumentKeySet,
24+
documentKeySet
25+
} from '../model/collections';
2226
import { DocumentKey } from '../model/document_key';
27+
import { FieldMask } from '../model/field_mask';
2328
import { ResourcePath } from '../model/path';
2429
import { debugAssert, fail, hardAssert } from '../util/assert';
2530
import { BATCHID_UNKNOWN } from '../util/types';
@@ -484,7 +489,9 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
484489
this.serializer.remoteSerializer
485490
);
486491

487-
const promises: Array<PersistencePromise<void>> = [];
492+
const promises: Array<
493+
PersistencePromise<DocumentKeyMap<FieldMask | null>>
494+
> = [];
488495
const userToDocumentSet = new Map<string, DocumentKeySet>();
489496

490497
return mutationsStore

packages/firestore/src/local/local_documents_view.ts

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import {
3333
newOverlayMap,
3434
documentMap,
3535
mutableDocumentMap,
36-
documentKeySet
36+
documentKeySet,
37+
DocumentKeyMap
3738
} from '../model/collections';
3839
import { Document, MutableDocument } from '../model/document';
3940
import { DocumentKey } from '../model/document_key';
@@ -52,6 +53,7 @@ import { SortedMap } from '../util/sorted_map';
5253
import { DocumentOverlayCache } from './document_overlay_cache';
5354
import { IndexManager } from './index_manager';
5455
import { MutationQueue } from './mutation_queue';
56+
import { OverlayedDocument } from './overlayed_document';
5557
import { PersistencePromise } from './persistence_promise';
5658
import { PersistenceTransaction } from './persistence_transaction';
5759
import { RemoteDocumentCache } from './remote_document_cache';
@@ -92,7 +94,7 @@ export class LocalDocumentsView {
9294
mutationApplyToLocalView(
9395
overlay.mutation,
9496
document,
95-
null,
97+
FieldMask.empty(),
9698
Timestamp.now()
9799
);
98100
}
@@ -141,10 +143,34 @@ export class LocalDocumentsView {
141143
docs,
142144
overlays,
143145
existenceStateChanged
144-
);
146+
).next(computeViewsResult => {
147+
let result = documentMap();
148+
computeViewsResult.forEach((documentKey, overlayedDocument) => {
149+
result = result.insert(
150+
documentKey,
151+
overlayedDocument.overlayedDocument
152+
);
153+
});
154+
return result;
155+
});
145156
});
146157
}
147158

159+
/**
160+
* Gets the overlayed documents for the given document map, which will include
161+
* the local view of those documents and a `FieldMask` indicating which fields
162+
* are mutated locally, `null` if overlay is a Set or Delete mutation.
163+
*/
164+
getOverlayedDocuments(
165+
transaction: PersistenceTransaction,
166+
docs: MutableDocumentMap
167+
): PersistencePromise<DocumentKeyMap<OverlayedDocument>> {
168+
const overlays = newOverlayMap();
169+
return this.populateOverlays(transaction, overlays, docs).next(() =>
170+
this.computeViews(transaction, docs, overlays, documentKeySet())
171+
);
172+
}
173+
148174
/**
149175
* Fetches the overlays for {@code docs} and adds them to provided overlay map
150176
* if the map does not already contain an entry for the given document key.
@@ -170,17 +196,26 @@ export class LocalDocumentsView {
170196
}
171197

172198
/**
173-
* Computes the local view for documents, applying overlays from both
174-
* `memoizedOverlays` and the overlay cache.
199+
* Computes the local view for the given documents.
200+
*
201+
* @param docs - The documents to compute views for. It also has the base
202+
* version of the documents.
203+
* @param overlays - The overlays that need to be applied to the given base
204+
* version of the documents.
205+
* @param existenceStateChanged - A set of documents whose existence states
206+
* might have changed. This is used to determine if we need to re-calculate
207+
* overlays from mutation queues.
208+
* @return A map represents the local documents view.
175209
*/
176210
computeViews(
177211
transaction: PersistenceTransaction,
178212
docs: MutableDocumentMap,
179213
overlays: OverlayMap,
180214
existenceStateChanged: DocumentKeySet
181-
): PersistencePromise<DocumentMap> {
182-
let results = documentMap();
215+
): PersistencePromise<DocumentKeyMap<OverlayedDocument>> {
183216
let recalculateDocuments = mutableDocumentMap();
217+
const mutatedFields = newDocumentKeyMap<FieldMask | null>();
218+
const results = newDocumentKeyMap<OverlayedDocument>();
184219
docs.forEach((_, doc) => {
185220
const overlay = overlays.get(doc.key);
186221
// Recalculate an overlay if the document's existence state changed due to
@@ -196,25 +231,40 @@ export class LocalDocumentsView {
196231
) {
197232
recalculateDocuments = recalculateDocuments.insert(doc.key, doc);
198233
} else if (overlay !== undefined) {
199-
mutationApplyToLocalView(overlay.mutation, doc, null, Timestamp.now());
234+
mutatedFields.set(doc.key, overlay.mutation.getFieldMask());
235+
mutationApplyToLocalView(
236+
overlay.mutation,
237+
doc,
238+
overlay.mutation.getFieldMask(),
239+
Timestamp.now()
240+
);
200241
}
201242
});
202243

203244
return this.recalculateAndSaveOverlays(
204245
transaction,
205246
recalculateDocuments
206-
).next(() => {
207-
docs.forEach((key, value) => {
208-
results = results.insert(key, value);
209-
});
247+
).next(recalculatedFields => {
248+
recalculatedFields.forEach((documentKey, mask) =>
249+
mutatedFields.set(documentKey, mask)
250+
);
251+
docs.forEach((documentKey, document) =>
252+
results.set(
253+
documentKey,
254+
new OverlayedDocument(
255+
document,
256+
mutatedFields.get(documentKey) ?? null
257+
)
258+
)
259+
);
210260
return results;
211261
});
212262
}
213263

214264
private recalculateAndSaveOverlays(
215265
transaction: PersistenceTransaction,
216266
docs: MutableDocumentMap
217-
): PersistencePromise<void> {
267+
): PersistencePromise<DocumentKeyMap<FieldMask | null>> {
218268
const masks = newDocumentKeyMap<FieldMask | null>();
219269
// A reverse lookup map from batch id to the documents within that batch.
220270
let documentsByBatchId = new SortedMap<number, DocumentKeySet>(
@@ -274,7 +324,8 @@ export class LocalDocumentsView {
274324
);
275325
}
276326
return PersistencePromise.waitFor(promises);
277-
});
327+
})
328+
.next(() => masks);
278329
}
279330

280331
/**
@@ -284,7 +335,7 @@ export class LocalDocumentsView {
284335
recalculateAndSaveOverlaysForDocumentKeys(
285336
transaction: PersistenceTransaction,
286337
documentKeys: DocumentKeySet
287-
): PersistencePromise<void> {
338+
): PersistencePromise<DocumentKeyMap<FieldMask | null>> {
288339
return this.remoteDocumentCache
289340
.getEntries(transaction, documentKeys)
290341
.next(docs => this.recalculateAndSaveOverlays(transaction, docs));
@@ -407,7 +458,7 @@ export class LocalDocumentsView {
407458
mutationApplyToLocalView(
408459
overlay.mutation,
409460
document,
410-
null,
461+
FieldMask.empty(),
411462
Timestamp.now()
412463
);
413464
}

packages/firestore/src/local/local_store_impl.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ import { canonifyTarget, Target, targetEquals } from '../core/target';
2828
import { BatchId, TargetId } from '../core/types';
2929
import { Timestamp } from '../lite-api/timestamp';
3030
import {
31+
DocumentKeyMap,
3132
documentKeySet,
3233
DocumentKeySet,
34+
documentMap,
3335
DocumentMap,
3436
mutableDocumentMap,
3537
MutableDocumentMap
@@ -75,6 +77,7 @@ import { LocalStore } from './local_store';
7577
import { LocalViewChanges } from './local_view_changes';
7678
import { LruGarbageCollector, LruResults } from './lru_garbage_collector';
7779
import { MutationQueue } from './mutation_queue';
80+
import { OverlayedDocument } from './overlayed_document';
7881
import { Persistence } from './persistence';
7982
import { PersistencePromise } from './persistence_promise';
8083
import { PersistenceTransaction } from './persistence_transaction';
@@ -315,7 +318,7 @@ export function localStoreWriteLocally(
315318
const localWriteTime = Timestamp.now();
316319
const keys = mutations.reduce((keys, m) => keys.add(m.key), documentKeySet());
317320

318-
let existingDocs: DocumentMap;
321+
let overlayedDocuments: DocumentKeyMap<OverlayedDocument>;
319322
let mutationBatch: MutationBatch;
320323

321324
return localStoreImpl.persistence
@@ -342,13 +345,13 @@ export function localStoreWriteLocally(
342345
// Load and apply all existing mutations. This lets us compute the
343346
// current base state for all non-idempotent transforms before applying
344347
// any additional user-provided writes.
345-
return localStoreImpl.localDocuments.getLocalViewOfDocuments(
348+
return localStoreImpl.localDocuments.getOverlayedDocuments(
346349
txn,
347350
remoteDocs
348351
);
349352
})
350353
.next(docs => {
351-
existingDocs = docs;
354+
overlayedDocuments = docs;
352355

353356
// For non-idempotent mutations (such as `FieldValue.increment()`),
354357
// we record the base state in a separate patch mutation. This is
@@ -360,7 +363,7 @@ export function localStoreWriteLocally(
360363
for (const mutation of mutations) {
361364
const baseValue = mutationExtractBaseValue(
362365
mutation,
363-
existingDocs.get(mutation.key)!
366+
overlayedDocuments.get(mutation.key)!.overlayedDocument
364367
);
365368
if (baseValue != null) {
366369
// NOTE: The base state should only be applied if there's some
@@ -387,7 +390,7 @@ export function localStoreWriteLocally(
387390
.next(batch => {
388391
mutationBatch = batch;
389392
const overlays = batch.applyToLocalDocumentSet(
390-
existingDocs,
393+
overlayedDocuments,
391394
docsWithoutRemoteVersion
392395
);
393396
return localStoreImpl.documentOverlayCache.saveOverlays(
@@ -398,7 +401,11 @@ export function localStoreWriteLocally(
398401
});
399402
})
400403
.then(() => {
401-
return { batchId: mutationBatch.batchId, changes: existingDocs };
404+
let documents = documentMap();
405+
overlayedDocuments.forEach(
406+
(key, val) => (documents = documents.insert(key, val.overlayedDocument))
407+
);
408+
return { batchId: mutationBatch.batchId, changes: documents };
402409
});
403410
}
404411

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Document } from '../model/document';
19+
import { FieldMask } from '../model/field_mask';
20+
21+
/**
22+
* Represents a local view (overlay) of a document, and the fields that are
23+
* locally mutated.
24+
*/
25+
export class OverlayedDocument {
26+
constructor(
27+
readonly overlayedDocument: Document,
28+
29+
/**
30+
* The fields that are locally mutated by patch mutations. If the overlayed
31+
* document is from set or delete mutations, this returns null.
32+
*/
33+
readonly mutatedFields: FieldMask | null
34+
) {}
35+
}

packages/firestore/src/model/mutation.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ export abstract class Mutation {
213213
abstract readonly key: DocumentKey;
214214
abstract readonly precondition: Precondition;
215215
abstract readonly fieldTransforms: FieldTransform[];
216+
/**
217+
* Returns a `FieldMask` representing the fields that will be changed by
218+
* applying this mutation. Returns `null` if the mutation will overwrite the
219+
* entire document.
220+
*/
221+
abstract getFieldMask(): FieldMask | null;
216222
}
217223

218224
/**
@@ -444,6 +450,10 @@ export class SetMutation extends Mutation {
444450
}
445451

446452
readonly type: MutationType = MutationType.Set;
453+
454+
getFieldMask(): FieldMask | null {
455+
return null;
456+
}
447457
}
448458

449459
function setMutationApplyToRemoteDocument(
@@ -516,6 +526,10 @@ export class PatchMutation extends Mutation {
516526
}
517527

518528
readonly type: MutationType = MutationType.Patch;
529+
530+
getFieldMask(): FieldMask | null {
531+
return this.fieldMask;
532+
}
519533
}
520534

521535
function patchMutationApplyToRemoteDocument(
@@ -670,6 +684,10 @@ export class DeleteMutation extends Mutation {
670684

671685
readonly type: MutationType = MutationType.Delete;
672686
readonly fieldTransforms: FieldTransform[] = [];
687+
688+
getFieldMask(): FieldMask | null {
689+
return null;
690+
}
673691
}
674692

675693
function deleteMutationApplyToRemoteDocument(
@@ -720,4 +738,8 @@ export class VerifyMutation extends Mutation {
720738

721739
readonly type: MutationType = MutationType.Verify;
722740
readonly fieldTransforms: FieldTransform[] = [];
741+
742+
getFieldMask(): FieldMask | null {
743+
return null;
744+
}
723745
}

0 commit comments

Comments
 (0)