use vibesql_ast::Statement;
use vibesql_storage::Database;
fn exec_sql(db: &mut Database, sql: &str) -> Result<String, String> {
let stmt =
vibesql_parser::Parser::parse_sql(sql).map_err(|e| format!("Parse error: {:?}", e))?;
match stmt {
Statement::CreateTable(s) => {
crate::CreateTableExecutor::execute(&s, db).map_err(|e| e.to_string())
}
Statement::Insert(s) => crate::InsertExecutor::execute(db, &s)
.map(|count| format!("{} row(s) inserted", count))
.map_err(|e| e.to_string()),
Statement::CreateIndex(s) => crate::CreateIndexExecutor::execute(&s, db)
.map(|_| "ok".to_string())
.map_err(|e| e.to_string()),
_ => Err(format!("Unsupported statement type: {:?}", sql)),
}
}
#[test]
fn fk_mismatch_when_parent_column_not_unique() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
exec_sql(&mut db, "CREATE TABLE tt(y)").unwrap();
exec_sql(&mut db, "CREATE TABLE c11(x REFERENCES tt(y))").unwrap();
let err = exec_sql(&mut db, "INSERT INTO c11 VALUES(1)").unwrap_err();
assert!(
err.contains("foreign key mismatch") && err.contains("\"c11\"") && err.contains("\"tt\""),
"expected mismatch wording, got: {}",
err
);
}
#[test]
fn fk_mismatch_when_only_partial_unique_index_exists() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
exec_sql(&mut db, "CREATE TABLE p1(x, y)").unwrap();
exec_sql(&mut db, "INSERT INTO p1 VALUES(1, 1)").unwrap();
exec_sql(&mut db, "CREATE TABLE c1(a REFERENCES p1(x))").unwrap();
exec_sql(&mut db, "CREATE UNIQUE INDEX p1x ON p1(x) WHERE y<2").unwrap();
let err = exec_sql(&mut db, "INSERT INTO c1 VALUES(1)").unwrap_err();
assert!(
err.contains("foreign key mismatch") && err.contains("\"c1\"") && err.contains("\"p1\""),
"expected mismatch wording, got: {}",
err
);
}
#[test]
fn fk_succeeds_when_full_unique_index_added_after_partial() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
exec_sql(&mut db, "CREATE TABLE p1(x, y)").unwrap();
exec_sql(&mut db, "INSERT INTO p1 VALUES(1, 1)").unwrap();
exec_sql(&mut db, "CREATE TABLE c1(a REFERENCES p1(x))").unwrap();
exec_sql(&mut db, "CREATE UNIQUE INDEX p1x ON p1(x) WHERE y<2").unwrap();
let err = exec_sql(&mut db, "INSERT INTO c1 VALUES(1)").unwrap_err();
assert!(err.contains("foreign key mismatch"), "expected mismatch, got: {}", err);
exec_sql(&mut db, "CREATE UNIQUE INDEX p1x2 ON p1(x)").unwrap();
let r = exec_sql(&mut db, "INSERT INTO c1 VALUES(1)");
assert!(r.is_ok(), "expected success after full UNIQUE INDEX; got: {:?}", r);
}
#[test]
fn fk_mismatch_resolved_by_creating_unique_index() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
exec_sql(&mut db, "CREATE TABLE p1(x, y)").unwrap();
exec_sql(&mut db, "INSERT INTO p1 VALUES(1, 1)").unwrap();
exec_sql(&mut db, "CREATE TABLE c1(a REFERENCES p1(x))").unwrap();
exec_sql(&mut db, "CREATE UNIQUE INDEX p1x2 ON p1(x)").unwrap();
let r = exec_sql(&mut db, "INSERT INTO c1 VALUES(1)");
assert!(r.is_ok(), "expected success after UNIQUE INDEX; got: {:?}", r);
}
#[test]
fn fk_succeeds_with_primary_key_target() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
exec_sql(&mut db, "CREATE TABLE parent(id INT PRIMARY KEY, name VARCHAR(50))").unwrap();
exec_sql(&mut db, "INSERT INTO parent VALUES(1, 'a'), (2, 'b')").unwrap();
exec_sql(&mut db, "CREATE TABLE child(pid INT REFERENCES parent(id))").unwrap();
let r = exec_sql(&mut db, "INSERT INTO child VALUES(1)");
assert!(r.is_ok(), "FK against PRIMARY KEY should succeed; got: {:?}", r);
}
#[test]
fn fk_succeeds_with_unique_constraint_target() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
exec_sql(&mut db, "CREATE TABLE parent(id INT, code VARCHAR(10) UNIQUE)").unwrap();
exec_sql(&mut db, "INSERT INTO parent VALUES(1, 'A'), (2, 'B')").unwrap();
exec_sql(&mut db, "CREATE TABLE child(c VARCHAR(10) REFERENCES parent(code))").unwrap();
let r = exec_sql(&mut db, "INSERT INTO child VALUES('A')");
assert!(r.is_ok(), "FK against UNIQUE column should succeed; got: {:?}", r);
}
#[test]
fn fk_uses_rtrim_collation_for_parent_match() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
exec_sql(
&mut db,
"CREATE TABLE p20(x VARCHAR(10) COLLATE RTRIM PRIMARY KEY, y VARCHAR(20))",
)
.unwrap();
exec_sql(&mut db, "INSERT INTO p20 VALUES('abc', 'Alpha')").unwrap();
exec_sql(
&mut db,
"CREATE TABLE c21(a VARCHAR(20), b VARCHAR(10) REFERENCES p20(x))",
)
.unwrap();
let r = exec_sql(&mut db, "INSERT INTO c21 VALUES('alpha', 'abc ')");
assert!(r.is_ok(), "RTRIM-aware FK should succeed; got: {:?}", r);
}
#[test]
fn fk_uses_nocase_collation_for_parent_match() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
exec_sql(
&mut db,
"CREATE TABLE p_nc(x VARCHAR(10) COLLATE NOCASE PRIMARY KEY)",
)
.unwrap();
exec_sql(&mut db, "INSERT INTO p_nc VALUES('Alpha')").unwrap();
exec_sql(
&mut db,
"CREATE TABLE c_nc(a VARCHAR(10) REFERENCES p_nc(x))",
)
.unwrap();
let r = exec_sql(&mut db, "INSERT INTO c_nc VALUES('alpha')");
assert!(r.is_ok(), "NOCASE-aware FK should succeed; got: {:?}", r);
}