Skip to content

fix(sqlite): synchronize transaction depth when SQLite auto-rolls back - #4473

Open
webdevsamran wants to merge 1 commit into
transact-rs:mainfrom
webdevsamran:fix/sqlite-full-stale-transaction-depth
Open

webdevsamran wants to merge 1 commit into
transact-rs:mainfrom
webdevsamran:fix/sqlite-full-stale-transaction-depth

Conversation

@webdevsamran

Copy link
Copy Markdown

Does your PR solve an issue?

Fixes #4434

Is this a breaking change?

No. This fixes stale transaction state when SQLite automatically rolls back a transaction on fatal errors (such as SQLITE_FULL, SQLITE_IOERR, or SQLITE_CORRUPT).

Description

When SQLite encounters errors that cause an automatic rollback (such as SQLITE_FULL or SQLITE_CORRUPT), SQLite turns autocommit mode back ON. When the application or transaction drop guard subsequently calls rollback/commit, SQLx attempted to execute a rollback query against SQLite which failed with cannot rollback - no transaction is active. Because the query failed, shared.transaction_depth was not decremented and remained permanently stale (depth >= 1). Any subsequent attempt to begin a new transaction (especially with custom statements like begin_with("BEGIN IMMEDIATE")) immediately failed with InvalidSavePointStatement.

This change:

  1. In Command::Rollback: if depth > 0 and !conn.handle.in_transaction(), resets shared.transaction_depth to 0 and treats rollback as successful.
  2. In Command::Commit: if commit fails and !conn.handle.in_transaction(), resets shared.transaction_depth to 0.
  3. In Command::Begin: synchronizes transaction_depth to 0 if SQLite is not currently in a transaction.
  4. Adds a regression test in tests/sqlite/sqlite.rs reproducing the automatic rollback via PRAGMA max_page_count.

When SQLite encounters errors such as SQLITE_FULL, SQLITE_IOERR, or SQLITE_CORRUPT during a statement execution, it automatically rolls back the active transaction and resets autocommit mode to active (1).

Previously, when the application or Transaction drop guard subsequently invoked rollback or commit, SQLx attempted to execute a ROLLBACK / RELEASE statement. Because no SQLite transaction was active, the statement failed, leaving shared.transaction_depth stuck at its previous value (>= 1). Subsequent attempts to start transactions via begin_with failed with InvalidSavePointStatement.

This change updates the SQLite connection worker:
- In Command::Rollback, if depth > 0 but !conn.handle.in_transaction(), reset shared.transaction_depth to 0 and treat the rollback as Ok(()).
- In Command::Commit, if res.is_err() and !conn.handle.in_transaction(), reset shared.transaction_depth to 0.
- In Command::Begin, synchronize depth to 0 if !conn.handle.in_transaction().
- Adds regression test reproducing SQLITE_FULL via page limit.

Fixes transact-rs#4434

Signed-off-by: Samran Asif <samranwebdev2000@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQLite: SQLITE_FULL leaves stale transaction depth and breaks subsequent begin_with calls

1 participant