Skip to content

Commit f1d3267

Browse files
authored
Merge 555f3c9 into bb8f37c
2 parents bb8f37c + 555f3c9 commit f1d3267

15 files changed

+1151
-9
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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 { DocumentKeySet } from '../model/collections';
19+
import { DocumentKey } from '../model/document_key';
20+
import { Mutation } from '../model/mutation';
21+
import { Overlay } from '../model/overlay';
22+
import { ResourcePath } from '../model/path';
23+
24+
import { PersistencePromise } from './persistence_promise';
25+
import { PersistenceTransaction } from './persistence_transaction';
26+
27+
/**
28+
* Provides methods to read and write document overlays.
29+
*
30+
* An overlay is a saved mutation, that gives a local view of a document when
31+
* applied to the remote version of the document.
32+
*
33+
* Each overlay stores the largest batch ID that is included in the overlay,
34+
* which allows us to remove the overlay once all batches leading up to it have
35+
* been acknowledged.
36+
*/
37+
export interface DocumentOverlayCache {
38+
/**
39+
* Gets the saved overlay mutation for the given document key.
40+
* Returns null if there is no overlay for that key.
41+
*/
42+
getOverlay(
43+
transaction: PersistenceTransaction,
44+
key: DocumentKey
45+
): PersistencePromise<Overlay | null>;
46+
47+
/**
48+
* Saves the given document mutation map to persistence as overlays.
49+
* All overlays will have their largest batch id set to `largestBatchId`.
50+
*/
51+
saveOverlays(
52+
transaction: PersistenceTransaction,
53+
largestBatchId: number,
54+
overlays: Map<DocumentKey, Mutation>
55+
): PersistencePromise<void>;
56+
57+
/** Removes overlays for the given document keys and batch ID. */
58+
removeOverlaysForBatchId(
59+
transaction: PersistenceTransaction,
60+
documentKeys: DocumentKeySet,
61+
batchId: number
62+
): PersistencePromise<void>;
63+
64+
/**
65+
* Returns all saved overlays for the given collection.
66+
*
67+
* @param transaction - The persistence transaction to use for this operation.
68+
* @param collection - The collection path to get the overlays for.
69+
* @param sinceBatchId - The minimum batch ID to filter by (exclusive).
70+
* Only overlays that contain a change past `sinceBatchId` are returned.
71+
* @returns Mapping of each document key in the collection to its overlay.
72+
*/
73+
getOverlaysForCollection(
74+
transaction: PersistenceTransaction,
75+
collection: ResourcePath,
76+
sinceBatchId: number
77+
): PersistencePromise<Map<DocumentKey, Overlay>>;
78+
79+
/**
80+
* Returns `count` overlays with a batch ID higher than `sinceBatchId` for the
81+
* provided collection group, processed by ascending batch ID. The method
82+
* always returns all overlays for a batch even if the last batch contains
83+
* more documents than the remaining limit.
84+
*
85+
* @param transaction - The persistence transaction used for this operation.
86+
* @param collectionGroup - The collection group to get the overlays for.
87+
* @param sinceBatchId - The minimum batch ID to filter by (exclusive).
88+
* Only overlays that contain a change past `sinceBatchId` are returned.
89+
* @param count - The number of overlays to return. Can be exceeded if the last
90+
* batch contains more entries.
91+
* @return Mapping of each document key in the collection group to its overlay.
92+
*/
93+
getOverlaysForCollectionGroup(
94+
transaction: PersistenceTransaction,
95+
collectionGroup: string,
96+
sinceBatchId: number,
97+
count: number
98+
): PersistencePromise<Map<DocumentKey, Overlay>>;
99+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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 { User } from '../auth/user';
19+
import { DocumentKeySet } from '../model/collections';
20+
import { DocumentKey } from '../model/document_key';
21+
import { Mutation } from '../model/mutation';
22+
import { Overlay } from '../model/overlay';
23+
import { ResourcePath } from '../model/path';
24+
25+
import { DocumentOverlayCache } from './document_overlay_cache';
26+
import { encodeResourcePath } from './encoded_resource_path';
27+
import { DbDocumentOverlay, DbDocumentOverlayKey } from './indexeddb_schema';
28+
import { getStore } from './indexeddb_transaction';
29+
import {
30+
fromDbDocumentOverlay,
31+
LocalSerializer,
32+
toDbDocumentOverlay,
33+
toDbDocumentOverlayKey
34+
} from './local_serializer';
35+
import { PersistencePromise } from './persistence_promise';
36+
import { PersistenceTransaction } from './persistence_transaction';
37+
import { SimpleDbStore } from './simple_db';
38+
39+
/**
40+
* Implementation of DocumentOverlayCache using IndexedDb.
41+
*/
42+
export class IndexedDbDocumentOverlayCache implements DocumentOverlayCache {
43+
/**
44+
* @param serializer - The document serializer.
45+
* @param userId - The userId for which we are accessing overlays.
46+
*/
47+
constructor(
48+
private readonly serializer: LocalSerializer,
49+
private readonly userId: string
50+
) {}
51+
52+
static forUser(
53+
serializer: LocalSerializer,
54+
user: User
55+
): IndexedDbDocumentOverlayCache {
56+
const userId = user.uid || '';
57+
return new IndexedDbDocumentOverlayCache(serializer, userId);
58+
}
59+
60+
getOverlay(
61+
transaction: PersistenceTransaction,
62+
key: DocumentKey
63+
): PersistencePromise<Overlay | null> {
64+
return documentOverlayStore(transaction)
65+
.get(toDbDocumentOverlayKey(this.userId, key))
66+
.next(dbOverlay => {
67+
if (dbOverlay) {
68+
return fromDbDocumentOverlay(this.serializer, dbOverlay);
69+
}
70+
return null;
71+
});
72+
}
73+
74+
saveOverlays(
75+
transaction: PersistenceTransaction,
76+
largestBatchId: number,
77+
overlays: Map<DocumentKey, Mutation>
78+
): PersistencePromise<void> {
79+
const promises: Array<PersistencePromise<void>> = [];
80+
overlays.forEach(mutation => {
81+
const overlay = new Overlay(largestBatchId, mutation);
82+
promises.push(this.saveOverlay(transaction, overlay));
83+
});
84+
return PersistencePromise.waitFor(promises);
85+
}
86+
87+
removeOverlaysForBatchId(
88+
transaction: PersistenceTransaction,
89+
documentKeys: DocumentKeySet,
90+
batchId: number
91+
): PersistencePromise<void> {
92+
const collectionPaths = new Set<string>();
93+
94+
// Get the set of unique collection paths.
95+
documentKeys.forEach(key =>
96+
collectionPaths.add(encodeResourcePath(key.getCollectionPath()))
97+
);
98+
99+
const promises: Array<PersistencePromise<void>> = [];
100+
collectionPaths.forEach(collectionPath => {
101+
const range = IDBKeyRange.bound(
102+
[this.userId, collectionPath, batchId],
103+
[this.userId, collectionPath, batchId + 1],
104+
/*lowerOpen=*/ false,
105+
/*upperOpen=*/ true
106+
);
107+
promises.push(
108+
documentOverlayStore(transaction).deleteAll(
109+
DbDocumentOverlay.collectionPathOverlayIndex,
110+
range
111+
)
112+
);
113+
});
114+
return PersistencePromise.waitFor(promises);
115+
}
116+
117+
getOverlaysForCollection(
118+
transaction: PersistenceTransaction,
119+
collection: ResourcePath,
120+
sinceBatchId: number
121+
): PersistencePromise<Map<DocumentKey, Overlay>> {
122+
const result = new Map<DocumentKey, Overlay>();
123+
const collectionPath = encodeResourcePath(collection);
124+
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
125+
// is not inclusive.
126+
const range = IDBKeyRange.bound(
127+
[this.userId, collectionPath, sinceBatchId],
128+
[this.userId, collectionPath, Number.POSITIVE_INFINITY],
129+
/*lowerOpen=*/ true
130+
);
131+
return documentOverlayStore(transaction)
132+
.loadAll(DbDocumentOverlay.collectionPathOverlayIndex, range)
133+
.next(dbOverlays => {
134+
for (const dbOverlay of dbOverlays) {
135+
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
136+
result.set(overlay.getKey(), overlay);
137+
}
138+
return result;
139+
});
140+
}
141+
142+
getOverlaysForCollectionGroup(
143+
transaction: PersistenceTransaction,
144+
collectionGroup: string,
145+
sinceBatchId: number,
146+
count: number
147+
): PersistencePromise<Map<DocumentKey, Overlay>> {
148+
const result = new Map<DocumentKey, Overlay>();
149+
let currentBatchId: number | undefined = undefined;
150+
// We want batch IDs larger than `sinceBatchId`, and so the lower bound
151+
// is not inclusive.
152+
const range = IDBKeyRange.bound(
153+
[this.userId, collectionGroup, sinceBatchId],
154+
[this.userId, collectionGroup, Number.POSITIVE_INFINITY],
155+
/*lowerOpen=*/ true
156+
);
157+
return documentOverlayStore(transaction)
158+
.iterate(
159+
{
160+
index: DbDocumentOverlay.collectionGroupOverlayIndex,
161+
range
162+
},
163+
(_, dbOverlay, control) => {
164+
// We do not want to return partial batch overlays, even if the size
165+
// of the result set exceeds the given `count` argument. Therefore, we
166+
// continue to aggregate results even after the result size exceeds
167+
// `count` if there are more overlays from the `currentBatchId`.
168+
const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
169+
if (
170+
result.size < count ||
171+
overlay.largestBatchId === currentBatchId
172+
) {
173+
result.set(overlay.getKey(), overlay);
174+
currentBatchId = overlay.largestBatchId;
175+
} else {
176+
control.done();
177+
}
178+
}
179+
)
180+
.next(() => result);
181+
}
182+
183+
private saveOverlay(
184+
transaction: PersistenceTransaction,
185+
overlay: Overlay
186+
): PersistencePromise<void> {
187+
return documentOverlayStore(transaction).put(
188+
toDbDocumentOverlay(this.serializer, this.userId, overlay)
189+
);
190+
}
191+
}
192+
193+
/**
194+
* Helper to get a typed SimpleDbStore for the document overlay object store.
195+
*/
196+
function documentOverlayStore(
197+
txn: PersistenceTransaction
198+
): SimpleDbStore<DbDocumentOverlayKey, DbDocumentOverlay> {
199+
return getStore<DbDocumentOverlayKey, DbDocumentOverlay>(
200+
txn,
201+
DbDocumentOverlay.store
202+
);
203+
}

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ import { logDebug, logError } from '../util/log';
2828
import { DocumentLike, WindowLike } from '../util/types';
2929

3030
import { BundleCache } from './bundle_cache';
31+
import { DocumentOverlayCache } from './document_overlay_cache';
3132
import { IndexManager } from './index_manager';
3233
import { IndexedDbBundleCache } from './indexeddb_bundle_cache';
34+
import { IndexedDbDocumentOverlayCache } from './indexeddb_document_overlay_cache';
3335
import { IndexedDbIndexManager } from './indexeddb_index_manager';
3436
import { IndexedDbLruDelegateImpl } from './indexeddb_lru_delegate_impl';
3537
import { IndexedDbMutationQueue } from './indexeddb_mutation_queue';
@@ -744,6 +746,14 @@ export class IndexedDbPersistence implements Persistence {
744746
return new IndexedDbIndexManager(user);
745747
}
746748

749+
getDocumentOverlayCache(user: User): DocumentOverlayCache {
750+
debugAssert(
751+
this.started,
752+
'Cannot initialize IndexedDbDocumentOverlayCache before persistence is started.'
753+
);
754+
return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
755+
}
756+
747757
getBundleCache(): BundleCache {
748758
debugAssert(
749759
this.started,

0 commit comments

Comments
 (0)