From f3329ecf062b217b2e062b92034152f623685a87 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 12 Dec 2019 22:39:45 +0100 Subject: refactor DB access --- src/util/query.ts | 302 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 196 insertions(+), 106 deletions(-) (limited to 'src/util/query.ts') diff --git a/src/util/query.ts b/src/util/query.ts index e05656bb7..08a8fec02 100644 --- a/src/util/query.ts +++ b/src/util/query.ts @@ -25,22 +25,6 @@ */ import { openPromise } from "./promiseUtils"; -/** - * Result of an inner join. - */ -export interface JoinResult { - left: L; - right: R; -} - -/** - * Result of a left outer join. - */ -export interface JoinLeftResult { - left: L; - right?: R; -} - /** * Definition of an object store. */ @@ -95,46 +79,6 @@ function transactionToPromise(tx: IDBTransaction): Promise { }); } -export async function oneShotGet( - db: IDBDatabase, - store: Store, - key: any, -): Promise { - const tx = db.transaction([store.name], "readonly"); - const req = tx.objectStore(store.name).get(key); - const v = await requestToPromise(req); - await transactionToPromise(tx); - return v; -} - -export async function oneShotGetIndexed( - db: IDBDatabase, - index: Index, - key: any, -): Promise { - const tx = db.transaction([index.storeName], "readonly"); - const req = tx - .objectStore(index.storeName) - .index(index.indexName) - .get(key); - const v = await requestToPromise(req); - await transactionToPromise(tx); - return v; -} - -export async function oneShotPut( - db: IDBDatabase, - store: Store, - value: T, - key?: any, -): Promise { - const tx = db.transaction([store.name], "readwrite"); - const req = tx.objectStore(store.name).put(value, key); - const v = await requestToPromise(req); - await transactionToPromise(tx); - return v; -} - function applyMutation( req: IDBRequest, f: (x: T) => T | undefined, @@ -166,18 +110,6 @@ function applyMutation( }); } -export async function oneShotMutate( - db: IDBDatabase, - store: Store, - key: any, - f: (x: T) => T | undefined, -): Promise { - const tx = db.transaction([store.name], "readwrite"); - const req = tx.objectStore(store.name).openCursor(key); - await applyMutation(req, f); - await transactionToPromise(tx); -} - type CursorResult = CursorEmptyResult | CursorValueResult; interface CursorEmptyResult { @@ -294,28 +226,6 @@ class ResultStream { } } -export function oneShotIter( - db: IDBDatabase, - store: Store, -): ResultStream { - const tx = db.transaction([store.name], "readonly"); - const req = tx.objectStore(store.name).openCursor(); - return new ResultStream(req); -} - -export function oneShotIterIndex( - db: IDBDatabase, - index: Index, - query?: any, -): ResultStream { - const tx = db.transaction([index.storeName], "readonly"); - const req = tx - .objectStore(index.storeName) - .index(index.indexName) - .openCursor(query); - return new ResultStream(req); -} - export class TransactionHandle { constructor(private tx: IDBTransaction) {} @@ -361,22 +271,6 @@ export class TransactionHandle { } } -export function runWithReadTransaction( - db: IDBDatabase, - stores: Store[], - f: (t: TransactionHandle) => Promise, -): Promise { - return runWithTransaction(db, stores, f, "readonly"); -} - -export function runWithWriteTransaction( - db: IDBDatabase, - stores: Store[], - f: (t: TransactionHandle) => Promise, -): Promise { - return runWithTransaction(db, stores, f, "readwrite"); -} - function runWithTransaction( db: IDBDatabase, stores: Store[], @@ -470,7 +364,203 @@ export class Index { protected _dummyKey: S | undefined; } +/** + * Return a promise that resolves + * to the taler wallet db. + */ +export function openDatabase( + idbFactory: IDBFactory, + databaseName: string, + databaseVersion: number, + schema: any, + onVersionChange: () => void, + onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void, +): Promise { + return new Promise((resolve, reject) => { + const req = idbFactory.open(databaseName, databaseVersion); + req.onerror = e => { + console.log("taler database error", e); + reject(new Error("database error")); + }; + req.onsuccess = e => { + req.result.onversionchange = (evt: IDBVersionChangeEvent) => { + console.log( + `handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`, + ); + req.result.close(); + onVersionChange(); + }; + resolve(req.result); + }; + req.onupgradeneeded = e => { + const db = req.result; + console.log( + `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 = 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 = (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; + } + }; + }); +} + /** * Exception that should be thrown by client code to abort a transaction. */ export const TransactionAbort = Symbol("transaction_abort"); + +export class Database { + constructor(private db: IDBDatabase) {} + + static deleteDatabase(idbFactory: IDBFactory, dbName: string) { + idbFactory.deleteDatabase(dbName); + } + + async exportDatabase(): Promise { + const db = this.db; + const dump = { + name: db.name, + stores: {} as { [s: string]: any }, + version: db.version, + }; + + return new Promise((resolve, reject) => { + const tx = db.transaction(Array.from(db.objectStoreNames)); + tx.addEventListener("complete", () => { + resolve(dump); + }); + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < db.objectStoreNames.length; i++) { + const name = db.objectStoreNames[i]; + const storeDump = {} as { [s: string]: any }; + dump.stores[name] = storeDump; + tx.objectStore(name) + .openCursor() + .addEventListener("success", (e: Event) => { + const cursor = (e.target as any).result; + if (cursor) { + storeDump[cursor.key] = cursor.value; + cursor.continue(); + } + }); + } + }); + } + + importDatabase(dump: any): Promise { + const db = this.db; + console.log("importing db", dump); + return new Promise((resolve, reject) => { + const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite"); + if (dump.stores) { + for (const storeName in dump.stores) { + const objects = []; + const dumpStore = dump.stores[storeName]; + for (const key in dumpStore) { + objects.push(dumpStore[key]); + } + console.log(`importing ${objects.length} records into ${storeName}`); + const store = tx.objectStore(storeName); + for (const obj of objects) { + store.put(obj); + } + } + } + tx.addEventListener("complete", () => { + resolve(); + }); + }); + } + + async get(store: Store, key: any): Promise { + const tx = this.db.transaction([store.name], "readonly"); + const req = tx.objectStore(store.name).get(key); + const v = await requestToPromise(req); + await transactionToPromise(tx); + return v; + } + + async getIndexed( + index: Index, + key: any, + ): Promise { + const tx = this.db.transaction([index.storeName], "readonly"); + const req = tx + .objectStore(index.storeName) + .index(index.indexName) + .get(key); + const v = await requestToPromise(req); + await transactionToPromise(tx); + return v; + } + + async put(store: Store, value: T, key?: any): Promise { + const tx = this.db.transaction([store.name], "readwrite"); + const req = tx.objectStore(store.name).put(value, key); + const v = await requestToPromise(req); + await transactionToPromise(tx); + return v; + } + + async mutate( + store: Store, + key: any, + f: (x: T) => T | undefined, + ): Promise { + const tx = this.db.transaction([store.name], "readwrite"); + const req = tx.objectStore(store.name).openCursor(key); + await applyMutation(req, f); + await transactionToPromise(tx); + } + + iter(store: Store): ResultStream { + const tx = this.db.transaction([store.name], "readonly"); + const req = tx.objectStore(store.name).openCursor(); + return new ResultStream(req); + } + + iterIndex( + index: Index, + query?: any, + ): ResultStream { + const tx = this.db.transaction([index.storeName], "readonly"); + const req = tx + .objectStore(index.storeName) + .index(index.indexName) + .openCursor(query); + return new ResultStream(req); + } + + async runWithReadTransaction( + stores: Store[], + f: (t: TransactionHandle) => Promise, + ): Promise { + return runWithTransaction(this.db, stores, f, "readonly"); + } + + async runWithWriteTransaction( + stores: Store[], + f: (t: TransactionHandle) => Promise, + ): Promise { + return runWithTransaction(this.db, stores, f, "readwrite"); + } +} -- cgit v1.2.3