diff --git a/packages/firestore/src/local/indexeddb_schema.ts b/packages/firestore/src/local/indexeddb_schema.ts index 74eb68d29d8..37310a891b6 100644 --- a/packages/firestore/src/local/indexeddb_schema.ts +++ b/packages/firestore/src/local/indexeddb_schema.ts @@ -16,6 +16,7 @@ */ import { BatchId, ListenSequenceNumber, TargetId } from '../core/types'; +import { IndexKind } from '../model/field_index'; import { ResourcePath } from '../model/path'; import { BundledQuery } from '../protos/firestore_bundle_proto'; import { @@ -31,6 +32,11 @@ import { encodeResourcePath } from './encoded_resource_path'; +// TODO(indexing): Remove this constant +const INDEXING_ENABLED = false; + +export const INDEXING_SCHEMA_VERSION = 12; + /** * Schema Version for the Web client: * 1. Initial version including Mutation Queue, Query Cache, and Remote @@ -49,8 +55,9 @@ import { * an auto-incrementing ID. This is required for Index-Free queries. * 10. Rewrite the canonical IDs to the explicit Protobuf-based format. * 11. Add bundles and named_queries for bundle support. + * 12. Add indexing support. */ -export const SCHEMA_VERSION = 11; +export const SCHEMA_VERSION = INDEXING_ENABLED ? INDEXING_SCHEMA_VERSION : 11; /** * Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects. @@ -655,9 +662,7 @@ export type DbClientMetadataKey = string; export type DbBundlesKey = string; -/** - * A object representing a bundle loaded by the SDK. - */ +/** An object representing a bundle loaded by the SDK. */ export class DbBundle { /** Name of the IndexedDb object store. */ static store = 'bundles'; @@ -676,9 +681,7 @@ export class DbBundle { export type DbNamedQueriesKey = string; -/** - * A object representing a named query loaded by the SDK via a bundle. - */ +/** An object representing a named query loaded by the SDK via a bundle. */ export class DbNamedQuery { /** Name of the IndexedDb object store. */ static store = 'namedQueries'; @@ -695,6 +698,101 @@ export class DbNamedQuery { ) {} } +/** The key for each index consisting of just the index id. */ +export type DbIndexConfigurationKey = number; + +/** An object representing the global configuration for a field index. */ +export class DbIndexConfiguration { + /** Name of the IndexedDb object store. */ + static store = 'indexConfiguration'; + + static keyPath = 'indexId'; + + constructor( + /** The index id for this entry. */ + public indexId: number, + /** The collection group this index belongs to. */ + public collectionGroup: string, + /** The fields to index for this index. */ + public fields: [[name: string, kind: IndexKind]] + ) {} +} + +/** The key for each index state consisting of the index id and its user id. */ +export type DbIndexStateKey = [number, string]; + +/** + * An object describing how up-to-date the index backfill is for each user and + * index. + */ +export class DbIndexState { + /** Name of the IndexedDb object store. */ + static store = 'indexState'; + + static keyPath = ['indexId', 'uid']; + + constructor( + /** The index id for this entry. */ + public indexId: number, + /** The user id for this entry. */ + public uid: string, + /** + * A number that indicates when the index was last updated (relative to + * other indexes). + */ + public sequenceNumber: number, + /** + * The latest read time that has been indexed by Firestore for this field + * index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed. + */ + public readTime: DbTimestamp, + /** + * The last document that has been indexed for this field index. Empty if + * no documents have been indexed. + */ + public documentKey: EncodedResourcePath, + /** + * The largest mutation batch id that has been processed for this index. -1 + * if no mutations have been indexed. + */ + public largestBatchId: number + ) {} +} + +/** + * The key for each index entry consists of the index id and its user id, + * the encoded array and directional value for the indexed fields as well as + * the encoded document path for the indexed document. + */ +export type DbIndexEntryKey = [number, string, Uint8Array, Uint8Array, string]; + +/** An object that stores the encoded entries for all documents and fields. */ +export class DbIndexEntries { + /** Name of the IndexedDb object store. */ + static store = 'indexEntries'; + + static keyPath = [ + 'indexId', + 'uid', + 'arrayValue', + 'directionalValue', + 'documentKey' + ]; + + constructor( + /** The index id for this entry. */ + public indexId: number, + /** The user id for this entry. */ + public uid: string, + /** The encoded array index value for this entry. */ + public arrayValue: Uint8Array, + /** The encoded directional value for equality and inequality filters. */ + public directionalValue: Uint8Array, + /** The document key this entry points to. */ + public documentKey: EncodedResourcePath + ) {} +} + // Visible for testing export const V1_STORES = [ DbMutationQueue.store, @@ -712,7 +810,6 @@ export const V1_STORES = [ // Visible for testing export const V3_STORES = V1_STORES; -// Visible for testing // Note: DbRemoteDocumentChanges is no longer used and dropped with v9. export const V4_STORES = [...V3_STORES, DbClientMetadata.store]; @@ -730,6 +827,13 @@ export const V8_STORES = [...V6_STORES, DbCollectionParent.store]; export const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store]; +export const V12_STORES = [ + ...V11_STORES, + DbIndexConfiguration.store, + DbIndexState.store, + DbIndexEntries.store +]; + /** * The list of all default IndexedDB stores used throughout the SDK. This is * used when creating transactions so that access across all stores is done diff --git a/packages/firestore/src/local/indexeddb_schema_converter.ts b/packages/firestore/src/local/indexeddb_schema_converter.ts index 1ba82e92322..f17a4c23723 100644 --- a/packages/firestore/src/local/indexeddb_schema_converter.ts +++ b/packages/firestore/src/local/indexeddb_schema_converter.ts @@ -35,6 +35,9 @@ import { DbCollectionParentKey, DbDocumentMutation, DbDocumentMutationKey, + DbIndexConfiguration, + DbIndexEntries, + DbIndexState, DbMutationBatch, DbMutationBatchKey, DbMutationQueue, @@ -51,7 +54,7 @@ import { DbTargetGlobal, DbTargetGlobalKey, DbTargetKey, - SCHEMA_VERSION + INDEXING_SCHEMA_VERSION } from './indexeddb_schema'; import { fromDbMutationBatch, @@ -80,10 +83,10 @@ export class SchemaConverter implements SimpleDbSchemaConverter { fromVersion: number, toVersion: number ): PersistencePromise { - hardAssert( + debugAssert( fromVersion < toVersion && fromVersion >= 0 && - toVersion <= SCHEMA_VERSION, + toVersion <= INDEXING_SCHEMA_VERSION, `Unexpected schema upgrade from v${fromVersion} to v${toVersion}.` ); @@ -169,6 +172,13 @@ export class SchemaConverter implements SimpleDbSchemaConverter { createNamedQueriesStore(db); }); } + + if (fromVersion < 12 && toVersion >= 12) { + p = p.next(() => { + createFieldIndex(db); + }); + } + return p; } @@ -504,3 +514,15 @@ function createNamedQueriesStore(db: IDBDatabase): void { keyPath: DbNamedQuery.keyPath }); } + +function createFieldIndex(db: IDBDatabase): void { + db.createObjectStore(DbIndexConfiguration.store, { + keyPath: DbIndexConfiguration.keyPath + }); + db.createObjectStore(DbIndexState.store, { + keyPath: DbIndexState.keyPath + }); + db.createObjectStore(DbIndexEntries.store, { + keyPath: DbIndexEntries.keyPath + }); +} diff --git a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts index 4185dfcc7ae..836e1287139 100644 --- a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts +++ b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts @@ -50,6 +50,7 @@ import { DbTargetKey, DbTimestamp, SCHEMA_VERSION, + V12_STORES, V1_STORES, V3_STORES, V4_STORES, @@ -1024,6 +1025,14 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { }); }); + it('can upgrade from version 11 to 12', async () => { + await withDb(11, async () => {}); + await withDb(12, async (db, version, objectStores) => { + expect(version).to.have.equal(12); + expect(objectStores).to.have.members(V12_STORES); + }); + }); + it('downgrading throws a custom error', async function (this: Context) { // Upgrade to latest version await withDb(SCHEMA_VERSION, async (db, version) => {