use vibesql_executor::{
CreateTableExecutor, InsertExecutor, SelectExecutor, TriggerExecutor, UpdateExecutor,
};
use vibesql_parser::Parser;
use vibesql_storage::Database;
use vibesql_types::SqlValue;
fn exec(db: &mut Database, sql: &str) {
use vibesql_ast::Statement;
let stmt = Parser::parse_sql(sql).unwrap_or_else(|e| panic!("parse failed for `{sql}`: {e:?}"));
match stmt {
Statement::CreateTable(s) => {
CreateTableExecutor::execute(&s, db).expect("CREATE TABLE failed");
}
Statement::CreateTrigger(s) => {
TriggerExecutor::create_trigger_with_sql(db, &s, Some(sql))
.expect("CREATE TRIGGER failed");
}
Statement::Insert(s) => {
InsertExecutor::execute(db, &s).expect("INSERT failed");
}
Statement::Update(s) => {
UpdateExecutor::execute(&s, db).expect("UPDATE failed");
}
other => panic!("unsupported statement in test helper: {other:?}"),
}
}
fn query_scalar_i64(db: &Database, sql: &str) -> i64 {
use vibesql_ast::Statement;
let stmt = Parser::parse_sql(sql).expect("parse failed");
let Statement::Select(select) = stmt else { panic!("expected SELECT") };
let rows = SelectExecutor::new(db).execute(&select).expect("SELECT failed");
match &rows[0].values[0] {
SqlValue::Integer(n) => *n,
other => panic!("expected integer, got {other:?}"),
}
}
#[test]
fn when_subquery_fires_per_subquery_result_trigger2_3_2() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE tbl (a, b, c, d);");
exec(&mut db, "CREATE TABLE log (a);");
exec(&mut db, "INSERT INTO log VALUES (0);");
exec(
&mut db,
"CREATE TRIGGER t1 BEFORE INSERT ON tbl WHEN new.a > 20 \
BEGIN UPDATE log SET a = a + 1; END;",
);
exec(
&mut db,
"CREATE TRIGGER t2 BEFORE INSERT ON tbl WHEN (SELECT count(*) FROM tbl) = 0 \
BEGIN UPDATE log SET a = a + 1; END;",
);
exec(&mut db, "INSERT INTO tbl VALUES(0, 0, 0, 0);");
assert_eq!(query_scalar_i64(&db, "SELECT a FROM log;"), 1, "empty-table insert: t2 should fire");
exec(&mut db, "UPDATE log SET a = 0;");
exec(&mut db, "INSERT INTO tbl VALUES(0, 0, 0, 0);");
assert_eq!(query_scalar_i64(&db, "SELECT a FROM log;"), 0, "non-empty insert: neither fires");
exec(&mut db, "UPDATE log SET a = 0;");
exec(&mut db, "INSERT INTO tbl VALUES(200, 0, 0, 0);");
assert_eq!(query_scalar_i64(&db, "SELECT a FROM log;"), 1, "a>20 insert: t1 should fire");
}
#[test]
fn when_correlated_subquery_referencing_new_fires_per_condition_5585() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t (a);");
exec(&mut db, "CREATE TABLE seen (a);");
exec(&mut db, "CREATE TABLE log (n);");
exec(&mut db, "INSERT INTO log VALUES (0);");
exec(
&mut db,
"CREATE TRIGGER tr AFTER INSERT ON t \
WHEN (SELECT count(*) FROM seen WHERE a = NEW.a) = 0 \
BEGIN UPDATE log SET n = n + 1; INSERT INTO seen VALUES(NEW.a); END;",
);
exec(&mut db, "INSERT INTO t VALUES(5);");
assert_eq!(query_scalar_i64(&db, "SELECT n FROM log;"), 1, "first 5: trigger should fire");
exec(&mut db, "INSERT INTO t VALUES(5);");
assert_eq!(query_scalar_i64(&db, "SELECT n FROM log;"), 1, "second 5: trigger should not fire");
exec(&mut db, "INSERT INTO t VALUES(7);");
assert_eq!(query_scalar_i64(&db, "SELECT n FROM log;"), 2, "7: trigger should fire");
assert_eq!(query_scalar_i64(&db, "SELECT count(*) FROM seen;"), 2, "seen should hold {{5,7}}");
assert_eq!(
query_scalar_i64(&db, "SELECT count(*) FROM seen WHERE a = 5;"),
1,
"seen should contain exactly one a=5"
);
assert_eq!(
query_scalar_i64(&db, "SELECT count(*) FROM seen WHERE a = 7;"),
1,
"seen should contain exactly one a=7"
);
}
#[test]
fn when_correlated_scalar_subquery_referencing_new_5585() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t (id, x);");
exec(&mut db, "CREATE TABLE other (id, val);");
exec(&mut db, "CREATE TABLE hit (id);");
exec(&mut db, "INSERT INTO other VALUES(1, 10);");
exec(&mut db, "INSERT INTO other VALUES(2, 0);");
exec(&mut db, "INSERT INTO other VALUES(3, 5);");
exec(
&mut db,
"CREATE TRIGGER tr AFTER INSERT ON t \
WHEN (SELECT val FROM other WHERE other.id = NEW.id) > 0 \
BEGIN INSERT INTO hit VALUES(NEW.id); END;",
);
exec(&mut db, "INSERT INTO t VALUES(1, 'a');");
exec(&mut db, "INSERT INTO t VALUES(2, 'b');");
exec(&mut db, "INSERT INTO t VALUES(3, 'c');");
assert_eq!(query_scalar_i64(&db, "SELECT count(*) FROM hit;"), 2, "hit should be {{1,3}}");
assert_eq!(
query_scalar_i64(&db, "SELECT count(*) FROM hit WHERE id = 1;"),
1,
"id 1 (val 10 > 0) should fire"
);
assert_eq!(
query_scalar_i64(&db, "SELECT count(*) FROM hit WHERE id = 2;"),
0,
"id 2 (val 0, not > 0) should not fire"
);
assert_eq!(
query_scalar_i64(&db, "SELECT count(*) FROM hit WHERE id = 3;"),
1,
"id 3 (val 5 > 0) should fire"
);
}
#[test]
fn when_correlated_exists_subquery_referencing_new_5585() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t (id);");
exec(&mut db, "CREATE TABLE other (id);");
exec(&mut db, "CREATE TABLE hit (id);");
exec(&mut db, "INSERT INTO other VALUES(2);");
exec(&mut db, "INSERT INTO other VALUES(4);");
exec(
&mut db,
"CREATE TRIGGER tr AFTER INSERT ON t \
WHEN EXISTS (SELECT 1 FROM other WHERE other.id = NEW.id) \
BEGIN INSERT INTO hit VALUES(NEW.id); END;",
);
exec(&mut db, "INSERT INTO t VALUES(1);");
exec(&mut db, "INSERT INTO t VALUES(2);");
exec(&mut db, "INSERT INTO t VALUES(4);");
assert_eq!(query_scalar_i64(&db, "SELECT count(*) FROM hit;"), 2, "hit should be {{2,4}}");
assert_eq!(query_scalar_i64(&db, "SELECT count(*) FROM hit WHERE id = 1;"), 0, "id 1 not in other");
assert_eq!(query_scalar_i64(&db, "SELECT count(*) FROM hit WHERE id = 2;"), 1, "id 2 in other");
assert_eq!(query_scalar_i64(&db, "SELECT count(*) FROM hit WHERE id = 4;"), 1, "id 4 in other");
}
#[test]
fn non_trigger_correlated_scalar_subquery_unaffected_5585() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE outer_t (id, label);");
exec(&mut db, "CREATE TABLE inner_t (id, val);");
exec(&mut db, "INSERT INTO outer_t VALUES(1, 'a');");
exec(&mut db, "INSERT INTO outer_t VALUES(2, 'b');");
exec(&mut db, "INSERT INTO inner_t VALUES(1, 100);");
exec(&mut db, "INSERT INTO inner_t VALUES(2, 200);");
assert_eq!(
query_scalar_i64(
&db,
"SELECT (SELECT val FROM inner_t WHERE inner_t.id = outer_t.id) \
FROM outer_t WHERE id = 2;",
),
200,
"ordinary correlated scalar subquery must resolve to inner_t.val for id=2",
);
}