diff --git a/packages/firestore/src/local/indexeddb_schema.ts b/packages/firestore/src/local/indexeddb_schema.ts index 8dd7dfe0c20..1b37a812b39 100644 --- a/packages/firestore/src/local/indexeddb_schema.ts +++ b/packages/firestore/src/local/indexeddb_schema.ts @@ -30,8 +30,11 @@ import { SnapshotVersion } from '../core/snapshot_version'; * 1. Initial version including Mutation Queue, Query Cache, and Remote Document * Cache * 2. Added targetCount to targetGlobal row. + * 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 */ -export const SCHEMA_VERSION = 2; +export const SCHEMA_VERSION = 3; /** * Performs database creation and schema upgrades. @@ -46,11 +49,8 @@ export function createOrUpgradeDb( fromVersion: number, toVersion: number ): PersistencePromise { - // This function currently supports migrating to schema version 1 (Mutation - // Queue, Query and Remote Document Cache) and schema version 2 (Query - // counting). assert( - fromVersion < toVersion && fromVersion >= 0 && toVersion <= 2, + fromVersion < toVersion && fromVersion >= 0 && toVersion <= SCHEMA_VERSION, 'Unexpected schema upgrade from v${fromVersion} to v{toVersion}.' ); @@ -67,6 +67,16 @@ export function createOrUpgradeDb( saveTargetCount(txn, targetGlobal) ); } + + p = p.next(() => { + // Brand new clients don't need to drop and recreate--only clients that + // potentially have corrupt data. + if (fromVersion !== 0 && fromVersion < 3 && toVersion >= 3) { + dropQueryCache(db); + createQueryCache(db); + } + }); + return p; } @@ -507,6 +517,12 @@ function createQueryCache(db: IDBDatabase): void { db.createObjectStore(DbTargetGlobal.store); } +function dropQueryCache(db: IDBDatabase): void { + db.deleteObjectStore(DbTargetDocument.store); + db.deleteObjectStore(DbTarget.store); + db.deleteObjectStore(DbTargetGlobal.store); +} + /** * Counts the number of targets persisted and adds that value to the target * global singleton. diff --git a/packages/firestore/test/unit/local/indexeddb_schema.test.ts b/packages/firestore/test/unit/local/indexeddb_schema.test.ts index c5b4eb63dbd..0be6e679bd7 100644 --- a/packages/firestore/test/unit/local/indexeddb_schema.test.ts +++ b/packages/firestore/test/unit/local/indexeddb_schema.test.ts @@ -19,9 +19,12 @@ import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence'; import { ALL_STORES, createOrUpgradeDb, + DbMutationBatch, + DbMutationBatchKey, DbTarget, DbTargetGlobal, - DbTargetGlobalKey + DbTargetGlobalKey, + DbTargetKey } from '../../../src/local/indexeddb_schema'; import { SimpleDb, SimpleDbTransaction } from '../../../src/local/simple_db'; import { PersistencePromise } from '../../../src/local/persistence_promise'; @@ -135,4 +138,63 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => { }) ); }); + + it('drops the query cache from 2 to 3', () => { + const userId = 'user'; + const batchId = 1; + const targetId = 2; + + const expectedMutation = new DbMutationBatch(userId, batchId, 1000, []); + + return withDb(2, db => { + const sdb = new SimpleDb(db); + return sdb.runTransaction( + 'readwrite', + [DbTarget.store, DbMutationBatch.store], + txn => { + const targets = txn.store(DbTarget.store); + const mutations = txn.store( + DbMutationBatch.store + ); + + return ( + targets + // tslint:disable-next-line:no-any + .put({ targetId, canonicalId: 'foo' } as any) + .next(() => mutations.put(expectedMutation)) + ); + } + ); + }).then(() => { + return withDb(3, db => { + expect(db.version).to.equal(3); + expect(getAllObjectStores(db)).to.have.members(ALL_STORES); + + const sdb = new SimpleDb(db); + return sdb.runTransaction( + 'readwrite', + [DbTarget.store, DbMutationBatch.store], + txn => { + const targets = txn.store(DbTarget.store); + const mutations = txn.store( + DbMutationBatch.store + ); + + return targets + .get(targetId) + .next(target => { + // The target should have been dropped + expect(target).to.be.null; + }) + .next(() => mutations.get([userId, batchId])) + .next(mutation => { + // Mutations should be unaffected. + expect(mutation.userId).to.equal(userId); + expect(mutation.batchId).to.equal(batchId); + }); + } + ); + }); + }); + }); });