mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-23 09:54:26 +01:00
Merge 'Fix deferred FK violations check before committing to WAL' from Jussi Saurio
DEFERRED was a bit too deferred - it allowed the dirty pages to be written out to WAL before checking for violations, resulting in the violations effectively being committed even though the transaction ended up aborting Closes #3784 Closes #3785
This commit is contained in:
@@ -2165,21 +2165,21 @@ pub fn halt(
|
||||
let auto_commit = program.connection.auto_commit.load(Ordering::SeqCst);
|
||||
tracing::trace!("halt(auto_commit={})", auto_commit);
|
||||
if auto_commit {
|
||||
let res = program.commit_txn(pager.clone(), state, mv_store, false);
|
||||
if res.is_ok()
|
||||
&& program.connection.foreign_keys_enabled()
|
||||
// In autocommit mode, a statement that leaves deferred violations must fail here.
|
||||
if program.connection.foreign_keys_enabled()
|
||||
&& program
|
||||
.connection
|
||||
.fk_deferred_violations
|
||||
.swap(0, Ordering::AcqRel)
|
||||
> 0
|
||||
{
|
||||
// In autocommit mode, a statement that leaves deferred violations must fail here.
|
||||
return Err(LimboError::Constraint(
|
||||
"foreign key constraint failed".to_string(),
|
||||
));
|
||||
}
|
||||
res.map(Into::into)
|
||||
program
|
||||
.commit_txn(pager.clone(), state, mv_store, false)
|
||||
.map(Into::into)
|
||||
} else {
|
||||
Ok(InsnFunctionStepResult::Done)
|
||||
}
|
||||
|
||||
@@ -208,6 +208,37 @@ fn test_constraint_error_aborts_transaction() {
|
||||
assert_eq!(row, vec![Value::Integer(0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Regression test for https://github.com/tursodatabase/turso/issues/3784 where dirty pages
|
||||
/// were flushed to WAL _before_ deferred FK violations were checked. This resulted in the
|
||||
/// violations being persisted to the database, even though the transaction was aborted.
|
||||
/// This test ensures that dirty pages are not flushed to WAL until after deferred violations are checked.
|
||||
fn test_deferred_fk_violation_rollback_in_autocommit() {
|
||||
let tmp_db = TempDatabase::new("test_deferred_fk_violation_rollback.db", true);
|
||||
let conn = tmp_db.connect_limbo();
|
||||
|
||||
// Enable foreign keys
|
||||
conn.execute("PRAGMA foreign_keys = ON").unwrap();
|
||||
|
||||
// Create parent and child tables with deferred FK constraint
|
||||
conn.execute("CREATE TABLE parent(a PRIMARY KEY)").unwrap();
|
||||
conn.execute("CREATE TABLE child(a, b, FOREIGN KEY(b) REFERENCES parent(a) DEFERRABLE INITIALLY DEFERRED)")
|
||||
.unwrap();
|
||||
|
||||
// This insert should fail because parent(1) doesn't exist
|
||||
// and the deferred FK violation should be caught at statement end in autocommit mode
|
||||
let result = conn.execute("INSERT INTO child VALUES(1,1)");
|
||||
assert!(matches!(result, Err(LimboError::Constraint(_))));
|
||||
|
||||
// Do a truncating checkpoint
|
||||
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)").unwrap();
|
||||
|
||||
// Verify that the child table is empty (the insert was rolled back)
|
||||
let stmt = conn.query("SELECT COUNT(*) FROM child").unwrap().unwrap();
|
||||
let row = helper_read_single_row(stmt);
|
||||
assert_eq!(row, vec![Value::Integer(0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mvcc_transactions_autocommit() {
|
||||
let tmp_db = TempDatabase::new_with_opts(
|
||||
|
||||
Reference in New Issue
Block a user