taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit ebadef8ba5cb0a08055ae3cf6226e6a78a9c43c5
parent 33f6978be678e7a8e7509ed34dccff9b20db4706
Author: Florian Dold <florian@dold.me>
Date:   Fri, 10 Jan 2025 12:05:11 +0100

idb-bridge: only reload schema on abort when in versionchange transaction

Diffstat:
Mpackages/idb-bridge/src/SqliteBackend.ts | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mpackages/idb-bridge/src/bridge-idb.ts | 8++++++--
2 files changed, 89 insertions(+), 34 deletions(-)

diff --git a/packages/idb-bridge/src/SqliteBackend.ts b/packages/idb-bridge/src/SqliteBackend.ts @@ -731,7 +731,9 @@ export class SqliteBackend implements Backend { throw Error( `object store ${JSON.stringify( req.objectStoreName, - )} not in transaction scope`, + )} not in transaction scope, only have ${JSON.stringify( + Object.keys(connInfo.storeMap), + )}`, ); } @@ -951,7 +953,6 @@ export class SqliteBackend implements Backend { dbConn: DatabaseConnection, objectStoreName: string, ): ObjectStoreMeta | undefined { - // FIXME: Use cached info from the connection for this! const connInfo = this.connectionMap.get(dbConn.connectionCookie); if (!connInfo) { throw Error("connection not found"); @@ -1034,7 +1035,9 @@ export class SqliteBackend implements Backend { async _runSqlGetDatabaseVersion( databaseName: string, ): Promise<number | undefined> { - const versionRes = await (await this._prep(sqlGetDatabaseVersion)).getFirst({ + const versionRes = await ( + await this._prep(sqlGetDatabaseVersion) + ).getFirst({ name: databaseName, }); if (versionRes == undefined) { @@ -1106,7 +1109,9 @@ export class SqliteBackend implements Backend { connInfo: ConnectionInfo, storeName: string, ): Promise<void> { - const objRes = await (await this._prep(sqlGetObjectStoreMetaByName)).getFirst({ + const objRes = await ( + await this._prep(sqlGetObjectStoreMetaByName) + ).getFirst({ name: storeName, database_name: connInfo.databaseName, }); @@ -1118,7 +1123,9 @@ export class SqliteBackend implements Backend { const objectStoreKeyPath = deserializeKeyPath( expectDbStringOrNull(objRes, "key_path"), ); - const indexRes = await (await this._prep(sqlGetIndexesByObjectStoreId)).getAll({ + const indexRes = await ( + await this._prep(sqlGetIndexesByObjectStoreId) + ).getAll({ object_store_id: objectStoreId, }); if (!indexRes) { @@ -1272,7 +1279,9 @@ export class SqliteBackend implements Backend { const objectStoreNames = await this._loadObjectStoreNames(databaseName); for (const storeName of objectStoreNames) { - const objRes = await (await this._prep(sqlGetObjectStoreMetaByName)).getFirst({ + const objRes = await ( + await this._prep(sqlGetObjectStoreMetaByName) + ).getFirst({ name: storeName, database_name: databaseName, }); @@ -1280,7 +1289,9 @@ export class SqliteBackend implements Backend { throw Error("object store not found"); } const objectStoreId = expectDbNumber(objRes, "id"); - const indexRes = await (await this._prep(sqlGetIndexesByObjectStoreId)).getAll({ + const indexRes = await ( + await this._prep(sqlGetIndexesByObjectStoreId) + ).getAll({ object_store_id: objectStoreId, }); if (!indexRes) { @@ -1319,18 +1330,26 @@ export class SqliteBackend implements Backend { await stmt.run({ index_id: indexInfo.indexId, }); - await (await this._prep(sqlIndexDelete)).run({ + await ( + await this._prep(sqlIndexDelete) + ).run({ index_id: indexInfo.indexId, }); } - await (await this._prep(sqlObjectDataDeleteAll)).run({ + await ( + await this._prep(sqlObjectDataDeleteAll) + ).run({ object_store_id: objectStoreId, }); - await (await this._prep(sqlObjectStoreDelete)).run({ + await ( + await this._prep(sqlObjectStoreDelete) + ).run({ object_store_id: objectStoreId, }); } - await (await this._prep(sqlDeleteDatabase)).run({ + await ( + await this._prep(sqlDeleteDatabase) + ).run({ name: databaseName, }); await (await this._prep(sqlCommit)).run(); @@ -1425,14 +1444,20 @@ export class SqliteBackend implements Backend { await stmt.run({ index_id: indexInfo.indexId, }); - await (await this._prep(sqlIndexDelete)).run({ + await ( + await this._prep(sqlIndexDelete) + ).run({ index_id: indexInfo.indexId, }); } - await (await this._prep(sqlObjectDataDeleteAll)).run({ + await ( + await this._prep(sqlObjectDataDeleteAll) + ).run({ object_store_id: scopeInfo.objectStoreId, }); - await (await this._prep(sqlObjectStoreDelete)).run({ + await ( + await this._prep(sqlObjectStoreDelete) + ).run({ object_store_id: scopeInfo.objectStoreId, }); } @@ -1469,7 +1494,9 @@ export class SqliteBackend implements Backend { await stmt.run({ index_id: indexMeta.indexId, }); - await (await this._prep(sqlIndexDelete)).run({ + await ( + await this._prep(sqlIndexDelete) + ).run({ index_id: indexMeta.indexId, }); } @@ -1516,13 +1543,17 @@ export class SqliteBackend implements Backend { return; } await (await this._prep(sqlRollback)).run(); - connInfo.storeList = []; - connInfo.storeMap.clear(); - const objectStoreNames: string[] = await this._loadObjectStoreNames( - connInfo.databaseName, - ); - for (const storeName of objectStoreNames) { - await this._loadScopeInfo(connInfo, storeName); + if (this.txLevel === TransactionLevel.VersionChange) { + // Rollback also undoes schema changes, but that is only + // relevant in a versionchange transaction. + connInfo.storeList = []; + connInfo.storeMap.clear(); + const objectStoreNames: string[] = await this._loadObjectStoreNames( + connInfo.databaseName, + ); + for (const storeName of objectStoreNames) { + await this._loadScopeInfo(connInfo, storeName); + } } this.txLevel = TransactionLevel.None; this.transactionMap.delete(btx.transactionCookie); @@ -1554,7 +1585,9 @@ export class SqliteBackend implements Backend { await this._provideObjectStore(connInfo, store); } if (store.nameDirty) { - await (await this._prep(sqlRenameObjectStore)).run({ + await ( + await this._prep(sqlRenameObjectStore) + ).run({ object_store_id: store.objectStoreId, name: store.currentName, }); @@ -1568,7 +1601,9 @@ export class SqliteBackend implements Backend { await this._provideIndex(connInfo, store, indexMeta); } if (indexMeta.nameDirty) { - await (await this._prep(sqlRenameIndex)).run({ + await ( + await this._prep(sqlRenameIndex) + ).run({ index_id: indexMeta.indexId, name: indexMeta.currentName, }); @@ -1594,7 +1629,9 @@ export class SqliteBackend implements Backend { throw Error("invalid state"); } - const runRes = await (await this._prep(sqlCreateObjectStore)).run({ + const runRes = await ( + await this._prep(sqlCreateObjectStore) + ).run({ name: storeMeta.currentName, key_path: serializeKeyPath(storeMeta.keyPath), auto_increment: storeMeta.autoIncrement ? 1 : 0, @@ -1661,7 +1698,9 @@ export class SqliteBackend implements Backend { if (storeMeta.objectStoreId == null) { throw Error("invariant failed"); } - const res = await (await this._prep(sqlCreateIndex)).run({ + const res = await ( + await this._prep(sqlCreateIndex) + ).run({ object_store_id: storeMeta.objectStoreId, name: indexMeta.currentName, key_path: serializeKeyPath(indexMeta.keyPath), @@ -1672,7 +1711,9 @@ export class SqliteBackend implements Backend { // FIXME: We can't use an iterator here, as it's not allowed to // execute a write statement while the iterator executes. // Maybe do multiple selects instead of loading everything into memory? - const keyRowsRes = await (await this._prep(sqlObjectDataGetAll)).getAll({ + const keyRowsRes = await ( + await this._prep(sqlObjectDataGetAll) + ).getAll({ object_store_id: storeMeta.objectStoreId, }); @@ -1812,7 +1853,9 @@ export class SqliteBackend implements Backend { // Now delete! - await (await this._prep(sqlObjectDataDeleteKey)).run({ + await ( + await this._prep(sqlObjectDataDeleteKey) + ).run({ object_store_id: scopeInfo.objectStoreId, key: currKey, }); @@ -1864,7 +1907,9 @@ export class SqliteBackend implements Backend { ); } const objectStoreId = await this._provideObjectStore(connInfo, scopeInfo); - const metaRes = await (await this._prep(sqlGetObjectStoreMetaById)).getFirst({ + const metaRes = await ( + await this._prep(sqlGetObjectStoreMetaById) + ).getFirst({ id: objectStoreId satisfies SqliteRowid, }); if (metaRes === undefined) { @@ -1928,14 +1973,18 @@ export class SqliteBackend implements Backend { } } - await (await this._prep(sqlInsertObjectData)).run({ + await ( + await this._prep(sqlInsertObjectData) + ).run({ object_store_id: objectStoreId, key: serializedObjectKey, value: JSON.stringify(structuredEncapsulate(value)), }); if (autoIncrement != 0) { - await (await this._prep(sqlUpdateAutoIncrement)).run({ + await ( + await this._prep(sqlUpdateAutoIncrement) + ).run({ object_store_id: objectStoreId, auto_increment: updatedKeyGenerator, }); @@ -2053,7 +2102,9 @@ export class SqliteBackend implements Backend { ); } - await (await this._prep(sqlClearObjectStore)).run({ + await ( + await this._prep(sqlClearObjectStore) + ).run({ object_store_id: scopeInfo.objectStoreId, }); diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts @@ -912,7 +912,9 @@ export class BridgeIDBFactory { const requestedVersion = version; if (BridgeIDBFactory.enableTracing) { - console.log(`existing version of DB ${name} is ${existingVersion}, requesting ${requestedVersion}`); + console.log( + `existing version of DB ${name} is ${existingVersion}, requesting ${requestedVersion}`, + ); } BridgeIDBFactory.enableTracing && @@ -2681,7 +2683,9 @@ export class BridgeIDBTransaction ); if (!objectStoreMeta) { - throw new NotFoundError(); + throw new NotFoundError( + `object store metadata for ${name} found in current transaction (bug!)`, + ); } const newObjectStore = new BridgeIDBObjectStore(