Skip to content

Commit b710edc

Browse files
Canonical ID Schema Migration
1 parent 251a980 commit b710edc

File tree

2 files changed

+79
-17
lines changed

2 files changed

+79
-17
lines changed

packages/firestore/src/local/indexeddb_schema.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,23 @@ import { SimpleDbSchemaConverter, SimpleDbTransaction } from './simple_db';
3333

3434
/**
3535
* Schema Version for the Web client:
36-
* 1. Initial version including Mutation Queue, Query Cache, and Remote Document
37-
* Cache
38-
* 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
39-
* longer required because migration 3 unconditionally clears it.
40-
* 3. Dropped and re-created Query Cache to deal with cache corruption related
41-
* to limbo resolution. Addresses
42-
* https://github.com/firebase/firebase-ios-sdk/issues/1548
43-
* 4. Multi-Tab Support.
44-
* 5. Removal of held write acks.
45-
* 6. Create document global for tracking document cache size.
46-
* 7. Ensure every cached document has a sentinel row with a sequence number.
47-
* 8. Add collection-parent index for Collection Group queries.
48-
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
49-
* an auto-incrementing ID. This is required for Index-Free queries.
36+
* 1. Initial version including Mutation Queue, Query Cache, and Remote
37+
* Document Cache
38+
* 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
39+
* longer required because migration 3 unconditionally clears it.
40+
* 3. Dropped and re-created Query Cache to deal with cache corruption related
41+
* to limbo resolution. Addresses
42+
* https://github.com/firebase/firebase-ios-sdk/issues/1548
43+
* 4. Multi-Tab Support.
44+
* 5. Removal of held write acks.
45+
* 6. Create document global for tracking document cache size.
46+
* 7. Ensure every cached document has a sentinel row with a sequence number.
47+
* 8. Add collection-parent index for Collection Group queries.
48+
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
49+
* an auto-incrementing ID. This is required for Index-Free queries.
50+
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
5051
*/
51-
export const SCHEMA_VERSION = 9;
52+
export const SCHEMA_VERSION = 10;
5253

5354
/** Performs database creation and schema upgrades. */
5455
export class SchemaConverter implements SimpleDbSchemaConverter {
@@ -71,7 +72,7 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
7172
fromVersion < toVersion &&
7273
fromVersion >= 0 &&
7374
toVersion <= SCHEMA_VERSION,
74-
`Unexpected schema upgrade from v${fromVersion} to v{toVersion}.`
75+
`Unexpected schema upgrade from v${fromVersion} to v${toVersion}.`
7576
);
7677

7778
const simpleDbTransaction = new SimpleDbTransaction(txn);
@@ -145,6 +146,10 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
145146
createRemoteDocumentReadTimeIndex(txn);
146147
});
147148
}
149+
150+
if (fromVersion < 10 && toVersion >= 10) {
151+
p = p.next(() => this.rewriteCanonicalIds(simpleDbTransaction));
152+
}
148153
return p;
149154
}
150155

@@ -299,6 +304,21 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
299304
});
300305
});
301306
}
307+
308+
private rewriteCanonicalIds(
309+
txn: SimpleDbTransaction
310+
): PersistencePromise<void> {
311+
const targetStore = txn.store<DbTargetKey, DbTarget>(DbTarget.store);
312+
313+
const persistencePromises: Array<PersistencePromise<void>> = [];
314+
targetStore.iterate((key, value) => {
315+
const originalTargetData = this.serializer.fromDbTarget(value);
316+
const updatedTargetData = this.serializer.toDbTarget(originalTargetData);
317+
persistencePromises.push(targetStore.put(updatedTargetData));
318+
});
319+
320+
return PersistencePromise.waitFor(persistencePromises);
321+
}
302322
}
303323

304324
function sentinelKey(path: ResourcePath): DbTargetDocumentKey {
@@ -1079,6 +1099,8 @@ export const V8_STORES = [...V6_STORES, DbCollectionParent.store];
10791099

10801100
// V9 does not change the set of stores.
10811101

1102+
// V10 does not change the set of stores.
1103+
10821104
/**
10831105
* The list of all default IndexedDB stores used throughout the SDK. This is
10841106
* used when creating transactions so that access across all stores is done

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

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { expect } from 'chai';
19+
import { Query } from '../../../src/core/query';
1920
import { SnapshotVersion } from '../../../src/core/snapshot_version';
2021
import { decode, encode } from '../../../src/local/encoded_resource_path';
2122
import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence';
@@ -53,12 +54,13 @@ import { LruParams } from '../../../src/local/lru_garbage_collector';
5354
import { PersistencePromise } from '../../../src/local/persistence_promise';
5455
import { ClientId } from '../../../src/local/shared_client_state';
5556
import { SimpleDb, SimpleDbTransaction } from '../../../src/local/simple_db';
57+
import { TargetData, TargetPurpose } from '../../../src/local/target_data';
5658
import { PlatformSupport } from '../../../src/platform/platform';
5759
import { firestoreV1ApiClientInterfaces } from '../../../src/protos/firestore_proto_api';
5860
import { JsonProtoSerializer } from '../../../src/remote/serializer';
5961
import { AsyncQueue } from '../../../src/util/async_queue';
6062
import { FirestoreError } from '../../../src/util/error';
61-
import { doc, path, version } from '../../util/helpers';
63+
import { doc, filter, path, version } from '../../util/helpers';
6264
import { SharedFakeWebStorage, TestPlatform } from '../../util/test_platform';
6365
import {
6466
INDEXEDDB_TEST_DATABASE_NAME,
@@ -728,6 +730,44 @@ describe('IndexedDbSchema: createOrUpgradeDb', () => {
728730
});
729731
});
730732

733+
it('rewrites canonical IDs during upgrade from version 9 to 10', async () => {
734+
await withDb(9, db => {
735+
const sdb = new SimpleDb(db);
736+
return sdb.runTransaction('readwrite-idempotent', V8_STORES, txn => {
737+
const targetsStore = txn.store<DbTargetKey, DbTarget>(DbTarget.store);
738+
739+
const filteredQuery = Query.atPath(path('collection')).addFilter(
740+
filter('foo', '==', 'bar')
741+
);
742+
const initialTargetData = new TargetData(
743+
filteredQuery.toTarget(),
744+
/* targetId= */ 2,
745+
TargetPurpose.Listen,
746+
/* sequenceNumber= */ 1
747+
);
748+
749+
const serializedData = TEST_SERIALIZER.toDbTarget(initialTargetData);
750+
serializedData.canonicalId = 'invalid_canonical_id';
751+
752+
return targetsStore.put(TEST_SERIALIZER.toDbTarget(initialTargetData));
753+
});
754+
});
755+
756+
await withDb(10, db => {
757+
const sdb = new SimpleDb(db);
758+
return sdb.runTransaction('readwrite-idempotent', V8_STORES, txn => {
759+
const targetsStore = txn.store<DbTargetKey, DbTarget>(DbTarget.store);
760+
return targetsStore.iterate((key, value) => {
761+
const targetData = TEST_SERIALIZER.fromDbTarget(value).target;
762+
const expectedCanonicalId = targetData.canonicalId();
763+
764+
const actualCanonicalId = value.canonicalId;
765+
expect(actualCanonicalId).to.equal(expectedCanonicalId);
766+
});
767+
});
768+
});
769+
});
770+
731771
it('can use read-time index after schema migration', async () => {
732772
// This test creates a database with schema version 8 that has a few
733773
// remote documents, adds an index and then reads new documents back

0 commit comments

Comments
 (0)