From 4649469b58b9a4068fa94de07f415f1bbc58794c Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 21 Sep 2022 21:47:00 +0200 Subject: [PATCH] wallet-core: DB improvements --- packages/idb-bridge/src/bridge-idb.ts | 37 ++++++++--------- packages/taler-wallet-core/src/db-utils.ts | 1 + packages/taler-wallet-core/src/db.ts | 23 ++++++++--- .../src/operations/recoup.ts | 4 +- packages/taler-wallet-core/src/util/query.ts | 40 +++++++++++++++---- 5 files changed, 72 insertions(+), 33 deletions(-) diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts index 35cedb1db..128a6900d 100644 --- a/packages/idb-bridge/src/bridge-idb.ts +++ b/packages/idb-bridge/src/bridge-idb.ts @@ -57,16 +57,17 @@ import { TransactionInactiveError, VersionError, } from "./util/errors.js"; -import { FakeDOMStringList, fakeDOMStringList } from "./util/fakeDOMStringList.js"; +import { + FakeDOMStringList, + fakeDOMStringList, +} from "./util/fakeDOMStringList.js"; import FakeEvent from "./util/FakeEvent.js"; import FakeEventTarget from "./util/FakeEventTarget.js"; import { makeStoreKeyValue } from "./util/makeStoreKeyValue.js"; import { normalizeKeyPath } from "./util/normalizeKeyPath.js"; import { openPromise } from "./util/openPromise.js"; import queueTask from "./util/queueTask.js"; -import { - checkStructuredCloneOrThrow, -} from "./util/structuredClone.js"; +import { checkStructuredCloneOrThrow } from "./util/structuredClone.js"; import { validateKeyPath } from "./util/validateKeyPath.js"; import { valueToKey } from "./util/valueToKey.js"; @@ -933,9 +934,8 @@ export class BridgeIDBFactory { ); // We need to expose the new version number to the upgrade transaction. - db._schema = this.backend.getCurrentTransactionSchema( - backendTransaction, - ); + db._schema = + this.backend.getCurrentTransactionSchema(backendTransaction); const transaction = db._internalTransaction( [], @@ -1110,9 +1110,8 @@ export class BridgeIDBIndex implements IDBIndex { this._backend.renameIndex(btx, this._objectStore.name, oldName, newName); - this._objectStore._transaction._db._schema = this._backend.getCurrentTransactionSchema( - btx, - ); + this._objectStore._transaction._db._schema = + this._backend.getCurrentTransactionSchema(btx); this._objectStore._indexesCache.delete(oldName); this._objectStore._indexesCache.set(newName, this); @@ -1629,9 +1628,8 @@ export class BridgeIDBObjectStore implements IDBObjectStore { } this._backend.renameObjectStore(btx, oldName, newName); - this._transaction._db._schema = this._backend.getCurrentTransactionSchema( - btx, - ); + this._transaction._db._schema = + this._backend.getCurrentTransactionSchema(btx); // We don't modify scope, as the scope of the transaction // doesn't matter if we're in an upgrade transaction. @@ -2235,11 +2233,8 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest { } return this._source; } - _source: - | BridgeIDBCursor - | BridgeIDBIndex - | BridgeIDBObjectStore - | null = null; + _source: BridgeIDBCursor | BridgeIDBIndex | BridgeIDBObjectStore | null = + null; transaction: BridgeIDBTransaction | null = null; readyState: "done" | "pending" = "pending"; onsuccess: EventListener | null = null; @@ -2302,7 +2297,8 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest { /** @public */ export class BridgeIDBOpenDBRequest extends BridgeIDBRequest - implements IDBOpenDBRequest { + implements IDBOpenDBRequest +{ public onupgradeneeded: EventListener | null = null; public onblocked: EventListener | null = null; @@ -2350,7 +2346,8 @@ function waitMacroQueue(): Promise { /** @public */ export class BridgeIDBTransaction extends FakeEventTarget - implements IDBTransaction { + implements IDBTransaction +{ _committed: boolean = false; /** * A transaction is active as long as new operations can be diff --git a/packages/taler-wallet-core/src/db-utils.ts b/packages/taler-wallet-core/src/db-utils.ts index b32b3d585..7f4b0de45 100644 --- a/packages/taler-wallet-core/src/db-utils.ts +++ b/packages/taler-wallet-core/src/db-utils.ts @@ -60,6 +60,7 @@ function upgradeFromStoreMap( const indexDesc: IndexDescriptor = swi.indexMap[indexName]; s.createIndex(indexDesc.name, indexDesc.keyPath, { multiEntry: indexDesc.multiEntry, + unique: indexDesc.unique, }); } } diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 493f1a1d0..8b96dd5be 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -527,6 +527,8 @@ export interface ExchangeRecord { /** * Public key of the reserve that we're currently using for * receiving P2P payments. + * + * FIXME: Make this a rowId of reserves! */ currentMergeReserveInfo?: MergeReserveInfo; } @@ -569,6 +571,8 @@ export interface PlanchetRecord { * * Can be the empty string (non-null/undefined for DB indexing) * if this is a tipping reserve. + * + * FIXME: Where is this used? */ reservePub: string; @@ -763,6 +767,8 @@ export interface ProposalDownload { /** * Extracted / parsed data from the contract terms. + * + * FIXME: Do we need to store *all* that data in duplicate? */ contractData: WalletContractData; } @@ -1762,9 +1768,14 @@ export interface PeerPullPaymentIncomingRecord { contractPriv: string; } -// FIXME: give this some smaller "row ID" to -// reference in other records? +/** + * Store for extra information about a reserve. + * + * Mostly used to store the private key for a reserve and to allow + * other records to reference the reserve key pair via a small row ID. + */ export interface ReserveRecord { + rowId?: number; reservePub: string; reservePriv: string; } @@ -1844,9 +1855,12 @@ export const WalletStoresV1 = { reserves: describeStore( "reserves", describeContents({ - keyPath: "reservePub", + keyPath: "rowId", + autoIncrement: true, }), - {}, + { + byReservePub: describeIndex("byReservePub", "reservePub", {}), + }, ), config: describeStore( "config", @@ -1956,7 +1970,6 @@ export const WalletStoresV1 = { keyPath: "withdrawalGroupId", }), { - byReservePub: describeIndex("byReservePub", "reservePub"), byStatus: describeIndex("byStatus", "status"), byTalerWithdrawUri: describeIndex( "byTalerWithdrawUri", diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 6d899b947..2d92ff8ba 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -356,7 +356,9 @@ export async function processRecoupGroupHandler( throw Error(`Coin ${coinPub} not found, can't request recoup`); } if (coin.coinSource.type === CoinSourceType.Withdraw) { - const reserve = await tx.reserves.get(coin.coinSource.reservePub); + const reserve = await tx.reserves.indexes.byReservePub.get( + coin.coinSource.reservePub, + ); if (!reserve) { return; } diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index 3d4ff79cb..71d7b9783 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -61,6 +61,13 @@ export interface IndexOptions { * undefined if added in the first version. */ versionAdded?: number; + + /** + * Does this index enforce unique keys? + * + * Defaults to false. + */ + unique?: boolean; } function requestToPromise(req: IDBRequest): Promise { @@ -276,6 +283,7 @@ export interface IndexDescriptor { name: string; keyPath: IDBKeyPath | IDBKeyPath[]; multiEntry?: boolean; + unique?: boolean; } export interface StoreDescriptor { @@ -292,7 +300,11 @@ export interface StoreOptions { export function describeContents( options: StoreOptions, ): StoreDescriptor { - return { keyPath: options.keyPath, _dummy: undefined as any }; + return { + keyPath: options.keyPath, + _dummy: undefined as any, + autoIncrement: options.autoIncrement, + }; } export function describeIndex( @@ -304,6 +316,7 @@ export function describeIndex( keyPath, name, multiEntry: options.multiEntry, + unique: options.unique, }; } @@ -339,11 +352,18 @@ export interface StoreReadOnlyAccessor { indexes: GetIndexReadOnlyAccess; } +export interface InsertResponse { + /** + * Key of the newly inserted (via put/add) record. + */ + key: IDBValidKey; +} + export interface StoreReadWriteAccessor { get(key: IDBValidKey): Promise; iter(query?: IDBValidKey): ResultStream; - put(r: RecordType): Promise; - add(r: RecordType): Promise; + put(r: RecordType): Promise; + add(r: RecordType): Promise; delete(key: IDBValidKey): Promise; indexes: GetIndexReadWriteAccess; } @@ -577,13 +597,19 @@ function makeWriteContext( const req = tx.objectStore(storeName).openCursor(query); return new ResultStream(req); }, - add(r) { + async add(r) { const req = tx.objectStore(storeName).add(r); - return requestToPromise(req); + const key = await requestToPromise(req); + return { + key: key, + }; }, - put(r) { + async put(r) { const req = tx.objectStore(storeName).put(r); - return requestToPromise(req); + const key = await requestToPromise(req); + return { + key: key, + }; }, delete(k) { const req = tx.objectStore(storeName).delete(k);