diff --git a/packages/firestore/src/local/indexeddb_schema.ts b/packages/firestore/src/local/indexeddb_schema.ts index 245279900d4..fcf8e87c93b 100644 --- a/packages/firestore/src/local/indexeddb_schema.ts +++ b/packages/firestore/src/local/indexeddb_schema.ts @@ -17,6 +17,7 @@ import { BatchId, ListenSequenceNumber, TargetId } from '../core/types'; import { ResourcePath } from '../model/path'; +import { BundledQuery } from '../protos/firestore_bundle_proto'; import * as api from '../protos/firestore_proto_api'; import { hardAssert, debugAssert } from '../util/assert'; @@ -52,8 +53,9 @@ import { SimpleDbSchemaConverter, SimpleDbTransaction } from './simple_db'; * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than * 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. */ -export const SCHEMA_VERSION = 10; +export const SCHEMA_VERSION = 11; /** Performs database creation and schema upgrades. */ export class SchemaConverter implements SimpleDbSchemaConverter { @@ -154,6 +156,13 @@ export class SchemaConverter implements SimpleDbSchemaConverter { if (fromVersion < 10 && toVersion >= 10) { p = p.next(() => this.rewriteCanonicalIds(simpleDbTransaction)); } + + if (fromVersion < 11 && toVersion >= 11) { + p = p.next(() => { + createBundlesStore(db); + createNamedQueriesStore(db); + }); + } return p; } @@ -1072,6 +1081,60 @@ function createClientMetadataStore(db: IDBDatabase): void { }); } +export type DbBundlesKey = string; + +/** + * A object representing a bundle loaded by the SDK. + */ +export class DbBundles { + /** Name of the IndexedDb object store. */ + static store = 'bundles'; + + static keyPath = ['bundleId']; + + constructor( + /** The ID of the loaded bundle. */ + public bundleId: string, + /** The create time of the loaded bundle. */ + public createTime: DbTimestamp, + /** The schema version of the loaded bundle. */ + public version: number + ) {} +} + +function createBundlesStore(db: IDBDatabase): void { + db.createObjectStore(DbBundles.store, { + keyPath: DbBundles.keyPath + }); +} + +export type DbNamedQueriesKey = string; + +/** + * A object representing a named query loaded by the SDK via a bundle. + */ +export class DbNamedQueries { + /** Name of the IndexedDb object store. */ + static store = 'namedQueries'; + + static keyPath = ['name']; + + constructor( + /** The name of the query. */ + public name: string, + /** The read time of the results saved in the bundle from the named query. */ + public readTime: DbTimestamp, + /** The query saved in the bundle. */ + public bundledQuery: BundledQuery + ) {} +} + +function createNamedQueriesStore(db: IDBDatabase): void { + db.createObjectStore(DbNamedQueries.store, { + keyPath: DbNamedQueries.keyPath + }); +} + // Visible for testing export const V1_STORES = [ DbMutationQueue.store, @@ -1105,9 +1168,11 @@ export const V8_STORES = [...V6_STORES, DbCollectionParent.store]; // V10 does not change the set of stores. +export const V11_STORES = [...V8_STORES, DbCollectionParent.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 * atomically. */ -export const ALL_STORES = V8_STORES; +export const ALL_STORES = V11_STORES; diff --git a/packages/firestore/src/protos/firestore/bundle.proto b/packages/firestore/src/protos/firestore/bundle.proto new file mode 100644 index 00000000000..ca19071e71f --- /dev/null +++ b/packages/firestore/src/protos/firestore/bundle.proto @@ -0,0 +1,118 @@ +// Copyright 2020 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This file defines the format of Firestore bundle file/stream. It is not a part of the +// Firestore API, only a specification used by Server and Client SDK to write and read +// bundles. + +syntax = "proto3"; + +package firestore; + +import "google/firestore/v1/document.proto"; +import "google/firestore/v1/query.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "Firestore.Proto"; +option go_package = "google.golang.org/genproto/firestore/proto;firestore"; +option java_multiple_files = true; +option java_outer_classname = "BundleProto"; +option java_package = "com.google.firestore.proto"; +option objc_class_prefix = "FSTPB"; +option php_namespace = "Firestore\\Proto"; + +// Describes a query saved in the bundle. +message BundledQuery { + // The parent resource name. + string parent = 1; + + // The query to run. + oneof query_type { + // A structured query. + google.firestore.v1.StructuredQuery structured_query = 2; + } + + // If the query is a limit query, should the limit be applied to the beginning or + // the end of results. + enum LimitType { + FIRST = 0; + LAST = 1; + } + LimitType limit_type = 3; +} + +// A Query associated with a name, created as part of the bundle file, and can be read +// by client SDKs once the bundle containing them is loaded. +message NamedQuery { + // Name of the query, such that client can use the name to load this query + // from bundle, and resume from when the query results are materialized + // into this bundle. + string name = 1; + + // The query saved in the bundle. + BundledQuery bundled_query = 2; + + // The read time of the query, when it is used to build the bundle. This is useful to + // resume the query from the bundle, once it is loaded by client SDKs. + google.protobuf.Timestamp read_time = 3; +} + +// Metadata describing a Firestore document saved in the bundle. +message BundledDocumentMetadata { + // The document key of a bundled document. + string name = 1; + + // The snapshot version of the document data bundled. + google.protobuf.Timestamp read_time = 2; + + // Whether the document exists. + bool exists = 3; +} + +// Metadata describing the bundle file/stream. +message BundleMetadata { + // The ID of the bundle. + string id = 1; + + // Time at which the documents snapshot is taken for this bundle. + google.protobuf.Timestamp create_time = 2; + + // The schema version of the bundle. + uint32 version = 3; + + // The number of documents in the bundle. + uint32 total_documents = 4; + + // The size of the bundle in bytes, excluding this `BundleMetadata`. + uint64 total_bytes = 5; +} + +// A Firestore bundle is a length-prefixed stream of JSON representations of +// `BundleElement`. +// Only one `BundleMetadata` is expected, and it should be the first element. +// The named queries follow after `metadata`. If a document exists when the +// bundle is built, `document_metadata` is immediately followed by the +// `document`, otherwise `document_metadata` will present by itself. +message BundleElement { + oneof element_type { + BundleMetadata metadata = 1; + + NamedQuery named_query = 2; + + BundledDocumentMetadata document_metadata = 3; + + google.firestore.v1.Document document = 4; + } +} diff --git a/packages/firestore/src/protos/firestore_bundle_proto.d.ts b/packages/firestore/src/protos/firestore_bundle_proto.d.ts new file mode 100644 index 00000000000..e98fabc7abd --- /dev/null +++ b/packages/firestore/src/protos/firestore_bundle_proto.d.ts @@ -0,0 +1,92 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as api from './firestore_proto_api'; + +/** Properties of a BundledQuery. */ +export interface BundledQuery { + /** BundledQuery parent */ + parent?: string | null; + + /** BundledQuery structuredQuery */ + structuredQuery?: api.StructuredQuery | null; + + /** BundledQuery limitType */ + limitType?: BundledQuery.LimitType | null; +} + +export namespace BundledQuery { + /** LimitType enum. */ + type LimitType = 'FIRST' | 'LAST'; +} + +/** Properties of a NamedQuery. */ +export interface NamedQuery { + /** NamedQuery name */ + name?: string | null; + + /** NamedQuery bundledQuery */ + bundledQuery?: BundledQuery | null; + + /** NamedQuery readTime */ + readTime?: api.Timestamp | null; +} + +/** Properties of a BundledDocumentMetadata. */ +export interface BundledDocumentMetadata { + /** BundledDocumentMetadata name */ + name?: string | null; + + /** BundledDocumentMetadata readTime */ + readTime?: api.Timestamp | null; + + /** BundledDocumentMetadata exists */ + exists?: boolean | null; +} + +/** Properties of a BundleMetadata. */ +interface BundleMetadata { + /** BundleMetadata id */ + id?: string | null; + + /** BundleMetadata createTime */ + createTime?: api.Timestamp | null; + + /** BundleMetadata version */ + version?: number | null; + + /** BundleMetadata totalDocuments */ + totalDocuments?: number | null; + + /** BundleMetadata totalBytes */ + totalBytes?: number | null; +} + +/** Properties of a BundleElement. */ +interface BundleElement { + /** BundleElement metadata */ + metadata?: BundleMetadata | null; + + /** BundleElement namedQuery */ + namedQuery?: NamedQuery | null; + + /** BundleElement documentMetadata */ + documentMetadata?: BundledDocumentMetadata | null; + + /** BundleElement document */ + document?: api.Document | null; +}