use vibesql_ast::Statement;
use vibesql_parser::Parser;
use super::super::*;
fn execute_sql(
db: &mut vibesql_storage::Database,
sql: &str,
) -> Result<Vec<vibesql_storage::Row>, ExecutorError> {
let stmt = Parser::parse_sql(sql).map_err(|e| ExecutorError::ParseError(format!("{:?}", e)))?;
match stmt {
Statement::CreateTable(create_stmt) => {
CreateTableExecutor::execute(&create_stmt, db)?;
Ok(vec![])
}
Statement::Insert(insert_stmt) => {
InsertExecutor::execute(db, &insert_stmt)?;
Ok(vec![])
}
Statement::CreateView(view_stmt) => {
crate::advanced_objects::execute_create_view(&view_stmt, db)?;
Ok(vec![])
}
Statement::Select(select_stmt) => Ok(SelectExecutor::new(db).execute(&select_stmt)?),
other => {
Err(ExecutorError::UnsupportedFeature(format!("Unsupported statement: {:?}", other)))
}
}
}
#[test]
fn test_collate_nocase_through_aggregate_into_in_subquery() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE t1(c1 VARCHAR(10))").unwrap();
execute_sql(&mut db, "INSERT INTO t1 VALUES('abcd')").unwrap();
let rows =
execute_sql(&mut db, "SELECT max(c1 COLLATE nocase) IN (SELECT 'aBCd') FROM t1").unwrap();
assert_eq!(rows.len(), 1);
assert!(
matches!(
rows[0].values[0],
vibesql_types::SqlValue::Boolean(true) | vibesql_types::SqlValue::Integer(1)
),
"expected truthy IN result, got {:?}",
rows[0].values[0]
);
}
#[test]
fn test_aggregate_in_subquery_without_collate_stays_binary() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE t1(c1 VARCHAR(10))").unwrap();
execute_sql(&mut db, "INSERT INTO t1 VALUES('abcd')").unwrap();
let rows = execute_sql(&mut db, "SELECT max(c1) IN (SELECT 'aBCd') FROM t1").unwrap();
assert_eq!(rows.len(), 1);
assert!(
matches!(
rows[0].values[0],
vibesql_types::SqlValue::Boolean(false) | vibesql_types::SqlValue::Integer(0)
),
"expected falsy IN result, got {:?}",
rows[0].values[0]
);
}
#[test]
fn test_window_function_in_recursive_cte_errors_with_sqlite_message() {
let mut db = vibesql_storage::Database::new();
let err = execute_sql(
&mut db,
"WITH RECURSIVE q(x, rn) AS (
SELECT 1, 1
UNION ALL
SELECT x+1, ROW_NUMBER() OVER (ORDER BY x) FROM q WHERE x < 3
)
SELECT * FROM q",
)
.unwrap_err();
assert_eq!(err.to_string(), "cannot use window functions in recursive queries");
}
#[test]
fn test_fromless_select_alias_in_where_falsy() {
let mut db = vibesql_storage::Database::new();
let rows = execute_sql(&mut db, "SELECT (SELECT '') x WHERE x+x").unwrap();
assert!(rows.is_empty(), "expected no rows, got {:?}", rows);
}
#[test]
fn test_fromless_select_alias_in_where_truthy() {
let mut db = vibesql_storage::Database::new();
let rows = execute_sql(&mut db, "SELECT (SELECT 1) x WHERE x+x").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].values[0], vibesql_types::SqlValue::Integer(1));
}
#[test]
fn test_fromless_select_where_unknown_column_still_errors() {
let mut db = vibesql_storage::Database::new();
let result = execute_sql(&mut db, "SELECT 1 x WHERE y");
assert!(result.is_err(), "unknown column in WHERE should still error");
}
#[test]
fn test_create_view_error_is_prefixed_with_view_name() {
let mut db = vibesql_storage::Database::new();
let err = execute_sql(
&mut db,
"CREATE VIEW a AS SELECT NULL INTERSECT SELECT NULL ORDER BY s() OVER R",
)
.unwrap_err();
let msg = err.to_string();
assert!(
msg.starts_with("error in view a: "),
"expected 'error in view a: ' prefix, got: {msg}"
);
}
#[test]
fn test_create_view_set_operation_mismatch_is_not_prefixed() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE t01(x INTEGER, y INTEGER)").unwrap();
execute_sql(&mut db, "CREATE TABLE t02(x INTEGER)").unwrap();
let err =
execute_sql(&mut db, "CREATE VIEW v0 AS SELECT x, y FROM t01 UNION SELECT x FROM t02")
.unwrap_err();
assert_eq!(
err.to_string(),
"SELECTs to the left and right of UNION do not have the same number of result columns"
);
}
#[test]
fn test_create_view_intersect_mismatch_is_not_prefixed() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE t01(x INTEGER, y INTEGER)").unwrap();
execute_sql(&mut db, "CREATE TABLE t02(x INTEGER)").unwrap();
let err =
execute_sql(&mut db, "CREATE VIEW v1 AS SELECT x, y FROM t01 INTERSECT SELECT x FROM t02")
.unwrap_err();
let msg = err.to_string();
assert!(
!msg.starts_with("error in view"),
"INTERSECT arity mismatch must not carry the view prefix, got: {msg}"
);
assert!(msg.contains("INTERSECT"), "expected INTERSECT in message, got: {msg}");
}
#[test]
fn test_cast_to_unknown_typename_numeric_affinity() {
let mut db = vibesql_storage::Database::new();
let rows = execute_sql(&mut db, "SELECT CAST('seventeen' AS banana)").unwrap();
assert_eq!(rows[0].values[0], vibesql_types::SqlValue::Integer(0));
let rows = execute_sql(&mut db, "SELECT CAST('5' AS banana)").unwrap();
assert_eq!(rows[0].values[0], vibesql_types::SqlValue::Integer(5));
let rows = execute_sql(&mut db, "SELECT CAST('1.5' AS banana)").unwrap();
assert_eq!(rows[0].values[0], vibesql_types::SqlValue::Double(1.5));
let rows = execute_sql(&mut db, "SELECT CAST(7 AS banana)").unwrap();
assert_eq!(rows[0].values[0], vibesql_types::SqlValue::Integer(7));
}
#[test]
fn test_cast_empty_typename_passes_value_through_sum() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE t1(a)").unwrap();
execute_sql(&mut db, "INSERT INTO t1 VALUES(5),(NULL),('seventeen')").unwrap();
let rows = execute_sql(&mut db, "SELECT sum(CAST(a AS )) FROM t1").unwrap();
assert_eq!(rows.len(), 1, "sum(CAST(a AS )) should produce one row");
}