use vibesql_ast::{Statement, TriggerAction};
use vibesql_storage::Database;
use crate::errors::ExecutorError;
fn exec(db: &mut Database, sql: &str) -> Result<String, ExecutorError> {
let stmt = vibesql_parser::Parser::parse_sql(sql).expect("parse");
match stmt {
Statement::CreateTable(s) => crate::CreateTableExecutor::execute(&s, db),
Statement::CreateTrigger(s) => {
crate::TriggerExecutor::create_trigger_with_sql(db, &s, Some(sql))
}
Statement::AlterTable(s) => crate::alter::AlterTableExecutor::execute(&s, db),
other => panic!("unexpected statement: {:?}", other),
}
}
fn trigger_sql(db: &Database, name: &str) -> String {
db.catalog
.get_trigger(name)
.expect("trigger exists")
.sql_definition
.clone()
.expect("sql_definition preserved")
}
fn trigger_body(db: &Database, name: &str) -> String {
match &db.catalog.get_trigger(name).expect("trigger exists").triggered_action {
TriggerAction::RawSql(sql) => sql.clone(),
}
}
fn setup(db: &mut Database) {
exec(db, "CREATE TABLE t1(a,b)").unwrap();
exec(db, "CREATE TABLE t2(c,d)").unwrap();
exec(db, "CREATE TABLE t3(e,f)").unwrap();
exec(db, "CREATE TABLE t4(e,f)").unwrap();
}
#[test]
fn rename_column_rewrites_unqualified_in_nested_subquery() {
let mut db = Database::new();
setup(&mut db);
exec(
&mut db,
"CREATE TRIGGER r1 INSERT ON t1 BEGIN \
UPDATE t1 SET a='xyz' FROM t3, (SELECT * FROM (SELECT e FROM t3)); END",
)
.unwrap();
exec(&mut db, "ALTER TABLE t3 RENAME e TO abc").unwrap();
let sql = trigger_sql(&db, "r1");
assert!(sql.contains("SELECT abc FROM t3"), "got: {sql}");
assert!(sql.contains("FROM t3,"), "got: {sql}");
}
#[test]
fn rename_column_rewrites_unqualified_in_where() {
let mut db = Database::new();
setup(&mut db);
exec(
&mut db,
"CREATE TRIGGER r1 INSERT ON t1 BEGIN \
UPDATE t1 SET a='xyz' FROM t3, (SELECT 1 FROM t2 WHERE c); END",
)
.unwrap();
exec(&mut db, "ALTER TABLE t2 RENAME c TO abc").unwrap();
assert!(trigger_sql(&db, "r1").contains("WHERE abc)"), "{}", trigger_sql(&db, "r1"));
}
#[test]
fn rename_column_rewrites_qualified_reference() {
let mut db = Database::new();
setup(&mut db);
exec(&mut db, "CREATE TRIGGER r1 INSERT ON t1 BEGIN UPDATE t1 SET a=t2.c FROM t2; END")
.unwrap();
exec(&mut db, "ALTER TABLE t2 RENAME c TO abc").unwrap();
let sql = trigger_sql(&db, "r1");
assert!(sql.contains("a=t2.abc"), "got: {sql}");
assert!(sql.contains("FROM t2;"), "qualifier table preserved, got: {sql}");
let body: String = trigger_body(&db, "r1").split_whitespace().collect::<Vec<_>>().join("");
assert!(body.contains("a=t2.abc"), "body: {}", trigger_body(&db, "r1"));
}
#[test]
fn rename_column_rewrites_qualified_with_multiple_from() {
let mut db = Database::new();
setup(&mut db);
exec(&mut db, "CREATE TRIGGER r1 INSERT ON t1 BEGIN UPDATE t1 SET a=t2.c FROM t2, t3; END")
.unwrap();
exec(&mut db, "ALTER TABLE t2 RENAME c TO abc").unwrap();
assert!(trigger_sql(&db, "r1").contains("a=t2.abc"), "{}", trigger_sql(&db, "r1"));
}
#[test]
fn rename_column_rewrites_qualified_in_natural_join() {
let mut db = Database::new();
setup(&mut db);
exec(
&mut db,
"CREATE TRIGGER r1 INSERT ON t1 BEGIN \
UPDATE t1 SET a=1 FROM t3 NATURAL JOIN t4 WHERE t4.e=a; END",
)
.unwrap();
exec(&mut db, "ALTER TABLE t4 RENAME e TO abc").unwrap();
assert!(trigger_sql(&db, "r1").contains("t4.abc=a"), "{}", trigger_sql(&db, "r1"));
}
#[test]
fn rename_column_updates_table_schema() {
let mut db = Database::new();
setup(&mut db);
exec(&mut db, "ALTER TABLE t2 RENAME COLUMN c TO abc").unwrap();
let t2 = db.get_table("t2").expect("t2 exists");
let cols: Vec<String> = t2.schema.columns.iter().map(|c| c.name.clone()).collect();
assert!(t2.schema.has_column("abc"), "columns: {cols:?}");
assert!(!t2.schema.has_column("c"), "columns: {cols:?}");
}
#[test]
fn rename_column_missing_column_errors() {
let mut db = Database::new();
setup(&mut db);
let err = exec(&mut db, "ALTER TABLE t2 RENAME nosuch TO x").unwrap_err();
assert!(matches!(err, ExecutorError::ColumnNotFound { .. }), "got: {err:?}");
}
#[test]
fn rename_column_onto_existing_errors() {
let mut db = Database::new();
setup(&mut db);
let err = exec(&mut db, "ALTER TABLE t2 RENAME c TO d").unwrap_err();
assert!(matches!(err, ExecutorError::ColumnAlreadyExists(_)), "got: {err:?}");
}
#[test]
fn rename_column_ambiguous_in_trigger_aborts_and_leaves_schema_unchanged() {
let mut db = Database::new();
setup(&mut db);
exec(
&mut db,
"CREATE TRIGGER r1 INSERT ON t3 BEGIN \
UPDATE t3 SET e=1 FROM t4 WHERE e=2; END",
)
.unwrap();
let body_before = trigger_body(&db, "r1");
let sql_before = trigger_sql(&db, "r1");
let err = exec(&mut db, "ALTER TABLE t3 RENAME e TO abc").unwrap_err();
match &err {
ExecutorError::Other(msg) => {
assert_eq!(msg, "error in trigger r1: ambiguous column name: e", "got: {msg}");
}
other => panic!("expected ambiguity Other error, got: {other:?}"),
}
let t3 = db.get_table("t3").expect("t3 exists");
assert!(t3.schema.has_column("e"), "t3.e should be unchanged after aborted ALTER");
assert!(!t3.schema.has_column("abc"), "t3.abc must not exist after aborted ALTER");
assert_eq!(trigger_body(&db, "r1"), body_before, "trigger body must be unchanged");
assert_eq!(trigger_sql(&db, "r1"), sql_before, "trigger sql must be unchanged");
}
#[test]
fn rename_column_unambiguous_still_succeeds_with_other_table_sharing_name() {
let mut db = Database::new();
setup(&mut db);
exec(
&mut db,
"CREATE TRIGGER r1 INSERT ON t3 BEGIN \
UPDATE t3 SET a=1 FROM t4 WHERE t3.e=2; END",
)
.unwrap();
exec(&mut db, "ALTER TABLE t3 RENAME e TO abc").unwrap();
let t3 = db.get_table("t3").expect("t3 exists");
assert!(t3.schema.has_column("abc"));
assert!(trigger_sql(&db, "r1").contains("t3.abc=2"), "{}", trigger_sql(&db, "r1"));
}