prepare for schema migrations

This commit is contained in:
Florian Dold 2019-12-19 13:48:37 +01:00
parent 20ebc44420
commit 49e3b3e5b9
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
7 changed files with 128 additions and 56 deletions

View File

@ -1,7 +1,7 @@
import { Stores } from "./types/dbTypes"; import { Stores } from "./types/dbTypes";
import { openDatabase, Database } from "./util/query"; import { openDatabase, Database, Store, Index } from "./util/query";
const TALER_DB_NAME = "taler"; const TALER_DB_NAME = "taler-wallet";
/** /**
* Current database version, should be incremented * Current database version, should be incremented
@ -9,7 +9,7 @@ const TALER_DB_NAME = "taler";
* In the future we might consider adding migration functions for * In the future we might consider adding migration functions for
* each version increment. * each version increment.
*/ */
export const WALLET_DB_VERSION = 28; export const WALLET_DB_VERSION = 1;
/** /**
* Return a promise that resolves * Return a promise that resolves
@ -18,15 +18,38 @@ export const WALLET_DB_VERSION = 28;
export function openTalerDatabase( export function openTalerDatabase(
idbFactory: IDBFactory, idbFactory: IDBFactory,
onVersionChange: () => void, onVersionChange: () => void,
onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void,
): Promise<IDBDatabase> { ): Promise<IDBDatabase> {
const onUpgradeNeeded = (
db: IDBDatabase,
oldVersion: number,
newVersion: number,
) => {
switch (oldVersion) {
case 0: // DB does not exist yet
for (const n in Stores) {
if ((Stores as any)[n] instanceof Store) {
const si: Store<any> = (Stores as any)[n];
const s = db.createObjectStore(si.name, si.storeParams);
for (const indexName in si as any) {
if ((si as any)[indexName] instanceof Index) {
const ii: Index<any, any> = (si as any)[indexName];
s.createIndex(ii.indexName, ii.keyPath, ii.options);
}
}
}
}
break;
default:
throw Error("unsupported existig DB version");
}
};
return openDatabase( return openDatabase(
idbFactory, idbFactory,
TALER_DB_NAME, TALER_DB_NAME,
WALLET_DB_VERSION, WALLET_DB_VERSION,
Stores,
onVersionChange, onVersionChange,
onUpgradeUnsupported, onUpgradeNeeded,
); );
} }

View File

@ -25,9 +25,7 @@
import { Wallet } from "../wallet"; import { Wallet } from "../wallet";
import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge"; import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge";
import { openTalerDatabase } from "../db"; import { openTalerDatabase } from "../db";
import { import { HttpRequestLibrary } from "../util/http";
HttpRequestLibrary,
} from "../util/http";
import * as amounts from "../util/amounts"; import * as amounts from "../util/amounts";
import { Bank } from "./bank"; import { Bank } from "./bank";
import fs = require("fs"); import fs = require("fs");
@ -39,7 +37,6 @@ import { Logger } from "../util/logging";
const logger = new Logger("helpers.ts"); const logger = new Logger("helpers.ts");
export interface DefaultNodeWalletArgs { export interface DefaultNodeWalletArgs {
/** /**
* Location of the wallet database. * Location of the wallet database.
@ -112,18 +109,9 @@ export async function getDefaultNodeWallet(
throw Error(); throw Error();
}; };
const myUnsupportedUpgrade = () => {
console.error("unsupported database migration");
throw Error();
};
shimIndexedDB(myBridgeIdbFactory); shimIndexedDB(myBridgeIdbFactory);
const myDb = await openTalerDatabase( const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
myIdbFactory,
myVersionChange,
myUnsupportedUpgrade,
);
//const worker = new SynchronousCryptoWorkerFactory(); //const worker = new SynchronousCryptoWorkerFactory();
//const worker = new NodeCryptoWorkerFactory(); //const worker = new NodeCryptoWorkerFactory();

View File

@ -1366,6 +1366,34 @@ export interface BankWithdrawUriRecord {
reservePub: string; reservePub: string;
} }
export const enum ImportPayloadType {
CoreSchema = "core-schema",
}
/**
* Record to keep track of data imported into the wallet.
*/
export class WalletImportRecord {
/**
* Unique ID to reference this import record.
*/
walletImportId: string;
/**
* When was the data imported?
*/
timestampImportStarted: Timestamp;
timestampImportFinished: Timestamp | undefined;
payloadType: ImportPayloadType;
/**
* The actual data to import.
*/
payload: any;
}
/* tslint:disable:completed-docs */ /* tslint:disable:completed-docs */
/** /**
@ -1517,6 +1545,12 @@ export namespace Stores {
} }
} }
class WalletImportsStore extends Store<WalletImportRecord> {
constructor() {
super("walletImports", { keyPath: "walletImportId" });
}
}
export const coins = new CoinsStore(); export const coins = new CoinsStore();
export const coinsReturns = new Store<CoinsReturnRecord>("coinsReturns", { export const coinsReturns = new Store<CoinsReturnRecord>("coinsReturns", {
keyPath: "contractTermsHash", keyPath: "contractTermsHash",
@ -1539,6 +1573,7 @@ export namespace Stores {
export const payEvents = new PayEventsStore(); export const payEvents = new PayEventsStore();
export const reserveUpdatedEvents = new ReserveUpdatedEventsStore(); export const reserveUpdatedEvents = new ReserveUpdatedEventsStore();
export const exchangeUpdatedEvents = new ExchangeUpdatedEventsStore(); export const exchangeUpdatedEvents = new ExchangeUpdatedEventsStore();
export const walletImports = new WalletImportsStore();
} }
/* tslint:enable:completed-docs */ /* tslint:enable:completed-docs */

58
src/types/schemacore.ts Normal file
View File

@ -0,0 +1,58 @@
/*
This file is part of GNU Taler
(C) 2019 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Core of the wallet's schema, used for painless export, import
* and schema migration.
*
* If this schema is extended, it must be extended in a completely
* backwards-compatible way.
*/
interface CoreCoin {
exchangeBaseUrl: string;
coinPub: string;
coinPriv: string;
amountRemaining: string;
}
interface CorePurchase {
noncePub: string;
noncePriv: string;
paySig: string;
contractTerms: any;
}
interface CoreReserve {
reservePub: string;
reservePriv: string;
exchangeBaseUrl: string;
}
interface SchemaCore {
coins: CoreCoin[];
purchases: CorePurchase[];
/**
* Schema version (of full schema) of wallet that exported the core schema.
*/
versionExporter: number;
/**
* Schema version of the database that has been exported to the core schema
*/
versionSourceDatabase: number;
}

View File

@ -383,9 +383,8 @@ export function openDatabase(
idbFactory: IDBFactory, idbFactory: IDBFactory,
databaseName: string, databaseName: string,
databaseVersion: number, databaseVersion: number,
schema: any,
onVersionChange: () => void, onVersionChange: () => void,
onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void, onUpgradeNeeded: (db: IDBDatabase, oldVersion: number, newVersion: number) => void,
): Promise<IDBDatabase> { ): Promise<IDBDatabase> {
return new Promise<IDBDatabase>((resolve, reject) => { return new Promise<IDBDatabase>((resolve, reject) => {
const req = idbFactory.open(databaseName, databaseVersion); const req = idbFactory.open(databaseName, databaseVersion);
@ -405,31 +404,10 @@ export function openDatabase(
}; };
req.onupgradeneeded = e => { req.onupgradeneeded = e => {
const db = req.result; const db = req.result;
onUpgradeNeeded(db, e.oldVersion, e.newVersion!);
console.log( console.log(
`DB: upgrade needed: oldVersion=${e.oldVersion}, newVersion=${e.newVersion}`, `DB: upgrade needed: oldVersion=${e.oldVersion}, newVersion=${e.newVersion}`,
); );
switch (e.oldVersion) {
case 0: // DB does not exist yet
for (const n in schema) {
if (schema[n] instanceof Store) {
const si: Store<any> = schema[n];
const s = db.createObjectStore(si.name, si.storeParams);
for (const indexName in si as any) {
if ((si as any)[indexName] instanceof Index) {
const ii: Index<any, any> = (si as any)[indexName];
s.createIndex(ii.indexName, ii.keyPath, ii.options);
}
}
}
}
break;
default:
if (e.oldVersion !== databaseVersion) {
onUpgradeUnsupported(e.oldVersion, databaseVersion);
throw Error("incompatible DB");
}
break;
}
}; };
}); });
} }

View File

@ -390,16 +390,6 @@ let outdatedDbVersion: number | undefined;
let walletInit: OpenedPromise<void> = openPromise<void>(); let walletInit: OpenedPromise<void> = openPromise<void>();
function handleUpgradeUnsupported(oldDbVersion: number, newDbVersion: number) {
console.log("DB migration not supported");
outdatedDbVersion = oldDbVersion;
chrome.tabs.create({
url: chrome.extension.getURL("/src/webex/pages/reset-required.html"),
});
setBadgeText({ text: "err" });
chrome.browserAction.setBadgeBackgroundColor({ color: "#F00" });
}
async function reinitWallet() { async function reinitWallet() {
if (currentWallet) { if (currentWallet) {
currentWallet.stop(); currentWallet.stop();
@ -412,7 +402,6 @@ async function reinitWallet() {
currentDatabase = await openTalerDatabase( currentDatabase = await openTalerDatabase(
indexedDB, indexedDB,
reinitWallet, reinitWallet,
handleUpgradeUnsupported,
); );
} catch (e) { } catch (e) {
console.error("could not open database", e); console.error("could not open database", e);

View File

@ -67,6 +67,7 @@
"src/types/history.ts", "src/types/history.ts",
"src/types/notifications.ts", "src/types/notifications.ts",
"src/types/pending.ts", "src/types/pending.ts",
"src/types/schemacore.ts",
"src/types/talerTypes.ts", "src/types/talerTypes.ts",
"src/types/types-test.ts", "src/types/types-test.ts",
"src/types/walletTypes.ts", "src/types/walletTypes.ts",