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.jsexport() 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 getNextSequenceValueBEGIN…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-zhuangand 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

vercelBot commented Jun 2, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJun 2, 2026 1:58am

Request Review

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 mainJun 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

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

@xuyushun441-sys@os-zhuang