From 54ff656c9d9fb612a06944b11cbd55df331e2f62 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Mon, 25 Aug 2025 08:49:22 +0300 Subject: [PATCH] Do not clear txn state inside nested statement If a connection does e.g. CREATE TABLE, it will start a "child statement" to reparse the schema. That statement does not start its own transaction, and so should not try to end the existing one either. We had a logic bug where these steps would happen: - `CREATE TABLE` executed successfully - pread fault happens inside `ParseSchema` child stmt - `handle_program_error()` is called - `pager.end_tx()` returns immediately because `is_nested_stmt` is true and we correctly no-op it. - however, crucially: `handle_program_error()` then sets tx state to None - parent statement now catches error from nested stmt and calls `handle_program_error()`, which calls `pager.end_tx()` again, and since txn state is None, when it calls `rollback()` we panic on the assertion `"dirty pages should be empty for read txn"` Solution: Do not do _any_ error processing in `handle_program_error()` inside a nested stmt. This means that the parent write txn is still active when it processes the error from the child and we avoid this panic. --- core/vdbe/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 5a9cbe646..a12e5772e 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -857,6 +857,10 @@ pub fn handle_program_error( connection: &Connection, err: &LimboError, ) -> Result<()> { + if connection.is_nested_stmt.get() { + // Errors from nested statements are handled by the parent statement. + return Ok(()); + } match err { // Transaction errors, e.g. trying to start a nested transaction, do not cause a rollback. LimboError::TxError(_) => {}