From: Justin Wind Date: Thu, 15 May 2025 16:59:47 +0000 (-0700) Subject: fix sqlite transaction wrapper X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=f05907a145692c89591ddd3fc147639129e7e9c4;p=squeep-db-helper fix sqlite transaction wrapper --- diff --git a/lib/sqlite-creator.js b/lib/sqlite-creator.js index 32fbd76..06b51f7 100644 --- a/lib/sqlite-creator.js +++ b/lib/sqlite-creator.js @@ -165,7 +165,7 @@ COMMIT;`); // eslint-disable-next-line security/detect-non-literal-fs-filename const stat = fs.statSync(fPath); if (!stat.isFile() - || fExt.toLowerCase() !== '.sql') { + || fExt.toLowerCase() !== '.sql') { continue; } // eslint-disable-next-line security/detect-non-literal-fs-filename @@ -187,6 +187,11 @@ COMMIT;`); } this.statement._optimize = this.db.prepare('SELECT * FROM pragma_optimize(0xffff)'); + // this.db.transaction fails when fn is async, so we need to wrangle our own + this.statement._beginImmediate = this.db.prepare('BEGIN IMMEDIATE'); + this.statement._commit = this.db.prepare('COMMIT'); + this.statement._rollback = this.db.prepare('ROLLBACK'); + this.logger.debug(_scope, 'statements initialized', { statements: Object.keys(this.statement).length }); } @@ -267,7 +272,17 @@ COMMIT;`); async transaction(dbCtx, fn) { dbCtx = dbCtx || this.db; - return dbCtx.transaction(fn).immediate(); + + try { + this.statement._beginImmediate.run(); + const result = await fn(dbCtx); + this.statement._commit.run(); + return result; + } finally { + if (this.db.inTransaction) { + this.statement._rollback.run(); + } + } } diff --git a/test/integration.js b/test/integration.js index 25847dc..2101646 100644 --- a/test/integration.js +++ b/test/integration.js @@ -146,6 +146,24 @@ describe('Database Integration', function () { }); }); + step('covers transaction rollback', async function () { + const expected = events; + const event = 'event3'; + const date = new Date('May 14 2025 12:02 PDT'); + await db.context(async (dbCtx) => { + try { + await db.transaction(dbCtx, async (txCtx) => { + await db.almanacUpsert(dbCtx, event, date); + throw new Error('we interrupt this transaction'); + }); + } catch (e) { + // expected + } + const allEvents = await db.almanacGetAll(dbCtx); + assert.deepStrictEqual(allEvents, expected); + }); + }); + }); // Almanac }); // specific implementation diff --git a/test/lib/sqlite-creator.js b/test/lib/sqlite-creator.js index 1c84aaa..6d2830f 100644 --- a/test/lib/sqlite-creator.js +++ b/test/lib/sqlite-creator.js @@ -259,11 +259,31 @@ describe('DatabaseSQLite', function () { }); // context describe('transaction', function () { - it('covers', function () { - db.transaction(db.db, nop); - }); - it('covers no context', function () { - db.transaction(undefined, nop); + let fn; + beforeEach(function () { + fn = sinon.stub().resolves(); + sinon.spy(db.statement._beginImmediate, 'run'); + sinon.spy(db.statement._commit, 'run'); + sinon.spy(db.statement._rollback, 'run'); + }); + it('covers', async function () { + await db.transaction(db.db, fn); + assert(db.statement._beginImmediate.run.called); + assert(fn.called); + assert(db.statement._commit.run.called); + }); + it('covers no context', async function () { + await db.transaction(undefined, fn); + assert(db.statement._beginImmediate.run.called); + assert(fn.called); + assert(db.statement._commit.run.called); + }); + it('covers rollback', async function () { + fn.rejects(); + await assert.rejects(async () => db.transaction(db.db, fn)); + assert(db.statement._beginImmediate.run.called); + assert(fn.called); + assert(db.statement._rollback.run.called); }); }); // transaction