fix sqlite transaction wrapper
authorJustin Wind <justin.wind+git@gmail.com>
Thu, 15 May 2025 16:59:47 +0000 (09:59 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Thu, 15 May 2025 16:59:47 +0000 (09:59 -0700)
lib/sqlite-creator.js
test/integration.js
test/lib/sqlite-creator.js

index 32fbd762f36c47fda15a4cad8497419024e8d689..06b51f7ac37b704572904ea14030a66a1d24a41c 100644 (file)
@@ -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();
+        }
+      }
     }
 
 
index 25847dcba61b70fb93f70d159161709660781fda..2101646fb95031f7f1a683427e6f419c3177433f 100644 (file)
@@ -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
index 1c84aaa19274e01072c594cf9b3c831d295f9f02..6d2830f0dc1e939cbe4b9eedb87a0a5cf49f0b27 100644 (file)
@@ -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