Skip to content

Add Index Schema #5954

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 2 commits into from
Feb 1, 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
120 changes: 112 additions & 8 deletions packages/firestore/src/local/indexeddb_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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,
Expand All @@ -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];

Expand All @@ -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
Expand Down
28 changes: 25 additions & 3 deletions packages/firestore/src/local/indexeddb_schema_converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import {
DbCollectionParentKey,
DbDocumentMutation,
DbDocumentMutationKey,
DbIndexConfiguration,
DbIndexEntries,
DbIndexState,
DbMutationBatch,
DbMutationBatchKey,
DbMutationQueue,
Expand All @@ -51,7 +54,7 @@ import {
DbTargetGlobal,
DbTargetGlobalKey,
DbTargetKey,
SCHEMA_VERSION
INDEXING_SCHEMA_VERSION
} from './indexeddb_schema';
import {
fromDbMutationBatch,
Expand Down Expand Up @@ -80,10 +83,10 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
fromVersion: number,
toVersion: number
): PersistencePromise<void> {
hardAssert(
debugAssert(
fromVersion < toVersion &&
fromVersion >= 0 &&
toVersion <= SCHEMA_VERSION,
toVersion <= INDEXING_SCHEMA_VERSION,
`Unexpected schema upgrade from v${fromVersion} to v${toVersion}.`
);

Expand Down Expand Up @@ -169,6 +172,13 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
createNamedQueriesStore(db);
});
}

if (fromVersion < 12 && toVersion >= 12) {
p = p.next(() => {
createFieldIndex(db);
});
}

return p;
}

Expand Down Expand Up @@ -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
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
DbTargetKey,
DbTimestamp,
SCHEMA_VERSION,
V12_STORES,
V1_STORES,
V3_STORES,
V4_STORES,
Expand Down Expand Up @@ -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) => {
Expand Down