Skip to content

Add IndexManager CRUD operations #5970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 161 additions & 13 deletions packages/firestore/src/local/indexeddb_index_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* limitations under the License.
*/

import { User } from '../auth/user';
import { Target } from '../core/target';
import {
documentKeySet,
Expand All @@ -31,26 +32,49 @@ import {
encodeResourcePath
} from './encoded_resource_path';
import { IndexManager } from './index_manager';
import { DbCollectionParent, DbCollectionParentKey } from './indexeddb_schema';
import {
DbCollectionParent,
DbCollectionParentKey,
DbIndexConfiguration,
DbIndexConfigurationKey,
DbIndexEntry,
DbIndexEntryKey,
DbIndexState,
DbIndexStateKey
} from './indexeddb_schema';
import { getStore } from './indexeddb_transaction';
import {
fromDbIndexConfiguration,
toDbIndexConfiguration,
toDbIndexState
} from './local_serializer';
import { MemoryCollectionParentIndex } from './memory_index_manager';
import { PersistencePromise } from './persistence_promise';
import { PersistenceTransaction } from './persistence_transaction';
import { SimpleDbStore } from './simple_db';

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

private uid: string;

constructor(private user: User) {
this.uid = user.uid || '';
}

/**
* Adds a new entry to the collection parent index.
*
Expand Down Expand Up @@ -114,16 +138,43 @@ export class IndexedDbIndexManager implements IndexManager {
transaction: PersistenceTransaction,
index: FieldIndex
): PersistencePromise<void> {
// TODO(indexing): Implement
return PersistencePromise.resolve();
// TODO(indexing): Verify that the auto-incrementing index ID works in
// Safari & Firefox.
const indexes = indexConfigurationStore(transaction);
const dbIndex = toDbIndexConfiguration(index);
delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
return indexes.add(dbIndex).next();
}

deleteFieldIndex(
transaction: PersistenceTransaction,
index: FieldIndex
): PersistencePromise<void> {
// TODO(indexing): Implement
return PersistencePromise.resolve();
const indexes = indexConfigurationStore(transaction);
const states = indexStateStore(transaction);
const entries = indexEntriesStore(transaction);
return indexes
.delete(index.indexId)
.next(() =>
states.delete(
IDBKeyRange.bound(
[index.indexId],
[index.indexId + 1],
/*lowerOpen=*/ false,
/*upperOpen=*/ true
)
)
)
.next(() =>
entries.delete(
IDBKeyRange.bound(
[index.indexId],
[index.indexId + 1],
/*lowerOpen=*/ false,
/*upperOpen=*/ true
)
)
);
}

getDocumentsMatchingTarget(
Expand All @@ -147,24 +198,71 @@ export class IndexedDbIndexManager implements IndexManager {
transaction: PersistenceTransaction,
collectionGroup?: string
): PersistencePromise<FieldIndex[]> {
// TODO(indexing): Implement
return PersistencePromise.resolve<FieldIndex[]>([]);
const indexes = indexConfigurationStore(transaction);
const states = indexStateStore(transaction);

return (
collectionGroup
? indexes.loadAll(
DbIndexConfiguration.collectionGroupIndex,
IDBKeyRange.bound(collectionGroup, collectionGroup)
)
: indexes.loadAll()
).next(indexConfigs => {
const result: FieldIndex[] = [];
return PersistencePromise.forEach(
indexConfigs,
(indexConfig: DbIndexConfiguration) => {
return states
.get([indexConfig.indexId!, this.uid])
.next(indexState => {
result.push(fromDbIndexConfiguration(indexConfig, indexState));
});
}
).next(() => result);
});
}

getNextCollectionGroupToUpdate(
transaction: PersistenceTransaction
): PersistencePromise<string | null> {
// TODO(indexing): Implement
return PersistencePromise.resolve<string | null>(null);
return this.getFieldIndexes(transaction).next(indexes => {
if (indexes.length === 0) {
return null;
}
indexes.sort(
(l, r) => l.indexState.sequenceNumber - r.indexState.sequenceNumber
);
return indexes[0].collectionGroup;
});
}

updateCollectionGroup(
transaction: PersistenceTransaction,
collectionGroup: string,
offset: IndexOffset
): PersistencePromise<void> {
// TODO(indexing): Implement
return PersistencePromise.resolve();
const indexes = indexConfigurationStore(transaction);
const states = indexStateStore(transaction);
return this.getNextSequenceNumber(transaction).next(nextSequenceNumber =>
indexes
.loadAll(
DbIndexConfiguration.collectionGroupIndex,
IDBKeyRange.bound(collectionGroup, collectionGroup)
)
.next(configs =>
PersistencePromise.forEach(configs, (config: DbIndexConfiguration) =>
states.put(
toDbIndexState(
config.indexId!,
this.user,
nextSequenceNumber,
offset
)
)
)
)
);
}

updateIndexEntries(
Expand All @@ -174,6 +272,26 @@ export class IndexedDbIndexManager implements IndexManager {
// TODO(indexing): Implement
return PersistencePromise.resolve();
}

private getNextSequenceNumber(
transaction: PersistenceTransaction
): PersistencePromise<number> {
let nextSequenceNumber = 1;
const states = indexStateStore(transaction);
return states
.iterate(
{
index: DbIndexState.sequenceNumberIndex,
reverse: true,
range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
},
(_, state, controller) => {
controller.done();
nextSequenceNumber = state.sequenceNumber + 1;
}
)
.next(() => nextSequenceNumber);
}
}

/**
Expand All @@ -188,3 +306,33 @@ function collectionParentsStore(
DbCollectionParent.store
);
}

/**
* Helper to get a typed SimpleDbStore for the index entry object store.
*/
function indexEntriesStore(
txn: PersistenceTransaction
): SimpleDbStore<DbIndexEntryKey, DbIndexEntry> {
return getStore<DbIndexEntryKey, DbIndexEntry>(txn, DbIndexEntry.store);
}

/**
* Helper to get a typed SimpleDbStore for the index configuration object store.
*/
function indexConfigurationStore(
txn: PersistenceTransaction
): SimpleDbStore<DbIndexConfigurationKey, DbIndexConfiguration> {
return getStore<DbIndexConfigurationKey, DbIndexConfiguration>(
txn,
DbIndexConfiguration.store
);
}

/**
* Helper to get a typed SimpleDbStore for the index state object store.
*/
function indexStateStore(
txn: PersistenceTransaction
): SimpleDbStore<DbIndexStateKey, DbIndexState> {
return getStore<DbIndexStateKey, DbIndexState>(txn, DbIndexState.store);
}
28 changes: 14 additions & 14 deletions packages/firestore/src/local/indexeddb_persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ import {
newIndexedDbRemoteDocumentCache
} from './indexeddb_remote_document_cache';
import {
ALL_STORES,
DbClientMetadata,
DbClientMetadataKey,
DbPrimaryClient,
DbPrimaryClientKey,
getObjectStores,
SCHEMA_VERSION
} from './indexeddb_schema';
import { SchemaConverter } from './indexeddb_schema_converter';
Expand Down Expand Up @@ -183,7 +183,6 @@ export class IndexedDbPersistence implements Persistence {
private primaryStateListener: PrimaryStateListener = _ => Promise.resolve();

private readonly targetCache: IndexedDbTargetCache;
private readonly indexManager: IndexedDbIndexManager;
private readonly remoteDocumentCache: IndexedDbRemoteDocumentCache;
private readonly bundleCache: IndexedDbBundleCache;
private readonly webStorage: Storage | null;
Expand All @@ -209,7 +208,8 @@ export class IndexedDbPersistence implements Persistence {
* If set to true, forcefully obtains database access. Existing tabs will
* no longer be able to access IndexedDB.
*/
private readonly forceOwningTab: boolean
private readonly forceOwningTab: boolean,
private readonly schemaVersion = SCHEMA_VERSION
) {
if (!IndexedDbPersistence.isAvailable()) {
throw new FirestoreError(
Expand All @@ -223,18 +223,14 @@ export class IndexedDbPersistence implements Persistence {
this.serializer = new LocalSerializer(serializer);
this.simpleDb = new SimpleDb(
this.dbName,
SCHEMA_VERSION,
this.schemaVersion,
new SchemaConverter(this.serializer)
);
this.targetCache = new IndexedDbTargetCache(
this.referenceDelegate,
this.serializer
);
this.indexManager = new IndexedDbIndexManager();
this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(
this.serializer,
this.indexManager
);
this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
this.bundleCache = new IndexedDbBundleCache();
if (this.window && this.window.localStorage) {
this.webStorage = this.window.localStorage;
Expand Down Expand Up @@ -708,15 +704,18 @@ export class IndexedDbPersistence implements Persistence {
return this._started;
}

getMutationQueue(user: User): IndexedDbMutationQueue {
getMutationQueue(
user: User,
indexManager: IndexManager
): IndexedDbMutationQueue {
debugAssert(
this.started,
'Cannot initialize MutationQueue before persistence is started.'
);
return IndexedDbMutationQueue.forUser(
user,
this.serializer,
this.indexManager,
indexManager,
this.referenceDelegate
);
}
Expand All @@ -737,12 +736,12 @@ export class IndexedDbPersistence implements Persistence {
return this.remoteDocumentCache;
}

getIndexManager(): IndexManager {
getIndexManager(user: User): IndexManager {
debugAssert(
this.started,
'Cannot initialize IndexManager before persistence is started.'
);
return this.indexManager;
return new IndexedDbIndexManager(user);
}

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

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

let persistenceTransaction: PersistenceTransaction;

// Do all transactions as readwrite against all object stores, since we
// are the only reader/writer.
return this.simpleDb
.runTransaction(action, simpleDbMode, ALL_STORES, simpleDbTxn => {
.runTransaction(action, simpleDbMode, objectStores, simpleDbTxn => {
persistenceTransaction = new IndexedDbTransaction(
simpleDbTxn,
this.listenSequence
Expand Down
27 changes: 10 additions & 17 deletions packages/firestore/src/local/indexeddb_remote_document_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,13 @@ export interface IndexedDbRemoteDocumentCache extends RemoteDocumentCache {
* `newIndexedDbRemoteDocumentCache()`.
*/
class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache {
/**
* @param serializer - The document serializer.
* @param indexManager - The query indexes that need to be maintained.
*/
constructor(
readonly serializer: LocalSerializer,
readonly indexManager: IndexManager
) {}
indexManager!: IndexManager;

constructor(readonly serializer: LocalSerializer) {}

setIndexManager(indexManager: IndexManager): void {
this.indexManager = indexManager;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty line after here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


/**
* Adds the supplied entries to the cache.
Expand Down Expand Up @@ -355,17 +354,11 @@ class IndexedDbRemoteDocumentCacheImpl implements IndexedDbRemoteDocumentCache {
}
}

/**
* Creates a new IndexedDbRemoteDocumentCache.
*
* @param serializer - The document serializer.
* @param indexManager - The query indexes that need to be maintained.
*/
/** Creates a new IndexedDbRemoteDocumentCache. */
export function newIndexedDbRemoteDocumentCache(
serializer: LocalSerializer,
indexManager: IndexManager
serializer: LocalSerializer
): IndexedDbRemoteDocumentCache {
return new IndexedDbRemoteDocumentCacheImpl(serializer, indexManager);
return new IndexedDbRemoteDocumentCacheImpl(serializer);
}

/**
Expand Down
Loading