Skip to content

Commit f94e186

Browse files
committed
Local docs, path
1 parent 65a53b2 commit f94e186

File tree

5 files changed

+94
-96
lines changed

5 files changed

+94
-96
lines changed

packages/firestore/src/local/indexeddb_mutation_queue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ export class IndexedDbMutationQueue implements MutationQueue {
373373
);
374374
const maxKey = DbDocumentMutation.prefixForPath(
375375
this.userId,
376-
documentKeys.first().path
376+
documentKeys.last().path
377377
);
378378
const keyRange = IDBKeyRange.bound(minKey, maxKey);
379379
let uniqueBatchIDs = new SortedSet<BatchId>(primitiveComparator);

packages/firestore/src/local/local_documents_view.ts

Lines changed: 49 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from '../model/collections';
2727
import { Document, MaybeDocument, NoDocument } from '../model/document';
2828
import { DocumentKey } from '../model/document_key';
29+
import { MutationBatch } from '../model/mutation_batch';
2930
import { ResourcePath } from '../model/path';
3031
import { fail } from '../util/assert';
3132

@@ -55,11 +56,25 @@ export class LocalDocumentsView {
5556
getDocument(
5657
transaction: PersistenceTransaction,
5758
key: DocumentKey
59+
): PersistencePromise<MaybeDocument | null> {
60+
return this.mutationQueue
61+
.getAllMutationBatchesAffectingDocumentKey(transaction, key)
62+
.next(batches => this.getDocumentInBatches(transaction, key, batches));
63+
}
64+
65+
/** Internal version of `getDocument` that allows reusing batches. */
66+
private getDocumentInBatches(
67+
transaction: PersistenceTransaction,
68+
key: DocumentKey,
69+
inBatches: MutationBatch[]
5870
): PersistencePromise<MaybeDocument | null> {
5971
return this.remoteDocumentCache
6072
.getEntry(transaction, key)
61-
.next(remoteDoc => {
62-
return this.computeLocalDocument(transaction, key, remoteDoc);
73+
.next(doc => {
74+
for (const batch of inBatches) {
75+
doc = batch.applyToLocalView(key, doc);
76+
}
77+
return doc;
6378
});
6479
}
6580

@@ -73,20 +88,23 @@ export class LocalDocumentsView {
7388
transaction: PersistenceTransaction,
7489
keys: DocumentKeySet
7590
): PersistencePromise<MaybeDocumentMap> {
76-
const promises = [] as Array<PersistencePromise<void>>;
77-
let results = maybeDocumentMap();
78-
keys.forEach(key => {
79-
promises.push(
80-
this.getDocument(transaction, key).next(maybeDoc => {
81-
// TODO(http://b/32275378): Don't conflate missing / deleted.
82-
if (!maybeDoc) {
83-
maybeDoc = new NoDocument(key, SnapshotVersion.forDeletedDoc());
84-
}
85-
results = results.insert(key, maybeDoc);
86-
})
87-
);
91+
return this.mutationQueue
92+
.getAllMutationBatchesAffectingDocumentKeys(transaction, keys).next(batches => {
93+
const promises = [] as Array<PersistencePromise<void>>;
94+
let results = maybeDocumentMap();
95+
keys.forEach(key => {
96+
promises.push(
97+
this.getDocumentInBatches(transaction, key, batches).next(maybeDoc => {
98+
// TODO(http://b/32275378): Don't conflate missing / deleted.
99+
if (!maybeDoc) {
100+
maybeDoc = new NoDocument(key, SnapshotVersion.forDeletedDoc());
101+
}
102+
results = results.insert(key, maybeDoc);
103+
})
104+
);
105+
});
106+
return PersistencePromise.waitFor(promises).next(() => results);
88107
});
89-
return PersistencePromise.waitFor(promises).next(() => results);
90108
}
91109

92110
/** Performs a query against the local view of all documents. */
@@ -122,48 +140,37 @@ export class LocalDocumentsView {
122140
query: Query
123141
): PersistencePromise<DocumentMap> {
124142
// Query the remote documents and overlay mutations.
125-
// TODO(mikelehen): There may be significant overlap between the mutations
126-
// affecting these remote documents and the
127-
// getAllMutationBatchesAffectingQuery() mutations. Consider optimizing.
128143
let results: DocumentMap;
129144
return this.remoteDocumentCache
130145
.getDocumentsMatchingQuery(transaction, query)
131146
.next(queryResults => {
132-
return this.computeLocalDocuments(transaction, queryResults);
133-
})
134-
.next(promisedResults => {
135-
results = promisedResults;
136-
// Now use the mutation queue to discover any other documents that may
137-
// match the query after applying mutations.
147+
results = queryResults;
138148
return this.mutationQueue.getAllMutationBatchesAffectingQuery(
139149
transaction,
140150
query
141151
);
142152
})
143153
.next(matchingMutationBatches => {
144-
let matchingKeys = documentKeySet();
145154
for (const batch of matchingMutationBatches) {
155+
// TODO(mikelehen): PERF: Check if this mutation actually affects the
156+
// query to reduce work.
146157
for (const mutation of batch.mutations) {
147-
// TODO(mikelehen): PERF: Check if this mutation actually
148-
// affects the query to reduce work.
149-
if (!results.get(mutation.key)) {
150-
matchingKeys = matchingKeys.add(mutation.key);
158+
if (!query.path.isImmediateParentOf(mutation.key.path)) {
159+
continue;
160+
}
161+
162+
const key = mutation.key;
163+
const baseDoc = results.get(key);
164+
const mutatedDoc = mutation.applyToLocalView(baseDoc, baseDoc, batch.localWriteTime);
165+
if (!mutatedDoc || mutatedDoc instanceof NoDocument) {
166+
results = results.remove(mutatedDoc.key);
167+
} else if (mutatedDoc instanceof Document) {
168+
results = results.insert(mutatedDoc.key, mutatedDoc);
169+
} else {
170+
fail('Unknown MaybeDocument: ' + mutatedDoc);
151171
}
152172
}
153173
}
154-
155-
// Now add in the results for the matchingKeys.
156-
const promises = [] as Array<PersistencePromise<void>>;
157-
matchingKeys.forEach(key => {
158-
promises.push(
159-
this.getDocument(transaction, key).next(doc => {
160-
if (doc instanceof Document) {
161-
results = results.insert(doc.key, doc);
162-
}
163-
})
164-
);
165-
});
166-
return PersistencePromise.waitFor(promises);
167174
})
168175
.next(() => {
169176
// Finally, filter out any documents that don't actually match
@@ -177,57 +184,4 @@ export class LocalDocumentsView {
177184
return results;
178185
});
179186
}
180-
181-
/**
182-
* Takes a remote document and applies local mutations to generate the local
183-
* view of the document.
184-
* @param transaction The transaction in which to perform any persistence
185-
* operations.
186-
* @param documentKey The key of the document (necessary when remoteDocument
187-
* is null).
188-
* @param document The base remote document to apply mutations to or null.
189-
*/
190-
private computeLocalDocument(
191-
transaction: PersistenceTransaction,
192-
documentKey: DocumentKey,
193-
document: MaybeDocument | null
194-
): PersistencePromise<MaybeDocument | null> {
195-
return this.mutationQueue
196-
.getAllMutationBatchesAffectingDocumentKey(transaction, documentKey)
197-
.next(batches => {
198-
for (const batch of batches) {
199-
document = batch.applyToLocalView(documentKey, document);
200-
}
201-
return document;
202-
});
203-
}
204-
205-
/**
206-
* Takes a set of remote documents and applies local mutations to generate the
207-
* local view of the documents.
208-
* @param transaction The transaction in which to perform any persistence
209-
* operations.
210-
* @param documents The base remote documents to apply mutations to.
211-
* @return The local view of the documents.
212-
*/
213-
private computeLocalDocuments(
214-
transaction: PersistenceTransaction,
215-
documents: DocumentMap
216-
): PersistencePromise<DocumentMap> {
217-
const promises = [] as Array<PersistencePromise<void>>;
218-
documents.forEach((key, doc) => {
219-
promises.push(
220-
this.computeLocalDocument(transaction, key, doc).next(mutatedDoc => {
221-
if (mutatedDoc instanceof Document) {
222-
documents = documents.insert(mutatedDoc.key, mutatedDoc);
223-
} else if (mutatedDoc instanceof NoDocument) {
224-
documents = documents.remove(mutatedDoc.key);
225-
} else {
226-
fail('Unknown MaybeDocument: ' + mutatedDoc);
227-
}
228-
})
229-
);
230-
});
231-
return PersistencePromise.waitFor(promises).next(() => documents);
232-
}
233187
}

packages/firestore/src/local/memory_mutation_queue.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { Timestamp } from '../api/timestamp';
1818
import { Query } from '../core/query';
1919
import { BatchId, ProtoByteString } from '../core/types';
20+
import { DocumentKeySet } from '../model/collections';
2021
import { DocumentKey } from '../model/document_key';
2122
import { Mutation } from '../model/mutation';
2223
import { BATCHID_UNKNOWN, MutationBatch } from '../model/mutation_batch';
@@ -252,6 +253,29 @@ export class MemoryMutationQueue implements MutationQueue {
252253
return PersistencePromise.resolve(result);
253254
}
254255

256+
getAllMutationBatchesAffectingDocumentKeys(
257+
transaction: PersistenceTransaction,
258+
documentKeys: DocumentKeySet
259+
): PersistencePromise<MutationBatch[]> {
260+
const start = new DocReference(documentKeys.first(), 0);
261+
const end = new DocReference(documentKeys.last(), 0);
262+
const result: MutationBatch[] = [];
263+
this.batchesByDocumentKey.forEachInRange([start, end], ref => {
264+
assert(
265+
documentKey.isEqual(ref.key),
266+
"Should only iterate over a single key's batches"
267+
);
268+
const batch = this.findMutationBatch(ref.targetOrBatchId);
269+
assert(
270+
batch !== null,
271+
'Batches in the index must exist in the main table'
272+
);
273+
result.push(batch!);
274+
});
275+
276+
return PersistencePromise.resolve(result);
277+
}
278+
255279
getAllMutationBatchesAffectingQuery(
256280
transaction: PersistenceTransaction,
257281
query: Query

packages/firestore/src/local/mutation_queue.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ export interface MutationQueue extends GarbageSource {
140140
* document key, so when looping through the batch you'll need to check that
141141
* the mutation itself matches the key.
142142
*
143+
* Batches are guaranteed to be in sorted order.
144+
*
143145
* Note that because of this requirement implementations are free to return
144146
* mutation batches that don't contain the document key at all if it's
145147
* convenient.
@@ -157,6 +159,8 @@ export interface MutationQueue extends GarbageSource {
157159
* each key, so when looping through the batch you'll need to
158160
* check that the mutation itself matches the key.
159161
*
162+
* Batches are guaranteed to be in sorted order.
163+
*
160164
* Note that because of this requirement implementations are free to return
161165
* mutation batches that don't contain any of the document keys at all if it's
162166
* convenient.
@@ -174,6 +178,8 @@ export interface MutationQueue extends GarbageSource {
174178
* when looping through the batch you'll need to check that the mutation
175179
* itself matches the query.
176180
*
181+
* Batches are guaranteed to be in sorted order.
182+
*
177183
* Note that because of this requirement implementations are free to return
178184
* mutation batches that don't match the query at all if it's convenient.
179185
*

packages/firestore/src/model/path.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,20 @@ export abstract class Path {
143143
return true;
144144
}
145145

146+
isImmediateParentOf(potentialChild: this): boolean {
147+
if (this.length + 1 !== potentialChild.length) {
148+
return false;
149+
}
150+
151+
for (let i = 0; i < this.length; i++) {
152+
if (this.get(i) !== potentialChild.get(i)) {
153+
return false;
154+
}
155+
}
156+
157+
return true;
158+
}
159+
146160
forEach(fn: (segment: string) => void): void {
147161
for (let i = this.offset, end = this.limit(); i < end; i++) {
148162
fn(this.segments[i]);

0 commit comments

Comments
 (0)