use vibesql_executor::SelectExecutor;
use vibesql_types::SqlValue;
fn run_stmt(db: &mut vibesql_storage::Database, sql: &str) {
let stmt = vibesql_parser::Parser::parse_sql(sql).unwrap();
match stmt {
vibesql_ast::Statement::CreateTable(create_table) => {
vibesql_executor::CreateTableExecutor::execute(&create_table, db).unwrap();
}
vibesql_ast::Statement::Insert(insert) => {
vibesql_executor::InsertExecutor::execute(db, &insert).unwrap();
}
vibesql_ast::Statement::CreateView(create_view) => {
vibesql_executor::ViewExecutor::execute_create_view(&create_view, db).unwrap();
}
other => panic!("Unsupported statement in test setup: {:?}", other),
}
}
fn query(db: &vibesql_storage::Database, sql: &str) -> Vec<Vec<SqlValue>> {
let stmt = vibesql_parser::Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Select(select_stmt) = stmt {
let executor = SelectExecutor::new(db);
executor
.execute(&select_stmt)
.unwrap_or_else(|e| panic!("Query failed: {} -- {:?}", sql, e))
.into_iter()
.map(|row| row.values.to_vec())
.collect()
} else {
panic!("Expected SELECT statement: {}", sql);
}
}
fn setup_db() -> vibesql_storage::Database {
let mut db = vibesql_storage::Database::new();
run_stmt(&mut db, "CREATE TABLE t1(id INTEGER PRIMARY KEY, grp_id INTEGER)");
run_stmt(&mut db, "INSERT INTO t1 VALUES (1,2),(2,3),(3,2)");
run_stmt(
&mut db,
"CREATE VIEW lll AS \
SELECT row_number() OVER (PARTITION BY grp_id) AS rn, grp_id, id FROM t1",
);
db
}
#[test]
fn test_scalar_subquery_sees_outer_cte() {
let db = setup_db();
let rows = query(
&db,
"WITH lll AS (SELECT 99 AS rn, 2 AS grp_id, 100 AS id) \
SELECT (SELECT max(id) FROM lll WHERE grp_id = 2)",
);
assert_eq!(rows, vec![vec![SqlValue::Integer(100)]]);
}
#[test]
fn test_exists_subquery_sees_outer_cte() {
let db = setup_db();
let rows = query(
&db,
"WITH lll AS (SELECT 99 AS rn, 2 AS grp_id, 100 AS id) \
SELECT EXISTS(SELECT 1 FROM lll WHERE id = 100)",
);
assert_eq!(rows, vec![vec![SqlValue::Boolean(true)]]);
}
#[test]
fn test_in_subquery_sees_outer_cte() {
let db = setup_db();
let rows = query(
&db,
"WITH lll AS (SELECT 99 AS rn, 2 AS grp_id, 100 AS id) \
SELECT 100 IN (SELECT id FROM lll)",
);
assert_eq!(rows, vec![vec![SqlValue::Boolean(true)]]);
}
#[test]
fn test_where_clause_subquery_sees_outer_cte() {
let db = setup_db();
let rows = query(
&db,
"WITH lll AS (SELECT 99 AS rn, 2 AS grp_id, 100 AS id) \
SELECT 5 WHERE (SELECT max(id) FROM lll) = 100",
);
assert_eq!(rows, vec![vec![SqlValue::Integer(5)]]);
}
#[test]
fn test_case_insensitive_cte_reference() {
let db = setup_db();
let rows = query(
&db,
"WITH lll AS (SELECT 99 AS rn, 2 AS grp_id, 100 AS id) \
SELECT (SELECT max(id) FROM LLL)",
);
assert_eq!(rows, vec![vec![SqlValue::Integer(100)]]);
}
#[test]
fn test_inner_with_shadows_outer_cte() {
let db = setup_db();
let rows = query(
&db,
"WITH lll AS (SELECT 100 AS id) \
SELECT (WITH lll AS (SELECT 200 AS id) SELECT max(id) FROM lll)",
);
assert_eq!(rows, vec![vec![SqlValue::Integer(200)]]);
}
#[test]
fn test_no_cte_still_resolves_catalog_view() {
let db = setup_db();
let rows = query(&db, "SELECT (SELECT max(id) FROM lll WHERE grp_id = 2)");
assert_eq!(rows, vec![vec![SqlValue::Integer(3)]]);
}
#[test]
fn test_cte_composes_with_correlated_subquery() {
let db = setup_db();
let rows = query(
&db,
"WITH c AS (SELECT 10 AS v) \
SELECT (SELECT t1.id + (SELECT v FROM c)) FROM t1 ORDER BY id",
);
assert_eq!(
rows,
vec![vec![SqlValue::Integer(11)], vec![SqlValue::Integer(12)], vec![SqlValue::Integer(13)],]
);
}
#[test]
fn test_union_branch_subquery_sees_outer_cte() {
let db = setup_db();
let mut rows = query(
&db,
"WITH lll AS (SELECT 100 AS id) \
SELECT 1 UNION SELECT (SELECT max(id) FROM lll)",
);
rows.sort();
assert_eq!(rows, vec![vec![SqlValue::Integer(1)], vec![SqlValue::Integer(100)]]);
}
#[test]
fn test_window_path_subquery_sees_outer_cte() {
let db = setup_db();
let rows = query(
&db,
"WITH lll AS (SELECT 100 AS id) \
SELECT (SELECT max(id) FROM lll), min(1) OVER ()",
);
assert_eq!(rows, vec![vec![SqlValue::Integer(100), SqlValue::Integer(1)]]);
}