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::Select(select_stmt) => {
let result = SelectExecutor::new(db).execute(&select_stmt)?;
Ok(result)
}
_ => Err(ExecutorError::UnsupportedFeature(format!(
"Unsupported statement type: {:?}",
stmt
))),
}
}
#[test]
fn test_cte_in_scalar_subquery_basic() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE supplier(s_suppkey INT, s_name VARCHAR(25))").unwrap();
execute_sql(&mut db, "CREATE TABLE lineitem(l_suppkey INT, l_amount DECIMAL(10,2))").unwrap();
execute_sql(&mut db, "INSERT INTO supplier VALUES (1, 'Supplier A')").unwrap();
execute_sql(&mut db, "INSERT INTO supplier VALUES (2, 'Supplier B')").unwrap();
execute_sql(&mut db, "INSERT INTO lineitem VALUES (1, 100.00)").unwrap();
execute_sql(&mut db, "INSERT INTO lineitem VALUES (1, 200.00)").unwrap();
execute_sql(&mut db, "INSERT INTO lineitem VALUES (2, 150.00)").unwrap();
let query = "
WITH revenue AS (
SELECT l_suppkey as supplier_no,
SUM(l_amount) as total_revenue
FROM lineitem
GROUP BY l_suppkey
)
SELECT s_suppkey, s_name, total_revenue
FROM supplier, revenue
WHERE s_suppkey = supplier_no
AND total_revenue = (SELECT MAX(total_revenue) FROM revenue)
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 1, "Should return exactly one supplier with max revenue");
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(1));
assert_eq!(
result[0].values[1],
vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from("Supplier A"))
);
}
#[test]
fn test_cte_in_scalar_subquery_ties() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE supplier(s_suppkey INT, s_name VARCHAR(25))").unwrap();
execute_sql(&mut db, "CREATE TABLE lineitem(l_suppkey INT, l_amount DECIMAL(10,2))").unwrap();
execute_sql(&mut db, "INSERT INTO supplier VALUES (1, 'Supplier A')").unwrap();
execute_sql(&mut db, "INSERT INTO supplier VALUES (2, 'Supplier B')").unwrap();
execute_sql(&mut db, "INSERT INTO supplier VALUES (3, 'Supplier C')").unwrap();
execute_sql(&mut db, "INSERT INTO lineitem VALUES (1, 150.00)").unwrap();
execute_sql(&mut db, "INSERT INTO lineitem VALUES (1, 130.00)").unwrap();
execute_sql(&mut db, "INSERT INTO lineitem VALUES (2, 280.00)").unwrap();
execute_sql(&mut db, "INSERT INTO lineitem VALUES (3, 200.00)").unwrap();
let query = "
WITH revenue AS (
SELECT l_suppkey as supplier_no,
SUM(l_amount) as total_revenue
FROM lineitem
GROUP BY l_suppkey
)
SELECT s_suppkey, s_name, total_revenue
FROM supplier, revenue
WHERE s_suppkey = supplier_no
AND total_revenue = (SELECT MAX(total_revenue) FROM revenue)
ORDER BY s_suppkey
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 2, "Should return both suppliers tied for max revenue");
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(1));
assert_eq!(result[1].values[0], vibesql_types::SqlValue::Integer(2));
}
#[test]
fn test_cte_multiple_scalar_subquery_refs() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE items(id INT, value INT)").unwrap();
execute_sql(&mut db, "INSERT INTO items VALUES (1, 10)").unwrap();
execute_sql(&mut db, "INSERT INTO items VALUES (2, 20)").unwrap();
execute_sql(&mut db, "INSERT INTO items VALUES (3, 30)").unwrap();
execute_sql(&mut db, "INSERT INTO items VALUES (4, 40)").unwrap();
let query = "
WITH stats AS (
SELECT value FROM items
)
SELECT id, value
FROM items
WHERE value >= (SELECT AVG(value) FROM stats)
AND value <= (SELECT MAX(value) FROM stats)
ORDER BY id
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(3));
assert_eq!(result[1].values[0], vibesql_types::SqlValue::Integer(4));
}
#[test]
fn test_cte_min_in_scalar_subquery() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE prices(product_id INT, price INT)").unwrap();
execute_sql(&mut db, "INSERT INTO prices VALUES (1, 100)").unwrap();
execute_sql(&mut db, "INSERT INTO prices VALUES (2, 50)").unwrap();
execute_sql(&mut db, "INSERT INTO prices VALUES (3, 75)").unwrap();
let query = "
WITH price_list AS (
SELECT product_id, price FROM prices
)
SELECT product_id, price
FROM price_list
WHERE price = (SELECT MIN(price) FROM price_list)
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(2));
assert_eq!(result[0].values[1], vibesql_types::SqlValue::Integer(50));
}
#[test]
fn test_cte_in_select_list_scalar_subquery() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE sales(region TEXT, amount INT)").unwrap();
execute_sql(&mut db, "INSERT INTO sales VALUES ('North', 100)").unwrap();
execute_sql(&mut db, "INSERT INTO sales VALUES ('South', 200)").unwrap();
execute_sql(&mut db, "INSERT INTO sales VALUES ('East', 150)").unwrap();
let query = "
WITH totals AS (
SELECT SUM(amount) as total FROM sales
)
SELECT region, amount, (SELECT total FROM totals) as grand_total
FROM sales
ORDER BY region
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 3);
assert_eq!(result[0].values[2], vibesql_types::SqlValue::Integer(450));
assert_eq!(result[1].values[2], vibesql_types::SqlValue::Integer(450));
assert_eq!(result[2].values[2], vibesql_types::SqlValue::Integer(450));
}
#[test]
fn test_cte_in_in_subquery() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE orders_cte_in(order_id INT, customer_id INT, amount INT)")
.unwrap();
execute_sql(&mut db, "INSERT INTO orders_cte_in VALUES (1, 1, 100)").unwrap();
execute_sql(&mut db, "INSERT INTO orders_cte_in VALUES (2, 2, 200)").unwrap();
execute_sql(&mut db, "INSERT INTO orders_cte_in VALUES (3, 1, 150)").unwrap();
execute_sql(&mut db, "INSERT INTO orders_cte_in VALUES (4, 3, 50)").unwrap();
let query = "
WITH high_value AS (
SELECT customer_id, amount FROM orders_cte_in WHERE amount >= 150
)
SELECT o.order_id, o.customer_id, o.amount
FROM orders_cte_in o
WHERE o.customer_id IN (SELECT h.customer_id FROM high_value h WHERE h.customer_id = o.customer_id)
ORDER BY o.order_id
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 3, "Expected 3 orders from customers with high-value orders");
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(1));
assert_eq!(result[1].values[0], vibesql_types::SqlValue::Integer(2));
assert_eq!(result[2].values[0], vibesql_types::SqlValue::Integer(3));
}
#[test]
fn test_cte_in_exists_subquery() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE products(product_id INT, name TEXT)").unwrap();
execute_sql(&mut db, "CREATE TABLE order_items(order_id INT, product_id INT)").unwrap();
execute_sql(&mut db, "INSERT INTO products VALUES (1, 'Widget')").unwrap();
execute_sql(&mut db, "INSERT INTO products VALUES (2, 'Gadget')").unwrap();
execute_sql(&mut db, "INSERT INTO products VALUES (3, 'Gizmo')").unwrap();
execute_sql(&mut db, "INSERT INTO order_items VALUES (100, 1)").unwrap();
execute_sql(&mut db, "INSERT INTO order_items VALUES (101, 2)").unwrap();
let query = "
WITH ordered_products AS (
SELECT DISTINCT product_id FROM order_items
)
SELECT product_id, name
FROM products p
WHERE EXISTS (SELECT 1 FROM ordered_products op WHERE op.product_id = p.product_id)
ORDER BY product_id
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(1));
assert_eq!(result[1].values[0], vibesql_types::SqlValue::Integer(2));
}
#[test]
fn test_cte_left_join_group_by_issue_3656() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE test_orders_3656(o_id INT, o_customer INT)").unwrap();
execute_sql(&mut db, "CREATE TABLE test_returns_3656(r_order_id INT, r_qty INT)").unwrap();
execute_sql(&mut db, "INSERT INTO test_orders_3656 VALUES (1, 100)").unwrap();
execute_sql(&mut db, "INSERT INTO test_orders_3656 VALUES (2, 100)").unwrap();
execute_sql(&mut db, "INSERT INTO test_orders_3656 VALUES (3, 200)").unwrap();
execute_sql(&mut db, "INSERT INTO test_returns_3656 VALUES (1, 5)").unwrap();
let query_derived = "
SELECT o_customer, COUNT(*) as cnt
FROM (
SELECT o_customer
FROM test_orders_3656 o
LEFT JOIN test_returns_3656 r ON o.o_id = r.r_order_id
) t
GROUP BY o_customer
";
let result_derived = execute_sql(&mut db, query_derived).unwrap();
assert_eq!(
result_derived.len(),
2,
"Derived table should have 2 groups (customer 100 and 200)"
);
let query_cte = "
WITH cte AS (
SELECT o_customer
FROM test_orders_3656 o
LEFT JOIN test_returns_3656 r ON o.o_id = r.r_order_id
)
SELECT o_customer, COUNT(*) as cnt
FROM cte
GROUP BY o_customer
";
let result_cte = execute_sql(&mut db, query_cte).unwrap();
assert_eq!(
result_cte.len(),
2,
"CTE with LEFT JOIN + GROUP BY should also have 2 groups (issue #3656)"
);
}
#[test]
fn test_cte_union_left_join_group_by_issue_3656() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE sales_a(sale_id INT, item_id INT, qty INT)").unwrap();
execute_sql(&mut db, "CREATE TABLE sales_b(sale_id INT, item_id INT, qty INT)").unwrap();
execute_sql(&mut db, "CREATE TABLE returns_a(sale_id INT, ret_qty INT)").unwrap();
execute_sql(&mut db, "CREATE TABLE returns_b(sale_id INT, ret_qty INT)").unwrap();
execute_sql(&mut db, "CREATE TABLE items(item_id INT, brand INT)").unwrap();
execute_sql(&mut db, "INSERT INTO items VALUES (1, 100)").unwrap();
execute_sql(&mut db, "INSERT INTO items VALUES (2, 100)").unwrap();
execute_sql(&mut db, "INSERT INTO items VALUES (3, 200)").unwrap();
execute_sql(&mut db, "INSERT INTO sales_a VALUES (1, 1, 10)").unwrap();
execute_sql(&mut db, "INSERT INTO sales_a VALUES (2, 2, 20)").unwrap();
execute_sql(&mut db, "INSERT INTO sales_a VALUES (3, 3, 30)").unwrap();
execute_sql(&mut db, "INSERT INTO sales_b VALUES (4, 1, 15)").unwrap();
execute_sql(&mut db, "INSERT INTO sales_b VALUES (5, 2, 25)").unwrap();
execute_sql(&mut db, "INSERT INTO returns_a VALUES (1, 2)").unwrap();
let query = "
WITH all_sales AS (
SELECT brand, SUM(net_qty) AS total_qty
FROM (
SELECT i.brand, s.qty - COALESCE(r.ret_qty, 0) AS net_qty
FROM sales_a s
JOIN items i ON s.item_id = i.item_id
LEFT JOIN returns_a r ON s.sale_id = r.sale_id
UNION ALL
SELECT i.brand, s.qty - COALESCE(r.ret_qty, 0) AS net_qty
FROM sales_b s
JOIN items i ON s.item_id = i.item_id
LEFT JOIN returns_b r ON s.sale_id = r.sale_id
) combined
GROUP BY brand
)
SELECT COUNT(*) FROM all_sales
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 1, "Should return one row with count");
let count = match &result[0].values[0] {
vibesql_types::SqlValue::Integer(n) => *n,
_ => panic!("Expected integer count"),
};
assert_eq!(
count, 2,
"CTE with UNION ALL + LEFT JOIN + GROUP BY should produce 2 groups (brands 100 and 200), got {}",
count
);
}
#[test]
fn test_multi_column_left_outer_join_issue_3656() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE sales_3656(ticket_num INT, item_id INT, qty INT)").unwrap();
execute_sql(&mut db, "CREATE TABLE returns_3656(ret_ticket INT, ret_item INT, ret_qty INT)")
.unwrap();
execute_sql(&mut db, "INSERT INTO sales_3656 VALUES (1, 100, 10)").unwrap(); execute_sql(&mut db, "INSERT INTO sales_3656 VALUES (1, 200, 20)").unwrap(); execute_sql(&mut db, "INSERT INTO sales_3656 VALUES (2, 100, 30)").unwrap(); execute_sql(&mut db, "INSERT INTO sales_3656 VALUES (3, 300, 40)").unwrap();
execute_sql(&mut db, "INSERT INTO returns_3656 VALUES (1, 100, 5)").unwrap();
let query = "
SELECT s.ticket_num, s.item_id, s.qty, COALESCE(r.ret_qty, 0) as ret_qty
FROM sales_3656 s
LEFT JOIN returns_3656 r
ON s.ticket_num = r.ret_ticket
AND s.item_id = r.ret_item
ORDER BY s.ticket_num, s.item_id
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(
result.len(),
4,
"Multi-column LEFT JOIN should preserve all 4 sales rows, got {}. \
Bug was: second condition applied as post-filter, which filtered out \
unmatched rows because NULL = value evaluates to NULL (falsy)",
result.len()
);
assert_eq!(
result[0].values.to_vec(),
vec![
vibesql_types::SqlValue::Integer(1),
vibesql_types::SqlValue::Integer(100),
vibesql_types::SqlValue::Integer(10),
vibesql_types::SqlValue::Integer(5), ]
);
assert_eq!(
result[1].values.to_vec(),
vec![
vibesql_types::SqlValue::Integer(1),
vibesql_types::SqlValue::Integer(200),
vibesql_types::SqlValue::Integer(20),
vibesql_types::SqlValue::Integer(0), ]
);
assert_eq!(
result[2].values.to_vec(),
vec![
vibesql_types::SqlValue::Integer(2),
vibesql_types::SqlValue::Integer(100),
vibesql_types::SqlValue::Integer(30),
vibesql_types::SqlValue::Integer(0),
]
);
assert_eq!(
result[3].values.to_vec(),
vec![
vibesql_types::SqlValue::Integer(3),
vibesql_types::SqlValue::Integer(300),
vibesql_types::SqlValue::Integer(40),
vibesql_types::SqlValue::Integer(0),
]
);
}
#[test]
fn test_cte_in_not_exists_subquery() {
let mut db = vibesql_storage::Database::new();
execute_sql(&mut db, "CREATE TABLE products(product_id INT, name TEXT)").unwrap();
execute_sql(&mut db, "CREATE TABLE order_items(order_id INT, product_id INT)").unwrap();
execute_sql(&mut db, "INSERT INTO products VALUES (1, 'Widget')").unwrap();
execute_sql(&mut db, "INSERT INTO products VALUES (2, 'Gadget')").unwrap();
execute_sql(&mut db, "INSERT INTO products VALUES (3, 'Gizmo')").unwrap();
execute_sql(&mut db, "INSERT INTO order_items VALUES (100, 1)").unwrap();
execute_sql(&mut db, "INSERT INTO order_items VALUES (101, 2)").unwrap();
let query = "
WITH ordered_products AS (
SELECT DISTINCT product_id FROM order_items
)
SELECT product_id, name
FROM products p
WHERE NOT EXISTS (SELECT 1 FROM ordered_products op WHERE op.product_id = p.product_id)
ORDER BY product_id
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(3));
assert_eq!(
result[0].values[1],
vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from("Gizmo"))
);
}
#[test]
fn test_recursive_cte_column_count_validation() {
let mut db = vibesql_storage::Database::new();
let query = "
WITH RECURSIVE bad AS (
SELECT 1 AS x
UNION ALL
SELECT x+1, x+2 FROM bad WHERE x<3
)
SELECT * FROM bad
";
let result = execute_sql(&mut db, query);
assert!(result.is_err(), "Expected error for mismatched column counts");
let err = result.unwrap_err();
let err_msg = format!("{:?}", err);
assert!(
err_msg.contains("SELECTs to the left and right of UNION ALL do not have the same number of result columns"),
"Expected specific SQLite error message, got: {}",
err_msg
);
}
#[test]
fn test_recursive_cte_matching_columns() {
let mut db = vibesql_storage::Database::new();
let query = "
WITH RECURSIVE good AS (
SELECT 1 AS x
UNION ALL
SELECT x+1 FROM good WHERE x<3
)
SELECT * FROM good
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 3);
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(1));
assert_eq!(result[1].values[0], vibesql_types::SqlValue::Integer(2));
assert_eq!(result[2].values[0], vibesql_types::SqlValue::Integer(3));
}
#[test]
fn test_recursive_cte_static_column_validation_edge_case() {
let mut db = vibesql_storage::Database::new();
let query = "
WITH RECURSIVE edge AS (
SELECT 1 AS x
UNION ALL
SELECT x+1, x+2 FROM edge WHERE x > 1000
)
SELECT * FROM edge
";
let result = execute_sql(&mut db, query);
assert!(result.is_err(), "Expected error for mismatched column counts (static validation)");
let err = result.unwrap_err();
let err_msg = format!("{:?}", err);
assert!(
err_msg.contains("SELECTs to the left and right of UNION ALL do not have the same number of result columns"),
"Expected specific SQLite error message, got: {}",
err_msg
);
}
#[test]
fn test_recursive_cte_static_validation_success() {
let mut db = vibesql_storage::Database::new();
let query = "
WITH RECURSIVE edge AS (
SELECT 1 AS x
UNION ALL
SELECT x+1 FROM edge WHERE x > 1000
)
SELECT * FROM edge
";
let result = execute_sql(&mut db, query).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].values[0], vibesql_types::SqlValue::Integer(1));
}