use vibesql_executor::{InsertExecutor, SelectExecutor, TriggerExecutor};
use vibesql_parser::Parser;
use vibesql_storage::Database;
use vibesql_types::SqlValue;
fn exec(db: &mut Database, sql: &str) -> Result<(), vibesql_executor::ExecutorError> {
match Parser::parse_sql(sql).expect("test SQL should parse") {
vibesql_ast::Statement::CreateTable(s) => {
vibesql_executor::CreateTableExecutor::execute(&s, db)?;
}
vibesql_ast::Statement::CreateTrigger(s) => {
TriggerExecutor::create_trigger_with_sql(db, &s, Some(sql))?;
}
vibesql_ast::Statement::Insert(s) => {
InsertExecutor::execute(db, &s)?;
}
other => panic!("unexpected statement in test: {other:?}"),
}
Ok(())
}
fn query(db: &Database, sql: &str) -> Vec<Vec<SqlValue>> {
match Parser::parse_sql(sql).expect("select should parse") {
vibesql_ast::Statement::Select(s) => SelectExecutor::new(db)
.execute(&s)
.expect("select should succeed")
.into_iter()
.map(|r| r.values.to_vec())
.collect(),
_ => panic!("expected SELECT"),
}
}
fn rowid_and_type(db: &Database, table: &str) -> (i64, SqlValue) {
let rows = query(db, &format!("SELECT rowid, typeof(rowid) FROM {table}"));
assert_eq!(rows.len(), 1, "expected exactly one row in {table}");
let rowid = match &rows[0][0] {
SqlValue::Integer(i) => *i,
SqlValue::Bigint(i) => *i,
other => panic!("rowid not an integer type: {other:?}"),
};
(rowid, rows[0][1].clone())
}
#[test]
fn text_integer_rowid_coerced_to_integer() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t(a TEXT)").unwrap();
exec(&mut db, "INSERT INTO t(rowid, a) VALUES('45', 'x')").unwrap();
let (rowid, ty) = rowid_and_type(&db, "t");
assert_eq!(rowid, 45);
assert_eq!(ty, SqlValue::Varchar("integer".into()));
}
#[test]
fn positive_real_integer_rowid_coerced_to_integer() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t(a TEXT)").unwrap();
exec(&mut db, "INSERT INTO t(rowid, a) VALUES(45.0, 'x')").unwrap();
let (rowid, ty) = rowid_and_type(&db, "t");
assert_eq!(rowid, 45);
assert_eq!(ty, SqlValue::Varchar("integer".into()));
}
#[test]
fn negative_real_integer_rowid_coerced_to_integer() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t(a TEXT)").unwrap();
exec(&mut db, "INSERT INTO t(rowid, a) VALUES(-42.0, 'x')").unwrap();
let (rowid, ty) = rowid_and_type(&db, "t");
assert_eq!(rowid, -42);
assert_eq!(ty, SqlValue::Varchar("integer".into()));
}
#[test]
fn negative_integer_rowid_accepted() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t(a TEXT)").unwrap();
exec(&mut db, "INSERT INTO t(rowid, a) VALUES(-7, 'x')").unwrap();
let (rowid, ty) = rowid_and_type(&db, "t");
assert_eq!(rowid, -7);
assert_eq!(ty, SqlValue::Varchar("integer".into()));
}
#[test]
fn fractional_real_rowid_raises_datatype_mismatch() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t(a TEXT)").unwrap();
let pos = exec(&mut db, "INSERT INTO t(rowid, a) VALUES(42.4, 'x')");
assert!(pos.is_err(), "fractional rowid should be rejected");
assert!(format!("{:?}", pos.unwrap_err()).contains("datatype mismatch"));
let neg = exec(&mut db, "INSERT INTO t(rowid, a) VALUES(-42.4, 'x')");
assert!(neg.is_err(), "fractional negative rowid should be rejected");
assert!(format!("{:?}", neg.unwrap_err()).contains("datatype mismatch"));
}
#[test]
fn non_numeric_text_rowid_raises_datatype_mismatch() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE t(a TEXT)").unwrap();
let res = exec(&mut db, "INSERT INTO t(rowid, a) VALUES('abc', 'x')");
assert!(res.is_err(), "non-numeric text rowid should be rejected");
assert!(format!("{:?}", res.unwrap_err()).contains("datatype mismatch"));
}
#[test]
fn trigger_observes_coerced_rowid_for_negative_real() {
let mut db = Database::new();
exec(&mut db, "CREATE TABLE log(t TEXT)").unwrap();
exec(&mut db, "CREATE TABLE t4(a TEXT, b INTEGER, c REAL)").unwrap();
exec(
&mut db,
"CREATE TRIGGER t4ai AFTER INSERT ON t4 BEGIN \
INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' || \
new.b || ' ' || typeof(new.b)); \
END",
)
.unwrap();
exec(&mut db, "INSERT INTO t4(rowid,a,b,c) VALUES(-42.0, -42.0, -42.0, -42.0)").unwrap();
let rows = query(&db, "SELECT t FROM log");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0][0], SqlValue::Varchar("-42 integer -42 integer".into()));
}