use vibesql_ast::{
CreateTriggerStmt, TriggerAction, TriggerEvent, TriggerGranularity, TriggerTiming,
};
use vibesql_storage::Database;
use super::create_users_table;
use crate::{InsertExecutor, SelectExecutor};
#[test]
fn test_trigger_failure_causes_rollback() {
let mut db = Database::new();
create_users_table(&mut db);
let trigger_stmt = CreateTriggerStmt {
if_not_exists: false,
schema: None,
trigger_name: "failing_trigger".to_string(),
name_source: None,
timing: TriggerTiming::After,
event: TriggerEvent::Insert,
table_name: "users".to_string(),
granularity: TriggerGranularity::Row,
when_condition: None,
triggered_action: TriggerAction::RawSql(
"INSERT INTO nonexistent_table (col) VALUES (1)".to_string(),
),
};
crate::advanced_objects::execute_create_trigger(&trigger_stmt, &mut db)
.expect("Failed to create trigger");
let insert = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "users".to_string(),
columns: vec!["id".to_string(), "username".to_string()],
source: vibesql_ast::InsertSource::Values(vec![vec![
vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Integer(1)),
vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Varchar(
arcstr::ArcStr::from("alice"),
)),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
let result = InsertExecutor::execute(&mut db, &insert);
assert!(result.is_err(), "Insert should have failed due to trigger error");
let select = vibesql_ast::SelectStmt {
with_clause: None,
distinct: false,
select_list: vec![vibesql_ast::SelectItem::Wildcard { alias: None }],
into_table: None,
into_variables: None,
from: Some(vibesql_ast::FromClause::Table {
index_hint: None,
quoted: false,
name: "USERS".to_string(),
alias: None,
column_aliases: None,
}),
where_clause: None,
group_by: None,
having: None,
window_definitions: None,
order_by: None,
limit: None,
offset: None,
set_operation: None,
values: None,
};
let executor = SelectExecutor::new(&db);
let rows = executor.execute(&select).expect("Failed to select");
assert_eq!(rows.len(), 0, "Table should be empty after failed trigger");
}
#[test]
fn test_recursion_prevention() {
std::thread::Builder::new()
.name("test_recursion_prevention".to_string())
.stack_size(96 * 1024 * 1024)
.spawn(|| {
let mut db = Database::new();
create_users_table(&mut db);
let trigger_stmt = CreateTriggerStmt {
if_not_exists: false,
schema: None,
trigger_name: "recursive_trigger".to_string(),
name_source: None,
timing: TriggerTiming::After,
event: TriggerEvent::Insert,
table_name: "users".to_string(),
granularity: TriggerGranularity::Row,
when_condition: None,
triggered_action: TriggerAction::RawSql(
"INSERT INTO users (id, username) VALUES (999, 'recursive')".to_string(),
),
};
crate::advanced_objects::execute_create_trigger(&trigger_stmt, &mut db)
.expect("Failed to create trigger");
let insert = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "users".to_string(),
columns: vec!["id".to_string(), "username".to_string()],
source: vibesql_ast::InsertSource::Values(vec![vec![
vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Integer(1)),
vibesql_ast::Expression::Literal(vibesql_types::SqlValue::Varchar(
arcstr::ArcStr::from("alice"),
)),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
let result = InsertExecutor::execute(&mut db, &insert);
assert!(result.is_err(), "Insert should have failed due to recursion limit");
let err = result.unwrap_err();
assert_eq!(err.to_string(), "too many levels of trigger recursion");
})
.expect("spawn recursion-test thread")
.join()
.expect("recursion cap must error, not overflow the stack");
}