diff --git a/packages/firestore/src/local/indexeddb_schema.ts b/packages/firestore/src/local/indexeddb_schema.ts index fa282e6f560..a5c79ef450e 100644 --- a/packages/firestore/src/local/indexeddb_schema.ts +++ b/packages/firestore/src/local/indexeddb_schema.ts @@ -33,22 +33,23 @@ import { SimpleDbSchemaConverter, SimpleDbTransaction } from './simple_db'; /** * Schema Version for the Web client: - * 1. Initial version including Mutation Queue, Query Cache, and Remote Document - * Cache - * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No - * longer required because migration 3 unconditionally clears it. - * 3. Dropped and re-created Query Cache to deal with cache corruption related - * to limbo resolution. Addresses - * https://github.com/firebase/firebase-ios-sdk/issues/1548 - * 4. Multi-Tab Support. - * 5. Removal of held write acks. - * 6. Create document global for tracking document cache size. - * 7. Ensure every cached document has a sentinel row with a sequence number. - * 8. Add collection-parent index for Collection Group queries. - * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than - * an auto-incrementing ID. This is required for Index-Free queries. + * 1. Initial version including Mutation Queue, Query Cache, and Remote + * Document Cache + * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No + * longer required because migration 3 unconditionally clears it. + * 3. Dropped and re-created Query Cache to deal with cache corruption related + * to limbo resolution. Addresses + * https://github.com/firebase/firebase-ios-sdk/issues/1548 + * 4. Multi-Tab Support. + * 5. Removal of held write acks. + * 6. Create document global for tracking document cache size. + * 7. Ensure every cached document has a sentinel row with a sequence number. + * 8. Add collection-parent index for Collection Group queries. + * 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. */ -export const SCHEMA_VERSION = 9; +export const SCHEMA_VERSION = 10; /** Performs database creation and schema upgrades. */ export class SchemaConverter implements SimpleDbSchemaConverter { @@ -71,7 +72,7 @@ export class SchemaConverter implements SimpleDbSchemaConverter { fromVersion < toVersion && fromVersion >= 0 && toVersion <= SCHEMA_VERSION, - `Unexpected schema upgrade from v${fromVersion} to v{toVersion}.` + `Unexpected schema upgrade from v${fromVersion} to v${toVersion}.` ); const simpleDbTransaction = new SimpleDbTransaction(txn); @@ -145,6 +146,10 @@ export class SchemaConverter implements SimpleDbSchemaConverter { createRemoteDocumentReadTimeIndex(txn); }); } + + if (fromVersion < 10 && toVersion >= 10) { + p = p.next(() => this.rewriteCanonicalIds(simpleDbTransaction)); + } return p; } @@ -299,6 +304,17 @@ export class SchemaConverter implements SimpleDbSchemaConverter { }); }); } + + private rewriteCanonicalIds( + txn: SimpleDbTransaction + ): PersistencePromise { + const targetStore = txn.store(DbTarget.store); + return targetStore.iterate((key, originalDbTarget) => { + const originalTargetData = this.serializer.fromDbTarget(originalDbTarget); + const updatedDbTarget = this.serializer.toDbTarget(originalTargetData); + return targetStore.put(updatedDbTarget); + }); + } } function sentinelKey(path: ResourcePath): DbTargetDocumentKey { @@ -1079,6 +1095,8 @@ export const V8_STORES = [...V6_STORES, DbCollectionParent.store]; // V9 does not change the set of stores. +// V10 does not change the set of stores. + /** * 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/test/unit/local/indexeddb_persistence.test.ts b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts index 04023a85b1a..c73e102c691 100644 --- a/packages/firestore/test/unit/local/indexeddb_persistence.test.ts +++ b/packages/firestore/test/unit/local/indexeddb_persistence.test.ts @@ -16,6 +16,7 @@ */ import { expect } from 'chai'; +import { Query } from '../../../src/core/query'; import { SnapshotVersion } from '../../../src/core/snapshot_version'; import { decode, encode } from '../../../src/local/encoded_resource_path'; import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence'; @@ -53,12 +54,13 @@ import { LruParams } from '../../../src/local/lru_garbage_collector'; import { PersistencePromise } from '../../../src/local/persistence_promise'; import { ClientId } from '../../../src/local/shared_client_state'; import { SimpleDb, SimpleDbTransaction } from '../../../src/local/simple_db'; +import { TargetData, TargetPurpose } from '../../../src/local/target_data'; import { PlatformSupport } from '../../../src/platform/platform'; import { firestoreV1ApiClientInterfaces } from '../../../src/protos/firestore_proto_api'; import { JsonProtoSerializer } from '../../../src/remote/serializer'; import { AsyncQueue } from '../../../src/util/async_queue'; import { FirestoreError } from '../../../src/util/error'; -import { doc, path, version } from '../../util/helpers'; +import { doc, filter, path, version } from '../../util/helpers'; import { SharedFakeWebStorage, TestPlatform } from '../../util/test_platform'; import { INDEXEDDB_TEST_DATABASE_NAME, @@ -728,6 +730,44 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { }); }); + it('rewrites canonical IDs during upgrade from version 9 to 10', async () => { + await withDb(9, db => { + const sdb = new SimpleDb(db); + return sdb.runTransaction('readwrite-idempotent', V8_STORES, txn => { + const targetsStore = txn.store(DbTarget.store); + + const filteredQuery = Query.atPath(path('collection')).addFilter( + filter('foo', '==', 'bar') + ); + const initialTargetData = new TargetData( + filteredQuery.toTarget(), + /* targetId= */ 2, + TargetPurpose.Listen, + /* sequenceNumber= */ 1 + ); + + const serializedData = TEST_SERIALIZER.toDbTarget(initialTargetData); + serializedData.canonicalId = 'invalid_canonical_id'; + + return targetsStore.put(TEST_SERIALIZER.toDbTarget(initialTargetData)); + }); + }); + + await withDb(10, db => { + const sdb = new SimpleDb(db); + return sdb.runTransaction('readwrite-idempotent', V8_STORES, txn => { + const targetsStore = txn.store(DbTarget.store); + return targetsStore.iterate((key, value) => { + const targetData = TEST_SERIALIZER.fromDbTarget(value).target; + const expectedCanonicalId = targetData.canonicalId(); + + const actualCanonicalId = value.canonicalId; + expect(actualCanonicalId).to.equal(expectedCanonicalId); + }); + }); + }); + }); + it('can use read-time index after schema migration', async () => { // This test creates a database with schema version 8 that has a few // remote documents, adds an index and then reads new documents back