Skip to content

fix(driver-sqlite-wasm): defer flush during transactions (#1494)#1501

Merged
xuyushun441-sys merged 4 commits into
mainfrom
fix/sqlite-wasm-tx-export-1494
Jun 2, 2026
Merged

fix(driver-sqlite-wasm): defer flush during transactions (#1494)#1501
xuyushun441-sys merged 4 commits into
mainfrom
fix/sqlite-wasm-tx-export-1494

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Summary

Fixes #1494 — under the sqlite-wasm driver, an authenticated insert could fail with COMMIT; - cannot commit - no transaction is active.

Root cause

It's a sql.js export() colliding with an open transaction, not a sharing-rule/hook bug.

  1. objectstack start with a file-backed DB uses persist: 'on-write' (standalone-stack.ts:182).
  2. In 'on-write' mode every write fires a fire-and-forget void this.flush().
  3. flush() calls db.export(). In sql.js 1.14.1 export() is implemented as finalize-statements → sqlite3_close → reopen — it closes and reopens the database (no in-place serialize).
  4. Closing a connection with an open transaction rolls it back. When that flush lands while a Knex transaction is mid-flight (e.g. the autonumber getNextSequenceValue BEGIN…COMMIT), the transaction is silently aborted and the trailing COMMIT runs with no active transaction.

The failure is timing-dependent, which is why objects with extra in-transaction writes (autonumber + sharing-rule recompute, like crm_account) tripped it reliably while a bare crm_lead insert usually committed before the flush fired. The reporter's sharing-rule hypothesis was a correlated symptom, not the cause.

Verified directly: db.export() inside BEGIN…INSERT makes the following COMMIT throw the exact error, reproduced end-to-end through the driver.

Fix

Make the connection transaction-aware so export() never runs mid-transaction:

  • wasm-connection.ts: track BEGIN/COMMIT/ROLLBACK/SAVEPOINT/RELEASE (noteTransactionControl). flush() now defers while a transaction is open and runs once it fully closes. close() clears the state so the final flush still persists committed data.
  • knex-wasm-dialect.ts: the dialect notifies the connection of every transaction-control statement, before markDirty (so a COMMIT has already cleared the flag when the deferred flush fires).

Test plan

  • New regression test sqlite-wasm-driver-transaction-persist.test.ts — multi-statement transactions, on-write disk persistence, autonumber inserts, nested savepoints. Fails without the fix.
  • Full driver suite: 116/116 pass. Build + DTS clean.

🤖 Generated with Claude Code

os-zhuang and others added 2 commits June 2, 2026 08:52
feat(studio): AI-draft review/diff mode in the object designer (v1) (#1456)

objectui@fdd083657e2da9832059492d4c88e818a5990a8d
Under `persist: 'on-write'` an authenticated insert could fail with
`COMMIT; - cannot commit - no transaction is active`.

Root cause: sql.js `Database.export()` closes and reopens the database
(it has no in-place serialize). Closing a connection rolls back any open
transaction, so the fire-and-forget `void flush()` triggered after a
write inside a Knex transaction (e.g. the autonumber sequence
`BEGIN…COMMIT`) aborted that transaction, leaving the trailing COMMIT to
run with no active transaction. The failure is timing-dependent, which is
why objects with extra in-transaction writes (autonumber + sharing-rule
recompute, like crm_account) tripped it while a bare insert usually did
not.

Fix: make the connection transaction-aware. Track
BEGIN/COMMIT/ROLLBACK/SAVEPOINT/RELEASE; `flush()` now defers while a
transaction is open and runs once it fully closes. `close()` clears the
state so the final flush still persists committed data. The dialect
notifies the connection of every transaction-control statement before
markDirty.

Adds a regression test covering multi-statement transactions, on-write
disk persistence, autonumber inserts, and nested savepoints.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 2, 2026 1:58am

Request Review

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Jun 2, 2026
Follow-up to the #1494 fix. Two issues surfaced in CI / under load:

- A COMMIT triggered two flushes (one from noteTransactionControl, one
  from the dialect's trailing markDirty('run')). Overlapping sql.js
  export() close/reopens raced and could persist 0 bytes. Route
  transaction-control statements through noteTransactionControl only, so
  the transaction lifecycle owns flushing and fires exactly once on close.
- Replace the pendingFlush + recursive re-flush mechanism with a single
  strictly-serialized flush chain. export() mutates the live connection
  (close/reopen), so two exports must never overlap, and flush() must not
  resolve until the caller's own write has hit disk (deterministic for
  close()/tests). Key the post-commit flush off flushDeferred (not dirty)
  so on-disconnect mode still defers to close() instead of flushing on
  every COMMIT.

Full driver suite green across repeated full-suite runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xuyushun441-sys xuyushun441-sys merged commit be20aa4 into main Jun 2, 2026
12 checks passed
@xuyushun441-sys xuyushun441-sys deleted the fix/sqlite-wasm-tx-export-1494 branch June 2, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sqlite-wasm: authenticated record insert fails with "COMMIT; - cannot commit - no transaction is active"

2 participants