use vibesql_ast::Statement;
use vibesql_storage::Database;
use vibesql_types::SqlValue;
fn exec(db: &mut Database, sql: &str) {
let stmt = vibesql_parser::Parser::parse_sql(sql).expect("parse");
match stmt {
Statement::CreateTable(s) => {
crate::CreateTableExecutor::execute(&s, db).unwrap();
}
Statement::CreateTrigger(s) => {
crate::TriggerExecutor::create_trigger(db, &s).unwrap();
}
Statement::Insert(s) => {
crate::InsertExecutor::execute(db, &s).unwrap();
}
Statement::Update(s) => {
crate::UpdateExecutor::execute(&s, db).unwrap();
}
Statement::Delete(s) => {
crate::delete::DeleteExecutor::execute(&s, db).unwrap();
}
other => panic!("unsupported statement: {other:?}"),
}
}
fn log_values(db: &Database) -> Vec<String> {
let stmt = vibesql_parser::Parser::parse_sql("SELECT t FROM log").expect("parse select");
let select = match stmt {
Statement::Select(s) => s,
other => panic!("expected SELECT, got {other:?}"),
};
let result =
crate::SelectExecutor::new(db).execute_with_columns(&select).expect("run select");
result
.rows
.iter()
.map(|r| match &r.values[0] {
SqlValue::Varchar(s) => s.to_string(),
other => format!("{other:?}"),
})
.collect()
}
fn setup_table(db: &mut Database) {
exec(db, "CREATE TABLE t1(a INT PRIMARY KEY, b INT)");
exec(db, "CREATE TABLE log(t)");
exec(db, "INSERT INTO t1 VALUES(1,10),(2,20),(3,30)");
}
#[test]
fn after_update_trigger_reads_applied_value_single_row() {
let mut db = Database::new();
setup_table(&mut db);
exec(
&mut db,
"CREATE TRIGGER tr_after AFTER UPDATE ON t1 BEGIN \
INSERT INTO log VALUES(old.a || ':' || new.b \
|| ':sum=' || (SELECT sum(b) FROM t1) \
|| ':max=' || (SELECT max(b) FROM t1) \
|| ':cnt=' || (SELECT count(*) FROM t1)); END",
);
exec(&mut db, "UPDATE t1 SET b=100 WHERE a=1");
assert_eq!(
log_values(&db),
vec!["1:100:sum=150:max=100:cnt=3".to_string()],
"AFTER UPDATE must see row 1's applied value (b=100)"
);
}
#[test]
fn after_update_trigger_reads_applied_value_multi_row() {
let mut db = Database::new();
setup_table(&mut db);
exec(
&mut db,
"CREATE TRIGGER tr_after AFTER UPDATE ON t1 BEGIN \
INSERT INTO log VALUES(old.a || ':' || new.b \
|| ':sum=' || (SELECT sum(b) FROM t1) \
|| ':max=' || (SELECT max(b) FROM t1)); END",
);
exec(&mut db, "UPDATE t1 SET b=b+1");
assert_eq!(
log_values(&db),
vec![
"1:11:sum=61:max=30".to_string(),
"2:21:sum=62:max=30".to_string(),
"3:31:sum=63:max=31".to_string(),
],
"each AFTER(R) must observe the running applied aggregates"
);
}
#[test]
fn before_update_trigger_sees_prior_rows_applied_multi_row() {
let mut db = Database::new();
setup_table(&mut db);
exec(
&mut db,
"CREATE TRIGGER tr_before BEFORE UPDATE ON t1 BEGIN \
INSERT INTO log VALUES('B:' || old.a \
|| ':sum=' || (SELECT sum(b) FROM t1) \
|| ':max=' || (SELECT max(b) FROM t1)); END",
);
exec(&mut db, "UPDATE t1 SET b=b+100");
assert_eq!(
log_values(&db),
vec![
"B:1:sum=60:max=30".to_string(),
"B:2:sum=160:max=110".to_string(),
"B:3:sum=260:max=120".to_string(),
],
"BEFORE(R) must observe rows 1..R-1 already applied"
);
}
#[test]
fn combined_before_after_update_trigger_interleaved_sum() {
let mut db = Database::new();
setup_table(&mut db);
exec(
&mut db,
"CREATE TRIGGER tr_before BEFORE UPDATE ON t1 BEGIN \
INSERT INTO log VALUES('B:' || old.a || ':sum=' || (SELECT sum(b) FROM t1)); END",
);
exec(
&mut db,
"CREATE TRIGGER tr_after AFTER UPDATE ON t1 BEGIN \
INSERT INTO log VALUES('A:' || old.a || ':sum=' || (SELECT sum(b) FROM t1)); END",
);
exec(&mut db, "UPDATE t1 SET b=b+100");
assert_eq!(
log_values(&db),
vec![
"B:1:sum=60".to_string(),
"A:1:sum=160".to_string(),
"B:2:sum=160".to_string(),
"A:2:sum=260".to_string(),
"B:3:sum=260".to_string(),
"A:3:sum=360".to_string(),
],
"interleaved BEFORE/AFTER must each observe the running applied sum"
);
}
#[test]
fn no_trigger_update_applies_all_rows() {
let mut db = Database::new();
setup_table(&mut db);
exec(&mut db, "UPDATE t1 SET b=b+100");
let stmt = vibesql_parser::Parser::parse_sql("SELECT sum(b), max(b), count(*) FROM t1")
.expect("parse");
let select = match stmt {
Statement::Select(s) => s,
other => panic!("expected SELECT, got {other:?}"),
};
let result =
crate::SelectExecutor::new(&db).execute_with_columns(&select).expect("run select");
assert_eq!(result.rows.len(), 1);
assert_eq!(result.rows[0].values[0], SqlValue::Integer(360));
assert_eq!(result.rows[0].values[1], SqlValue::Integer(130));
assert_eq!(result.rows[0].values[2], SqlValue::Integer(3));
}