Skip to content

Commit 434d87e

Browse files
Add IndexManager CRUD operations (#5970)
1 parent 6c8eabf commit 434d87e

24 files changed

+774
-145
lines changed

packages/firestore/src/local/indexeddb_index_manager.ts

Lines changed: 161 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { User } from '../auth/user';
1819
import { Target } from '../core/target';
1920
import {
2021
documentKeySet,
@@ -31,26 +32,49 @@ import {
3132
encodeResourcePath
3233
} from './encoded_resource_path';
3334
import { IndexManager } from './index_manager';
34-
import { DbCollectionParent, DbCollectionParentKey } from './indexeddb_schema';
35+
import {
36+
DbCollectionParent,
37+
DbCollectionParentKey,
38+
DbIndexConfiguration,
39+
DbIndexConfigurationKey,
40+
DbIndexEntry,
41+
DbIndexEntryKey,
42+
DbIndexState,
43+
DbIndexStateKey
44+
} from './indexeddb_schema';
3545
import { getStore } from './indexeddb_transaction';
46+
import {
47+
fromDbIndexConfiguration,
48+
toDbIndexConfiguration,
49+
toDbIndexState
50+
} from './local_serializer';
3651
import { MemoryCollectionParentIndex } from './memory_index_manager';
3752
import { PersistencePromise } from './persistence_promise';
3853
import { PersistenceTransaction } from './persistence_transaction';
3954
import { SimpleDbStore } from './simple_db';
4055

4156
/**
4257
* A persisted implementation of IndexManager.
58+
*
59+
* PORTING NOTE: Unlike iOS and Android, the Web SDK does not memoize index
60+
* data as it supports multi-tab access.
4361
*/
4462
export class IndexedDbIndexManager implements IndexManager {
4563
/**
4664
* An in-memory copy of the index entries we've already written since the SDK
4765
* launched. Used to avoid re-writing the same entry repeatedly.
4866
*
49-
* This is *NOT* a complete cache of what's in persistence and so can never be used to
50-
* satisfy reads.
67+
* This is *NOT* a complete cache of what's in persistence and so can never be
68+
* used to satisfy reads.
5169
*/
5270
private collectionParentsCache = new MemoryCollectionParentIndex();
5371

72+
private uid: string;
73+
74+
constructor(private user: User) {
75+
this.uid = user.uid || '';
76+
}
77+
5478
/**
5579
* Adds a new entry to the collection parent index.
5680
*
@@ -114,16 +138,43 @@ export class IndexedDbIndexManager implements IndexManager {
114138
transaction: PersistenceTransaction,
115139
index: FieldIndex
116140
): PersistencePromise<void> {
117-
// TODO(indexing): Implement
118-
return PersistencePromise.resolve();
141+
// TODO(indexing): Verify that the auto-incrementing index ID works in
142+
// Safari & Firefox.
143+
const indexes = indexConfigurationStore(transaction);
144+
const dbIndex = toDbIndexConfiguration(index);
145+
delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
146+
return indexes.add(dbIndex).next();
119147
}
120148

121149
deleteFieldIndex(
122150
transaction: PersistenceTransaction,
123151
index: FieldIndex
124152
): PersistencePromise<void> {
125-
// TODO(indexing): Implement
126-
return PersistencePromise.resolve();
153+
const indexes = indexConfigurationStore(transaction);
154+
const states = indexStateStore(transaction);
155+
const entries = indexEntriesStore(transaction);
156+
return indexes
157+
.delete(index.indexId)
158+
.next(() =>
159+
states.delete(
160+
IDBKeyRange.bound(
161+
[index.indexId],
162+
[index.indexId + 1],
163+
/*lowerOpen=*/ false,
164+
/*upperOpen=*/ true
165+
)
166+
)
167+
)
168+
.next(() =>
169+
entries.delete(
170+
IDBKeyRange.bound(
171+
[index.indexId],
172+
[index.indexId + 1],
173+
/*lowerOpen=*/ false,
174+
/*upperOpen=*/ true
175+
)
176+
)
177+
);
127178
}
128179

129180
getDocumentsMatchingTarget(
@@ -147,24 +198,71 @@ export class IndexedDbIndexManager implements IndexManager {
147198
transaction: PersistenceTransaction,
148199
collectionGroup?: string
149200
): PersistencePromise<FieldIndex[]> {
150-
// TODO(indexing): Implement
151-
return PersistencePromise.resolve<FieldIndex[]>([]);
201+
const indexes = indexConfigurationStore(transaction);
202+
const states = indexStateStore(transaction);
203+
204+
return (
205+
collectionGroup
206+
? indexes.loadAll(
207+
DbIndexConfiguration.collectionGroupIndex,
208+
IDBKeyRange.bound(collectionGroup, collectionGroup)
209+
)
210+
: indexes.loadAll()
211+
).next(indexConfigs => {
212+
const result: FieldIndex[] = [];
213+
return PersistencePromise.forEach(
214+
indexConfigs,
215+
(indexConfig: DbIndexConfiguration) => {
216+
return states
217+
.get([indexConfig.indexId!, this.uid])
218+
.next(indexState => {
219+
result.push(fromDbIndexConfiguration(indexConfig, indexState));
220+
});
221+
}
222+
).next(() => result);
223+
});
152224
}
153225

154226
getNextCollectionGroupToUpdate(
155227
transaction: PersistenceTransaction
156228
): PersistencePromise<string | null> {
157-
// TODO(indexing): Implement
158-
return PersistencePromise.resolve<string | null>(null);
229+
return this.getFieldIndexes(transaction).next(indexes => {
230+
if (indexes.length === 0) {
231+
return null;
232+
}
233+
indexes.sort(
234+
(l, r) => l.indexState.sequenceNumber - r.indexState.sequenceNumber
235+
);
236+
return indexes[0].collectionGroup;
237+
});
159238
}
160239

161240
updateCollectionGroup(
162241
transaction: PersistenceTransaction,
163242
collectionGroup: string,
164243
offset: IndexOffset
165244
): PersistencePromise<void> {
166-
// TODO(indexing): Implement
167-
return PersistencePromise.resolve();
245+
const indexes = indexConfigurationStore(transaction);
246+
const states = indexStateStore(transaction);
247+
return this.getNextSequenceNumber(transaction).next(nextSequenceNumber =>
248+
indexes
249+
.loadAll(
250+
DbIndexConfiguration.collectionGroupIndex,
251+
IDBKeyRange.bound(collectionGroup, collectionGroup)
252+
)
253+
.next(configs =>
254+
PersistencePromise.forEach(configs, (config: DbIndexConfiguration) =>
255+
states.put(
256+
toDbIndexState(
257+
config.indexId!,
258+
this.user,
259+
nextSequenceNumber,
260+
offset
261+
)
262+
)
263+
)
264+
)
265+
);
168266
}
169267

170268
updateIndexEntries(
@@ -174,6 +272,26 @@ export class IndexedDbIndexManager implements IndexManager {
174272
// TODO(indexing): Implement
175273
return PersistencePromise.resolve();
176274
}
275+
276+
private getNextSequenceNumber(
277+
transaction: PersistenceTransaction
278+
): PersistencePromise<number> {
279+
let nextSequenceNumber = 1;
280+
const states = indexStateStore(transaction);
281+
return states
282+
.iterate(
283+
{
284+
index: DbIndexState.sequenceNumberIndex,
285+
reverse: true,
286+
range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
287+
},
288+
(_, state, controller) => {
289+
controller.done();
290+
nextSequenceNumber = state.sequenceNumber + 1;
291+
}
292+
)
293+
.next(() => nextSequenceNumber);
294+
}
177295
}
178296

179297
/**
@@ -188,3 +306,33 @@ function collectionParentsStore(
188306
DbCollectionParent.store
189307
);
190308
}
309+
310+
/**
311+
* Helper to get a typed SimpleDbStore for the index entry object store.
312+
*/
313+
function indexEntriesStore(
314+
txn: PersistenceTransaction
315+
): SimpleDbStore<DbIndexEntryKey, DbIndexEntry> {
316+
return getStore<DbIndexEntryKey, DbIndexEntry>(txn, DbIndexEntry.store);
317+
}
318+
319+
/**
320+
* Helper to get a typed SimpleDbStore for the index configuration object store.
321+
*/
322+
function indexConfigurationStore(
323+
txn: PersistenceTransaction
324+
): SimpleDbStore<DbIndexConfigurationKey, DbIndexConfiguration> {
325+
return getStore<DbIndexConfigurationKey, DbIndexConfiguration>(
326+
txn,
327+
DbIndexConfiguration.store
328+
);
329+
}
330+
331+
/**
332+
* Helper to get a typed SimpleDbStore for the index state object store.
333+
*/
334+
function indexStateStore(
335+
txn: PersistenceTransaction
336+
): SimpleDbStore<DbIndexStateKey, DbIndexState> {
337+
return getStore<DbIndexStateKey, DbIndexState>(txn, DbIndexState.store);
338+
}

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ import {
3838
newIndexedDbRemoteDocumentCache
3939
} from './indexeddb_remote_document_cache';
4040
import {
41-
ALL_STORES,
4241
DbClientMetadata,
4342
DbClientMetadataKey,
4443
DbPrimaryClient,
4544
DbPrimaryClientKey,
45+
getObjectStores,
4646
SCHEMA_VERSION
4747
} from './indexeddb_schema';
4848
import { SchemaConverter } from './indexeddb_schema_converter';
@@ -183,7 +183,6 @@ export class IndexedDbPersistence implements Persistence {
183183
private primaryStateListener: PrimaryStateListener = _ => Promise.resolve();
184184

185185
private readonly targetCache: IndexedDbTargetCache;
186-
private readonly indexManager: IndexedDbIndexManager;
187186
private readonly remoteDocumentCache: IndexedDbRemoteDocumentCache;
188187
private readonly bundleCache: IndexedDbBundleCache;
189188
private readonly webStorage: Storage | null;
@@ -209,7 +208,8 @@ export class IndexedDbPersistence implements Persistence {
209208
* If set to true, forcefully obtains database access. Existing tabs will
210209
* no longer be able to access IndexedDB.
211210
*/
212-
private readonly forceOwningTab: boolean
211+
private readonly forceOwningTab: boolean,
212+
private readonly schemaVersion = SCHEMA_VERSION
213213
) {
214214
if (!IndexedDbPersistence.isAvailable()) {
215215
throw new FirestoreError(
@@ -223,18 +223,14 @@ export class IndexedDbPersistence implements Persistence {
223223
this.serializer = new LocalSerializer(serializer);
224224
this.simpleDb = new SimpleDb(
225225
this.dbName,
226-
SCHEMA_VERSION,
226+
this.schemaVersion,
227227
new SchemaConverter(this.serializer)
228228
);
229229
this.targetCache = new IndexedDbTargetCache(
230230
this.referenceDelegate,
231231
this.serializer
232232
);
233-
this.indexManager = new IndexedDbIndexManager();
234-
this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(
235-
this.serializer,
236-
this.indexManager
237-
);
233+
this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
238234
this.bundleCache = new IndexedDbBundleCache();
239235
if (this.window && this.window.localStorage) {
240236
this.webStorage = this.window.localStorage;
@@ -708,15 +704,18 @@ export class IndexedDbPersistence implements Persistence {
708704
return this._started;
709705
}
710706

711-
getMutationQueue(user: User): IndexedDbMutationQueue {
707+
getMutationQueue(
708+
user: User,
709+
indexManager: IndexManager
710+
): IndexedDbMutationQueue {
712711
debugAssert(
713712
this.started,
714713
'Cannot initialize MutationQueue before persistence is started.'
715714
);
716715
return IndexedDbMutationQueue.forUser(
717716
user,
718717
this.serializer,
719-
this.indexManager,
718+
indexManager,
720719
this.referenceDelegate
721720
);
722721
}
@@ -737,12 +736,12 @@ export class IndexedDbPersistence implements Persistence {
737736
return this.remoteDocumentCache;
738737
}
739738

740-
getIndexManager(): IndexManager {
739+
getIndexManager(user: User): IndexManager {
741740
debugAssert(
742741
this.started,
743742
'Cannot initialize IndexManager before persistence is started.'
744743
);
745-
return this.indexManager;
744+
return new IndexedDbIndexManager(user);
746745
}
747746

748747
getBundleCache(): BundleCache {
@@ -763,13 +762,14 @@ export class IndexedDbPersistence implements Persistence {
763762
logDebug(LOG_TAG, 'Starting transaction:', action);
764763

765764
const simpleDbMode = mode === 'readonly' ? 'readonly' : 'readwrite';
765+
const objectStores = getObjectStores(this.schemaVersion);
766766

767767
let persistenceTransaction: PersistenceTransaction;
768768

769769
// Do all transactions as readwrite against all object stores, since we
770770
// are the only reader/writer.
771771
return this.simpleDb
772-
.runTransaction(action, simpleDbMode, ALL_STORES, simpleDbTxn => {
772+
.runTransaction(action, simpleDbMode, objectStores, simpleDbTxn => {
773773
persistenceTransaction = new IndexedDbTransaction(
774774
simpleDbTxn,
775775
this.listenSequence

packages/firestore/src/local/indexeddb_remote_document_cache.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,13 @@ export interface IndexedDbRemoteDocumentCache extends RemoteDocumentCache {
6969
* `newIndexedDbRemoteDocumentCache()`.
7070
*/
7171
class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache {
72-
/**
73-
* @param serializer - The document serializer.
74-
* @param indexManager - The query indexes that need to be maintained.
75-
*/
76-
constructor(
77-
readonly serializer: LocalSerializer,
78-
readonly indexManager: IndexManager
79-
) {}
72+
indexManager!: IndexManager;
73+
74+
constructor(readonly serializer: LocalSerializer) {}
75+
76+
setIndexManager(indexManager: IndexManager): void {
77+
this.indexManager = indexManager;
78+
}
8079

8180
/**
8281
* Adds the supplied entries to the cache.
@@ -355,17 +354,11 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache {
355354
}
356355
}
357356

358-
/**
359-
* Creates a new IndexedDbRemoteDocumentCache.
360-
*
361-
* @param serializer - The document serializer.
362-
* @param indexManager - The query indexes that need to be maintained.
363-
*/
357+
/** Creates a new IndexedDbRemoteDocumentCache. */
364358
export function newIndexedDbRemoteDocumentCache(
365-
serializer: LocalSerializer,
366-
indexManager: IndexManager
359+
serializer: LocalSerializer
367360
): IndexedDbRemoteDocumentCache {
368-
return new IndexedDbRemoteDocumentCacheImpl(serializer, indexManager);
361+
return new IndexedDbRemoteDocumentCacheImpl(serializer);
369362
}
370363

371364
/**

0 commit comments

Comments
 (0)