Skip to content

Commit f18792a

Browse files
Add IndexManager CRUD operations
1 parent 00916e7 commit f18792a

22 files changed

+754
-141
lines changed

packages/firestore/src/local/indexeddb_index_manager.ts

Lines changed: 160 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,42 @@ 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+
return indexes.add(dbIndex).next();
119146
}
120147

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

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

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

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

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

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

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: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +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) {}
8075

76+
setIndexManager(indexManager: IndexManager): void {
77+
this.indexManager = indexManager;
78+
}
8179
/**
8280
* Adds the supplied entries to the cache.
8381
*
@@ -355,17 +353,11 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache {
355353
}
356354
}
357355

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-
*/
356+
/** Creates a new IndexedDbRemoteDocumentCache. */
364357
export function newIndexedDbRemoteDocumentCache(
365-
serializer: LocalSerializer,
366-
indexManager: IndexManager
358+
serializer: LocalSerializer
367359
): IndexedDbRemoteDocumentCache {
368-
return new IndexedDbRemoteDocumentCacheImpl(serializer, indexManager);
360+
return new IndexedDbRemoteDocumentCacheImpl(serializer);
369361
}
370362

371363
/**

0 commit comments

Comments
 (0)