arohou
### I have found these related issues/pull requests - https://github.com/transact-rs/sqlx/issues/3932 — SQLite transaction-creation cancellation/error handling. This reproduction successfully starts a transaction with begin(), then encounters SQLITE_FULL during a statement. It reproduces on 0.9.0, which includes the transaction-creation fixes. - https://github.com/transact-rs/sqlx/issues/4031 — stale depth after manually executing ROLLBACK. This reproduction uses SQLx transaction APIs and does not manually execute transaction-control SQL. - https://github.com/transact-rs/sqlx/issues/3742 — related transaction-state synchronization concern for PostgreSQL. This report concerns SQLite and includes a standalone reproduction. I searched the issue tracker for SQLITE_FULL, the transaction-depth error string, and SQLite rollback reports; I did not find an existing report covering this reproduction. ### Description After SQLITE_FULL occurs inside a transaction, dropping that transaction can leave SQLx's SQLite transaction depth stale. Removing the capacity constraint restores ordinary SQL execution, but a subsequent begin_with("BEGIN IMMEDIATE") fails with InvalidSavePointStatement (displayed as "attempted to call begin_with at non-zero transaction depth"). Expected: after dropping the failed transaction and removing the capacity constraint, starting a new transaction on the connection should succeed, or SQLx should clearly mark the connection unusable. Actual: the same insert succeeds after the constraint is removed, but starting a new transaction fails. Suspected mechanism: SQLite can automatically roll back a transaction after SQLITE_FULL. SQLx then queues another ROLLBACK when the transaction is dropped. That rollback fails because no transaction remains active, but Command::Rollback only decrements transaction_depth on success. The connection retains stale depth, and the next custom begin statement is rejected before execution. SQLite documents automatic rollback and the API for checking the underlying transaction state: https://www.sqlite.org/c3ref/get_autocommit.html The relevant rollback logic is also present at upstream commit b54008a329541c0ce230d8b203e0c00fc8e8ea48: https://github.com/transact-rs/sqlx/blob/b54008a329541c0ce230d8b203e0c00fc8e8ea48/sqlx-sqlite/src/connection/worker.rs#L285-L315 The reproduction below was built and executed against released 0.9.0. The upstream revision was inspected, not executed. ### Reproduction steps Create a binary project with the following two files. PRAGMA max_page_count forces SQLITE_FULL in memory, without filling the host disk. The program then raises the limit, successfully repeats the same insert, and tries to start another transaction. Cargo.toml: ```toml [package] name = "sqlx-sqlite-full-repro" version = "0.1.0" edition = "2021" [dependencies] sqlx = { version = "=0.9.0", default-features = false, features = ["sqlite-bundled"] } futures-executor = "=0.3.32" ``` src/main.rs: ```rust use sqlx::{Connection, SqliteConnection}; fn main() -> Result<(), sqlx::Error> { futures_executor::block_on(async { let mut conn = SqliteConnection::connect("sqlite::memory:").await?; sqlx::query("CREATE TABLE t (data BLOB)").execute(&mut conn).await?; sqlx::query("PRAGMA max_page_count = 2").execute(&mut conn).await?; let mut tx = conn.begin().await?; let error = sqlx::query("INSERT INTO t VALUES (zeroblob(65536))") .execute(&mut *tx).await.unwrap_err(); assert_eq!(error.as_database_error().and_then(|e| e.code()).as_deref(), Some("13")); println!("insert: {error}"); drop(tx); // Remove the capacity constraint; the identical insert now succeeds. sqlx::query("PRAGMA max_page_count = 1000").execute(&mut conn).await?; sqlx::query("INSERT INTO t VALUES (zeroblob(65536))") .execute(&mut conn).await?; println!("insert after lifting limit: OK"); // Expected to succeed; instead returns InvalidSavePointStatement. conn.begin_with("BEGIN IMMEDIATE").await?.rollback().await?; Ok(()) }) } ``` Build with `cargo build`, then execute `./target/debug/sqlx-sqlite-full-repro` separately. Observed stdout: ```text insert: error returned from database: (code: 13) database or disk is full insert after lifting limit: OK ``` Observed stderr: ```text Error: InvalidSavePointStatement ``` The program exits with status 1 at the final begin_with call. Expected: that call and the following rollback succeed, and the program exits with status 0. The initial assertion checks that the triggering database error is SQLITE_FULL (code 13). ### SQLx version 0.9.0 (sqlx, sqlx-core, and sqlx-sqlite) ### Enabled SQLx features sqlite-bundled; default-features = false ### Database server and version SQLite 3.51.3 (bundled); sqlx-sqlite 0.9.0; libsqlite3-sys 0.37.0; in-memory database ### Operating system macOS 14.8.9, Apple Silicon (aarch64-apple-darwin) ### Rust version rustc 1.96.1 (31fca3adb 2026-06-26)