From 904cbe535d366c69f4a5001ef35e1dc393d4fea0 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Tue, 21 Oct 2025 12:12:02 +0300 Subject: [PATCH] VDBE: handle subtransaction commits/aborts in op_halt --- core/vdbe/execute.rs | 52 ++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 1ba9e14ca..442e6857d 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -1,5 +1,5 @@ #![allow(unused_variables)] -use crate::error::{SQLITE_CONSTRAINT_FOREIGNKEY, SQLITE_CONSTRAINT_UNIQUE}; +use crate::error::SQLITE_CONSTRAINT_UNIQUE; use crate::function::AlterTableFunc; use crate::mvcc::database::CheckpointStateMachine; use crate::numeric::{NullableInteger, Numeric}; @@ -21,7 +21,7 @@ use crate::util::{ normalize_ident, rewrite_column_references_if_needed, rewrite_fk_parent_cols_if_self_ref, }; use crate::vdbe::insn::InsertFlags; -use crate::vdbe::{registers_to_ref_values, TxnCleanup}; +use crate::vdbe::{registers_to_ref_values, EndStatement, TxnCleanup}; use crate::vector::{vector32_sparse, vector_concat, vector_distance_jaccard, vector_slice}; use crate::{ error::{ @@ -2149,8 +2149,8 @@ pub fn halt( description: &str, ) -> Result { if err_code > 0 { - // invalidate page cache in case of error - pager.clear_page_cache(false); + // Any non-FK constraint violation causes the statement subtransaction to roll back. + state.end_statement(&program.connection, &pager, EndStatement::RollbackSavepoint)?; } match err_code { 0 => {} @@ -2169,9 +2169,6 @@ pub fn halt( "UNIQUE constraint failed: {description} (19)" ))); } - SQLITE_CONSTRAINT_FOREIGNKEY => { - return Err(LimboError::Constraint(format!("{description} (19)"))); - } _ => { return Err(LimboError::Constraint(format!( "undocumented halt error code {description}" @@ -2181,23 +2178,46 @@ pub fn halt( let auto_commit = program.connection.auto_commit.load(Ordering::SeqCst); tracing::trace!("halt(auto_commit={})", auto_commit); + + // Check for immediate foreign key violations. + // Any immediate violation causes the statement subtransaction to roll back. + if program.connection.foreign_keys_enabled() + && state + .fk_immediate_violations_during_stmt + .load(Ordering::Acquire) + > 0 + { + state.end_statement(&program.connection, &pager, EndStatement::RollbackSavepoint)?; + return Err(LimboError::Constraint( + "foreign key constraint failed".to_string(), + )); + } + if auto_commit { - // In autocommit mode, a statement that leaves deferred violations must fail here. - if program.connection.foreign_keys_enabled() - && program + // In autocommit mode, a statement that leaves deferred violations must fail here, + // and it also ends the transaction. + if program.connection.foreign_keys_enabled() { + let deferred_violations = program .connection .fk_deferred_violations - .swap(0, Ordering::AcqRel) - > 0 - { - return Err(LimboError::Constraint( - "foreign key constraint failed".to_string(), - )); + .swap(0, Ordering::AcqRel); + if deferred_violations > 0 { + pager.rollback_tx(&program.connection); + program.connection.set_tx_state(TransactionState::None); + program.connection.auto_commit.store(true, Ordering::SeqCst); + return Err(LimboError::Constraint( + "foreign key constraint failed".to_string(), + )); + } } + state.end_statement(&program.connection, &pager, EndStatement::ReleaseSavepoint)?; program .commit_txn(pager.clone(), state, mv_store, false) .map(Into::into) } else { + // Even if deferred violations are present, the statement subtransaction completes successfully when + // it is part of an interactive transaction. + state.end_statement(&program.connection, &pager, EndStatement::ReleaseSavepoint)?; Ok(InsnFunctionStepResult::Done) } }