Skip to content

Commit eb05b2d

Browse files
committed
Add a schema migration that drops the query cache
This is a force fix for potential existence filter mismatches caused by firebase/firebase-ios-sdk#1548 The essential problem is that when resuming a query, the server is allowed to forget deletes. If the number of incorrectly synthesized deletes matches the number of server-forgotten deletes then the existence filter can give a false positive, preventing the cache from self healing. Dropping the query cache clears any client-side resume token which prevents a false positive existence filter mismatch. Note that the remote document cache and mutation queues are unaffected so any cached documents that do exist will still work while offline.
1 parent 6d66392 commit eb05b2d

File tree

2 files changed

+81
-6
lines changed

2 files changed

+81
-6
lines changed

packages/firestore/src/local/indexeddb_schema.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ import { SnapshotVersion } from '../core/snapshot_version';
3030
* 1. Initial version including Mutation Queue, Query Cache, and Remote Document
3131
* Cache
3232
* 2. Added targetCount to targetGlobal row.
33+
* 3. Dropped and re-created Query Cache to deal with cache corruption related
34+
* to limbo resolution. Addresses
35+
* https://github.com/firebase/firebase-ios-sdk/issues/1548
3336
*/
34-
export const SCHEMA_VERSION = 2;
37+
export const SCHEMA_VERSION = 3;
3538

3639
/**
3740
* Performs database creation and schema upgrades.
@@ -46,11 +49,8 @@ export function createOrUpgradeDb(
4649
fromVersion: number,
4750
toVersion: number
4851
): PersistencePromise<void> {
49-
// This function currently supports migrating to schema version 1 (Mutation
50-
// Queue, Query and Remote Document Cache) and schema version 2 (Query
51-
// counting).
5252
assert(
53-
fromVersion < toVersion && fromVersion >= 0 && toVersion <= 2,
53+
fromVersion < toVersion && fromVersion >= 0 && toVersion <= SCHEMA_VERSION,
5454
'Unexpected schema upgrade from v${fromVersion} to v{toVersion}.'
5555
);
5656

@@ -67,6 +67,13 @@ export function createOrUpgradeDb(
6767
saveTargetCount(txn, targetGlobal)
6868
);
6969
}
70+
71+
// Brand new clients don't need to drop and recreate--only clients that
72+
// potentially have corrupt data.
73+
if (fromVersion !== 0 && fromVersion < 3 && toVersion >= 3) {
74+
dropQueryCache(db);
75+
createQueryCache(db);
76+
}
7077
return p;
7178
}
7279

@@ -507,6 +514,12 @@ function createQueryCache(db: IDBDatabase): void {
507514
db.createObjectStore(DbTargetGlobal.store);
508515
}
509516

517+
function dropQueryCache(db: IDBDatabase): void {
518+
db.deleteObjectStore(DbTargetDocument.store);
519+
db.deleteObjectStore(DbTarget.store);
520+
db.deleteObjectStore(DbTargetGlobal.store);
521+
}
522+
510523
/**
511524
* Counts the number of targets persisted and adds that value to the target
512525
* global singleton.

packages/firestore/test/unit/local/indexeddb_schema.test.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence';
1919
import {
2020
ALL_STORES,
2121
createOrUpgradeDb,
22+
DbMutationBatch,
23+
DbMutationBatchKey,
2224
DbTarget,
2325
DbTargetGlobal,
24-
DbTargetGlobalKey
26+
DbTargetGlobalKey,
27+
DbTargetKey
2528
} from '../../../src/local/indexeddb_schema';
2629
import { SimpleDb, SimpleDbTransaction } from '../../../src/local/simple_db';
2730
import { PersistencePromise } from '../../../src/local/persistence_promise';
@@ -135,4 +138,63 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => {
135138
})
136139
);
137140
});
141+
142+
it('drops the query cache from 2 to 3', () => {
143+
const userId = 'user';
144+
const batchId = 1;
145+
const targetId = 2;
146+
147+
const expectedMutation = new DbMutationBatch(userId, batchId, 1000, []);
148+
149+
return withDb(2, db => {
150+
const sdb = new SimpleDb(db);
151+
return sdb.runTransaction(
152+
'readwrite',
153+
[DbTarget.store, DbMutationBatch.store],
154+
txn => {
155+
const targets = txn.store<DbTargetKey, DbTarget>(DbTarget.store);
156+
const mutations = txn.store<DbMutationBatchKey, DbMutationBatch>(
157+
DbMutationBatch.store
158+
);
159+
160+
return PersistencePromise.resolve().next(() =>
161+
targets
162+
// tslint:disable-next-line:no-any
163+
.put({ targetId, canonicalId: 'foo' } as any)
164+
.next(() => mutations.put(expectedMutation))
165+
);
166+
}
167+
);
168+
}).then(() => {
169+
return withDb(3, db => {
170+
expect(db.version).to.equal(3);
171+
expect(getAllObjectStores(db)).to.have.members(ALL_STORES);
172+
173+
const sdb = new SimpleDb(db);
174+
return sdb.runTransaction(
175+
'readwrite',
176+
[DbTarget.store, DbMutationBatch.store],
177+
txn => {
178+
const targets = txn.store<DbTargetKey, DbTarget>(DbTarget.store);
179+
const mutations = txn.store<DbMutationBatchKey, DbMutationBatch>(
180+
DbMutationBatch.store
181+
);
182+
183+
return PersistencePromise.resolve()
184+
.next(() => targets.get(targetId))
185+
.next(target => {
186+
// The target should have been dropped
187+
expect(target).to.be.null;
188+
})
189+
.next(() => mutations.get([userId, batchId]))
190+
.next(mutation => {
191+
// Mutations should be unaffected.
192+
expect(mutation.userId).to.equal(userId);
193+
expect(mutation.batchId).to.equal(batchId);
194+
});
195+
}
196+
);
197+
});
198+
});
199+
});
138200
});

0 commit comments

Comments
 (0)