use vibesql_ast::Statement;
use vibesql_parser::Parser;
use vibesql_types::SqlValue;
use super::super::*;
fn exec_ok(db: &mut vibesql_storage::Database, sql: &str) {
let stmt =
Parser::parse_sql(sql).unwrap_or_else(|e| panic!("Failed to parse {:?}: {:?}", sql, e));
match stmt {
Statement::CreateTable(s) => {
CreateTableExecutor::execute(&s, db).expect("CREATE TABLE failed");
}
Statement::CreateTrigger(s) => {
crate::advanced_objects::execute_create_trigger(&s, db).expect("CREATE TRIGGER failed");
}
Statement::CreateView(s) => {
crate::advanced_objects::execute_create_view(&s, db).expect("CREATE VIEW failed");
}
Statement::Insert(s) => {
InsertExecutor::execute(db, &s).expect("INSERT failed");
}
Statement::Update(s) => {
UpdateExecutor::execute(&s, db).expect("UPDATE failed");
}
Statement::Delete(s) => {
DeleteExecutor::execute(&s, db).expect("DELETE failed");
}
other => panic!("Unsupported setup statement: {:?}", other),
}
}
fn column_values(db: &vibesql_storage::Database, table: &str, col: &str) -> Vec<SqlValue> {
let schema = db.catalog.get_table(table).expect("table exists");
let idx = schema.columns.iter().position(|c| c.name == col).expect("column exists");
db.get_table(table)
.expect("table exists")
.scan()
.iter()
.map(|row| row.values[idx].clone())
.collect()
}
fn strings(db: &vibesql_storage::Database, table: &str, col: &str) -> Vec<String> {
column_values(db, table, col)
.into_iter()
.map(|v| match v {
SqlValue::Varchar(s) | SqlValue::Character(s) => s.to_string(),
other => panic!("expected text, got {:?}", other),
})
.collect()
}
fn ints(db: &vibesql_storage::Database, table: &str, col: &str) -> Vec<i64> {
column_values(db, table, col)
.into_iter()
.map(|v| match v {
SqlValue::Integer(i) => i,
SqlValue::Bigint(i) => i,
other => panic!("expected integer, got {:?}", other),
})
.collect()
}
#[test]
fn case_end_then_insert_fires_both_statements() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE t (id INTEGER, v INTEGER)");
exec_ok(&mut db, "CREATE TABLE log (msg VARCHAR(16))");
exec_ok(
&mut db,
"CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
UPDATE t SET v = CASE WHEN NEW.v > 0 THEN 1 ELSE 0 END; \
INSERT INTO log VALUES ('x'); \
END",
);
exec_ok(&mut db, "INSERT INTO t (id, v) VALUES (1, 5)");
assert_eq!(strings(&db, "log", "msg"), vec!["x".to_string()]);
assert_eq!(ints(&db, "t", "v"), vec![1]);
}
#[test]
fn multiple_case_expressions_across_statements() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE t (id INTEGER, v INTEGER)");
exec_ok(&mut db, "CREATE TABLE log (msg VARCHAR(16))");
exec_ok(
&mut db,
"CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
UPDATE t SET v = CASE WHEN NEW.v > 0 THEN 1 ELSE 0 END; \
INSERT INTO log VALUES (CASE WHEN NEW.v > 0 THEN 'pos' ELSE 'neg' END); \
END",
);
exec_ok(&mut db, "INSERT INTO t (id, v) VALUES (1, 5)");
assert_eq!(ints(&db, "t", "v"), vec![1]);
assert_eq!(strings(&db, "log", "msg"), vec!["pos".to_string()]);
}
#[test]
fn nested_case_end_does_not_truncate_body() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE t (id INTEGER, v INTEGER)");
exec_ok(&mut db, "CREATE TABLE log (msg VARCHAR(16))");
exec_ok(
&mut db,
"CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
UPDATE t SET v = CASE WHEN NEW.v > 0 \
THEN CASE WHEN NEW.v > 10 THEN 99 ELSE 5 END \
ELSE 0 END; \
INSERT INTO log VALUES ('done'); \
END",
);
exec_ok(&mut db, "INSERT INTO t (id, v) VALUES (1, 20)");
assert_eq!(ints(&db, "t", "v"), vec![99]);
assert_eq!(strings(&db, "log", "msg"), vec!["done".to_string()]);
}
#[test]
fn case_in_where_clause_does_not_truncate_body() {
let mut db = vibesql_storage::Database::new();
db.set_recursive_triggers(false);
exec_ok(&mut db, "CREATE TABLE t (id INTEGER, v INTEGER)");
exec_ok(&mut db, "CREATE TABLE log (msg VARCHAR(16))");
exec_ok(&mut db, "INSERT INTO t (id, v) VALUES (1, 0)");
exec_ok(
&mut db,
"CREATE TRIGGER tr AFTER UPDATE ON t BEGIN \
UPDATE t SET v = 7 WHERE id = CASE WHEN NEW.id > 0 THEN NEW.id ELSE -1 END; \
INSERT INTO log VALUES ('w'); \
END",
);
exec_ok(&mut db, "UPDATE t SET v = 1 WHERE id = 1");
assert_eq!(strings(&db, "log", "msg"), vec!["w".to_string()]);
}
#[test]
fn instead_of_insert_case_raise_ignore_body_fires_and_resolves_new() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE base (id INTEGER, v INTEGER)");
exec_ok(&mut db, "CREATE VIEW vw AS SELECT id, v FROM base");
let sql = "CREATE TRIGGER trg INSTEAD OF INSERT ON vw BEGIN \
SELECT CASE WHEN NEW.id = 1 THEN raise(IGNORE) END; \
INSERT INTO base (id, v) VALUES (NEW.id, NEW.v); \
END";
let stmt = Parser::parse_sql(sql)
.unwrap_or_else(|e| panic!("CREATE TRIGGER with CASE...END must parse (#5439): {:?}", e));
match stmt {
Statement::CreateTrigger(s) => {
let vibesql_ast::TriggerAction::RawSql(body) = &s.triggered_action;
let up = body.to_uppercase();
assert!(up.contains("CASE"), "body lost the CASE expression: {}", body);
assert!(
up.contains("INSERT INTO BASE"),
"body truncated before the trailing INSERT (the #5439 bug): {}",
body
);
crate::advanced_objects::execute_create_trigger(&s, &mut db)
.expect("CREATE TRIGGER should succeed");
}
other => panic!("expected CreateTrigger, got {:?}", other),
}
exec_ok(&mut db, "INSERT INTO vw (id, v) VALUES (1, 100)");
exec_ok(&mut db, "INSERT INTO vw (id, v) VALUES (2, 200)");
assert_eq!(ints(&db, "base", "id"), vec![2]);
assert_eq!(ints(&db, "base", "v"), vec![200]);
}
#[test]
fn from_less_select_new_resolves_in_trigger_body() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE t (id INTEGER, v INTEGER)");
exec_ok(&mut db, "CREATE TABLE log (val INTEGER)");
exec_ok(
&mut db,
"CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
SELECT NEW.id; \
INSERT INTO log VALUES (NEW.v + 1); \
END",
);
exec_ok(&mut db, "INSERT INTO t (id, v) VALUES (5, 99)");
assert_eq!(ints(&db, "log", "val"), vec![100]);
}
#[test]
fn from_less_select_old_raise_ignore_in_delete_trigger() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE d (id INTEGER, v INTEGER)");
exec_ok(&mut db, "CREATE TABLE dlog (val INTEGER)");
exec_ok(&mut db, "INSERT INTO d (id, v) VALUES (1, 10)");
exec_ok(&mut db, "INSERT INTO d (id, v) VALUES (2, 20)");
exec_ok(
&mut db,
"CREATE TRIGGER trd AFTER DELETE ON d BEGIN \
SELECT CASE WHEN OLD.id = 1 THEN raise(IGNORE) END; \
INSERT INTO dlog VALUES (OLD.v); \
END",
);
exec_ok(&mut db, "DELETE FROM d");
assert_eq!(ints(&db, "dlog", "val"), vec![20]);
assert!(column_values(&db, "d", "id").is_empty(), "all rows should be deleted");
}
#[test]
fn from_less_select_expression_over_new_and_old() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE t (id INTEGER, v INTEGER)");
exec_ok(&mut db, "CREATE TABLE log (val INTEGER)");
exec_ok(&mut db, "INSERT INTO t (id, v) VALUES (1, 10)");
exec_ok(
&mut db,
"CREATE TRIGGER tr AFTER UPDATE ON t BEGIN \
SELECT CASE WHEN NEW.v - OLD.v > 5 THEN raise(IGNORE) END; \
INSERT INTO log VALUES (NEW.v); \
END",
);
exec_ok(&mut db, "UPDATE t SET v = 12 WHERE id = 1"); exec_ok(&mut db, "UPDATE t SET v = 100 WHERE id = 1");
assert_eq!(ints(&db, "log", "val"), vec![12]);
}
#[test]
fn insert_select_from_less_resolves_old_in_trigger_body() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE item (a INTEGER, b INTEGER, c INTEGER)");
exec_ok(&mut db, "CREATE TABLE undo (msg VARCHAR(64))");
exec_ok(&mut db, "INSERT INTO item (a, b, c) VALUES (1, 2, 3)");
exec_ok(
&mut db,
"CREATE TRIGGER tr AFTER DELETE ON item FOR EACH ROW BEGIN \
INSERT INTO undo SELECT 'a=' || OLD.a || ',b=' || OLD.b || ',c=' || OLD.c; \
END",
);
exec_ok(&mut db, "DELETE FROM item WHERE a = 1");
assert_eq!(strings(&db, "undo", "msg"), vec!["a=1,b=2,c=3".to_string()]);
assert!(column_values(&db, "item", "a").is_empty(), "row should be deleted");
}
#[test]
fn insert_select_from_less_resolves_new_in_trigger_body() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE t (c INTEGER)");
exec_ok(&mut db, "CREATE TABLE log (val INTEGER)");
exec_ok(
&mut db,
"CREATE TRIGGER tr AFTER INSERT ON t FOR EACH ROW BEGIN \
INSERT INTO log SELECT NEW.c * 100; \
END",
);
exec_ok(&mut db, "INSERT INTO t (c) VALUES (2)");
exec_ok(&mut db, "INSERT INTO t (c) VALUES (7)");
let mut logged = ints(&db, "log", "val");
logged.sort_unstable();
assert_eq!(logged, vec![200, 700]);
}
#[test]
fn instead_of_insert_case_then_base_write_fires_both() {
let mut db = vibesql_storage::Database::new();
exec_ok(&mut db, "CREATE TABLE base (id INTEGER, label VARCHAR(8))");
exec_ok(&mut db, "CREATE TABLE log (msg VARCHAR(8))");
exec_ok(&mut db, "CREATE VIEW vw AS SELECT id, label FROM base");
exec_ok(
&mut db,
"CREATE TRIGGER trg INSTEAD OF INSERT ON vw BEGIN \
INSERT INTO base (id, label) VALUES (NEW.id, CASE WHEN NEW.id > 0 THEN 'pos' ELSE 'neg' END); \
INSERT INTO log VALUES ('fired'); \
END",
);
exec_ok(&mut db, "INSERT INTO vw (id, label) VALUES (10, 'ignored')");
assert_eq!(ints(&db, "base", "id"), vec![10]);
assert_eq!(strings(&db, "base", "label"), vec!["pos".to_string()]);
assert_eq!(strings(&db, "log", "msg"), vec!["fired".to_string()]);
}