-
Notifications
You must be signed in to change notification settings - Fork 938
[Multi-Tab] Adding Schema Migration #485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
eb44929
07e44e7
3e1053f
5e84782
fd50301
be3b463
de237c5
53e56b5
7b3bedb
7b97c0a
c176eff
9154aad
6b072ff
662365f
dd21376
5e54b3a
cb81df8
7991970
2c6d6e1
f16bb4f
038c159
b70303c
34ab25a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,53 +22,48 @@ import { assert } from '../util/assert'; | |
|
||
import { encode, EncodedResourcePath } from './encoded_resource_path'; | ||
|
||
export const SCHEMA_VERSION = 1; | ||
|
||
/** Performs database creation and (in the future) upgrades between versions. */ | ||
export function createOrUpgradeDb(db: IDBDatabase, oldVersion: number): void { | ||
assert(oldVersion === 0, 'Unexpected upgrade from version ' + oldVersion); | ||
|
||
db.createObjectStore(DbMutationQueue.store, { | ||
keyPath: DbMutationQueue.keyPath | ||
}); | ||
|
||
// TODO(mikelehen): Get rid of "as any" if/when TypeScript fixes their | ||
// types. https://github.com/Microsoft/TypeScript/issues/14322 | ||
db.createObjectStore( | ||
DbMutationBatch.store, | ||
// tslint:disable-next-line:no-any | ||
{ keyPath: DbMutationBatch.keyPath as any } | ||
); | ||
/** | ||
* Default Schema Version for the Web client (containing the Mutation Queue, the | ||
* Query and the Remote Document Cache). | ||
*/ | ||
export const DEFAULT_SCHEMA_VERSION = 1; | ||
|
||
const targetDocumentsStore = db.createObjectStore( | ||
DbTargetDocument.store, | ||
// tslint:disable-next-line:no-any | ||
{ keyPath: DbTargetDocument.keyPath as any } | ||
); | ||
targetDocumentsStore.createIndex( | ||
DbTargetDocument.documentTargetsIndex, | ||
DbTargetDocument.documentTargetsKeyPath, | ||
{ unique: true } | ||
/** | ||
* Performs database creation and schema upgrades. | ||
* | ||
* Note that in production, this method is only ever used to upgrade the schema | ||
* to DEFAULT_SCHEMA_VERSION. Different versions are only used for testing and | ||
* local feature development. | ||
*/ | ||
export function createOrUpgradeDb( | ||
db: IDBDatabase, | ||
fromVersion: number, | ||
toVersion: number | ||
): void { | ||
// This function currently supports migrating to schema version 1 (Mutation | ||
// Queue, Query and Remote Document Cache) and schema version 2 (Multi-Tab). | ||
assert( | ||
fromVersion < toVersion && fromVersion >= 0 && toVersion <= 2, | ||
'Unexpected schema upgrade from v${fromVersion} to v{toVersion}.' | ||
); | ||
|
||
const targetStore = db.createObjectStore(DbTarget.store, { | ||
keyPath: DbTarget.keyPath | ||
}); | ||
// NOTE: This is unique only because the TargetId is the suffix. | ||
targetStore.createIndex( | ||
DbTarget.queryTargetsIndexName, | ||
DbTarget.queryTargetsKeyPath, | ||
{ unique: true } | ||
); | ||
if (fromVersion < 1 && toVersion >= 1) { | ||
createOwnerStore(db); | ||
createMutationQueue(db); | ||
createQueryCache(db); | ||
createRemoteDocumentCache(db); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're not doing the switch / case pattern, I think you should still organize similarly (in particular put version 1 stuff before version 2), and I think we need to have a simple pattern that makes it obvious how to add code whenever we bump SCHEMA_VERSION... As-is, the Perhaps:
Or you could do a for loop:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used code snippet version 1. |
||
|
||
// NOTE: keys for these stores are specified explicitly rather than using a | ||
// keyPath. | ||
db.createObjectStore(DbDocumentMutation.store); | ||
db.createObjectStore(DbRemoteDocument.store); | ||
db.createObjectStore(DbOwner.store); | ||
db.createObjectStore(DbTargetGlobal.store); | ||
if (fromVersion < 2 && toVersion >= 2) { | ||
createClientMetadataStore(db); | ||
createTargetChangeStore(db); | ||
} | ||
} | ||
|
||
// TODO(mikelehen): Get rid of "as any" if/when TypeScript fixes their types. | ||
// https://github.com/Microsoft/TypeScript/issues/14322 | ||
type KeyPath = any; // tslint:disable-line:no-any | ||
|
||
/** | ||
* Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects. | ||
*/ | ||
|
@@ -94,6 +89,10 @@ export class DbOwner { | |
constructor(public ownerId: string, public leaseTimestampMs: number) {} | ||
} | ||
|
||
function createOwnerStore(db: IDBDatabase): void { | ||
db.createObjectStore(DbOwner.store); | ||
} | ||
|
||
/** Object keys in the 'mutationQueues' store are userId strings. */ | ||
export type DbMutationQueueKey = string; | ||
|
||
|
@@ -183,6 +182,18 @@ export class DbMutationBatch { | |
*/ | ||
export type DbDocumentMutationKey = [string, EncodedResourcePath, BatchId]; | ||
|
||
function createMutationQueue(db: IDBDatabase): void { | ||
db.createObjectStore(DbMutationQueue.store, { | ||
keyPath: DbMutationQueue.keyPath | ||
}); | ||
|
||
db.createObjectStore(DbMutationBatch.store, { | ||
keyPath: DbMutationBatch.keyPath as KeyPath | ||
}); | ||
|
||
db.createObjectStore(DbDocumentMutation.store); | ||
} | ||
|
||
/** | ||
* An object to be stored in the 'documentMutations' store in IndexedDb. | ||
* | ||
|
@@ -241,6 +252,10 @@ export class DbDocumentMutation { | |
*/ | ||
export type DbRemoteDocumentKey = string[]; | ||
|
||
function createRemoteDocumentCache(db: IDBDatabase): void { | ||
db.createObjectStore(DbRemoteDocument.store); | ||
} | ||
|
||
/** | ||
* Represents the known absence of a document at a particular version. | ||
* Stored in IndexedDb as part of a DbRemoteDocument object. | ||
|
@@ -455,11 +470,100 @@ export class DbTargetGlobal { | |
) {} | ||
} | ||
|
||
function createQueryCache(db: IDBDatabase): void { | ||
const targetDocumentsStore = db.createObjectStore(DbTargetDocument.store, { | ||
keyPath: DbTargetDocument.keyPath as KeyPath | ||
}); | ||
targetDocumentsStore.createIndex( | ||
DbTargetDocument.documentTargetsIndex, | ||
DbTargetDocument.documentTargetsKeyPath, | ||
{ unique: true } | ||
); | ||
|
||
const targetStore = db.createObjectStore(DbTarget.store, { | ||
keyPath: DbTarget.keyPath | ||
}); | ||
|
||
// NOTE: This is unique only because the TargetId is the suffix. | ||
targetStore.createIndex( | ||
DbTarget.queryTargetsIndexName, | ||
DbTarget.queryTargetsKeyPath, | ||
{ unique: true } | ||
); | ||
db.createObjectStore(DbTargetGlobal.store); | ||
} | ||
|
||
/** | ||
* The list of all IndexedDB stored used by the SDK. This is used when creating | ||
* transactions so that access across all stores is done atomically. | ||
* An object representing the changes at a particular snapshot version for the | ||
* given target. This is used to facilitate storing query changelogs in the | ||
* targetChanges object store. | ||
* | ||
* PORTING NOTE: This is used for change propagation during multi-tab syncing | ||
* and not needed on iOS and Android. | ||
*/ | ||
export const ALL_STORES = [ | ||
export class DbTargetChange { | ||
/** Name of the IndexedDb object store. */ | ||
static store = 'targetChanges'; | ||
|
||
/** Keys are automatically assigned via the targetId and snapshotVersion. */ | ||
static keyPath = ['targetId', 'snapshotVersion']; | ||
|
||
constructor( | ||
/** | ||
* The targetId identifying a target. | ||
*/ | ||
public targetId: TargetId, | ||
/** | ||
* The snapshot version for this change. | ||
*/ | ||
public snapshotVersion: DbTimestamp, | ||
/** | ||
* The keys of the changed documents in this snapshot. | ||
*/ | ||
public changes: { | ||
added?: EncodedResourcePath[]; | ||
modified?: EncodedResourcePath[]; | ||
removed?: EncodedResourcePath[]; | ||
} | ||
) {} | ||
} | ||
|
||
function createTargetChangeStore(db: IDBDatabase): void { | ||
db.createObjectStore(DbTargetChange.store, { | ||
keyPath: DbTargetChange.keyPath as KeyPath | ||
}); | ||
} | ||
|
||
/** | ||
* A record of the metadata state of each client. | ||
* | ||
* PORTING NOTE: This is used to synchronize multi-tab state and does not need | ||
* to be ported to iOS or Android. | ||
*/ | ||
export class DbClientMetadata { | ||
/** Name of the IndexedDb object store. */ | ||
static store = 'clientMetadata'; | ||
|
||
/** Keys are automatically assigned via the clientKey properties. */ | ||
static keyPath = ['clientKey']; | ||
|
||
constructor( | ||
/** The auto-generated client key assigned at client startup. */ | ||
public clientKey: string, | ||
/** The last time this state was updated. */ | ||
public updateTimeMs: DbTimestamp, | ||
/** Whether this client is running in a foreground tab. */ | ||
public inForeground: boolean | ||
) {} | ||
} | ||
|
||
function createClientMetadataStore(db: IDBDatabase): void { | ||
db.createObjectStore(DbClientMetadata.store, { | ||
keyPath: DbClientMetadata.keyPath as KeyPath | ||
}); | ||
} | ||
|
||
const V1_STORES = [ | ||
DbMutationQueue.store, | ||
DbMutationBatch.store, | ||
DbDocumentMutation.store, | ||
|
@@ -469,3 +573,18 @@ export const ALL_STORES = [ | |
DbTargetGlobal.store, | ||
DbTargetDocument.store | ||
]; | ||
|
||
const V2_STORES = [DbClientMetadata.store, DbTargetChange.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 DEFAULT_STORES = V1_STORES; | ||
|
||
/** | ||
* The list of all IndexedDb stores. This is only available in multi-tab enabled | ||
* clients with the schema version 2. | ||
*/ | ||
export const ALL_STORES = [...V1_STORES, ...V2_STORES]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 => SCHEMA_VERSION ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually changed
SCHEMA_VERSION
toDEFAULT_SCHEMA_VERSION
and reset the value back to 1. This way, I can merge this code to master, and Greg can build the GC migration on top of this.