use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_executor::DeleteExecutor;
use vibesql_parser::Parser;
use vibesql_storage::{Database, Row};
use vibesql_types::{DataType, SqlValue};
fn setup_customers_orders_db() -> Database {
let mut db = Database::new();
let customers_schema = TableSchema::new(
"CUSTOMERS".to_string(),
vec![
ColumnSchema::new("ID".to_string(), DataType::Integer, false),
ColumnSchema::new(
"NAME".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
ColumnSchema::new("ACTIVE".to_string(), DataType::Boolean, false),
],
);
db.create_table(customers_schema).unwrap();
let orders_schema = TableSchema::new(
"ORDERS".to_string(),
vec![
ColumnSchema::new("ID".to_string(), DataType::Integer, false),
ColumnSchema::new("CUSTOMER_ID".to_string(), DataType::Integer, false),
ColumnSchema::new("TOTAL".to_string(), DataType::Integer, false),
],
);
db.create_table(orders_schema).unwrap();
db.insert_row(
"CUSTOMERS",
Row::new(vec![
SqlValue::Integer(1),
SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
SqlValue::Boolean(true),
]),
)
.unwrap();
db.insert_row(
"CUSTOMERS",
Row::new(vec![
SqlValue::Integer(2),
SqlValue::Varchar(arcstr::ArcStr::from("Bob")),
SqlValue::Boolean(true),
]),
)
.unwrap();
db.insert_row(
"CUSTOMERS",
Row::new(vec![
SqlValue::Integer(3),
SqlValue::Varchar(arcstr::ArcStr::from("Charlie")),
SqlValue::Boolean(false),
]),
)
.unwrap();
db.insert_row(
"CUSTOMERS",
Row::new(vec![
SqlValue::Integer(4),
SqlValue::Varchar(arcstr::ArcStr::from("Diana")),
SqlValue::Boolean(true),
]),
)
.unwrap();
db.insert_row(
"ORDERS",
Row::new(vec![
SqlValue::Integer(101),
SqlValue::Integer(1), SqlValue::Integer(100),
]),
)
.unwrap();
db.insert_row(
"ORDERS",
Row::new(vec![
SqlValue::Integer(102),
SqlValue::Integer(1), SqlValue::Integer(200),
]),
)
.unwrap();
db.insert_row(
"ORDERS",
Row::new(vec![
SqlValue::Integer(103),
SqlValue::Integer(2), SqlValue::Integer(150),
]),
)
.unwrap();
db
}
#[test]
fn test_delete_with_exists_correlated() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE EXISTS (SELECT 1 FROM orders WHERE customer_id = customers.id);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 2, "Should delete 2 customers with orders");
let customers = db.get_table("CUSTOMERS").unwrap();
assert_eq!(customers.row_count(), 2);
let remaining_ids: Vec<i64> = customers
.scan_live()
.map(|(_, row)| if let SqlValue::Integer(id) = row.get(0).unwrap() { *id } else { 0 })
.collect();
assert!(remaining_ids.contains(&3)); assert!(remaining_ids.contains(&4)); } else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_with_not_exists_correlated() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE NOT EXISTS (SELECT 1 FROM orders WHERE customer_id = customers.id);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 2, "Should delete 2 customers without orders");
let customers = db.get_table("CUSTOMERS").unwrap();
assert_eq!(customers.row_count(), 2);
let remaining_ids: Vec<i64> = customers
.scan_live()
.map(|(_, row)| if let SqlValue::Integer(id) = row.get(0).unwrap() { *id } else { 0 })
.collect();
assert!(remaining_ids.contains(&1)); assert!(remaining_ids.contains(&2)); } else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_with_exists_empty_result() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE EXISTS (SELECT 1 FROM orders WHERE total > 10000);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 0, "Should delete 0 customers (EXISTS returns false)");
let customers = db.get_table("CUSTOMERS").unwrap();
assert_eq!(customers.row_count(), 4);
} else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_with_not_exists_empty_result() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE NOT EXISTS (SELECT 1 FROM orders WHERE total > 10000);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 4, "Should delete all 4 customers (NOT EXISTS returns true)");
let customers = db.get_table("CUSTOMERS").unwrap();
assert_eq!(customers.row_count(), 0);
} else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_with_exists_and_other_conditions() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE active = TRUE AND EXISTS (SELECT 1 FROM orders WHERE customer_id = customers.id);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 2, "Should delete 2 active customers with orders");
let customers = db.get_table("CUSTOMERS").unwrap();
assert_eq!(customers.row_count(), 2);
let remaining_ids: Vec<i64> = customers
.scan_live()
.map(|(_, row)| if let SqlValue::Integer(id) = row.get(0).unwrap() { *id } else { 0 })
.collect();
assert!(remaining_ids.contains(&3)); assert!(remaining_ids.contains(&4)); } else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_with_exists_uncorrelated() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE EXISTS (SELECT 1 FROM orders);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 4, "Should delete all customers (uncorrelated EXISTS is TRUE)");
let customers = db.get_table("CUSTOMERS").unwrap();
assert_eq!(customers.row_count(), 0);
} else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_with_exists_complex_subquery() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE EXISTS (SELECT 1 FROM orders WHERE customer_id = customers.id AND total > 150);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 1, "Should delete 1 customer with order > 150");
let customers = db.get_table("CUSTOMERS").unwrap();
assert_eq!(customers.row_count(), 3);
let remaining_ids: Vec<i64> = customers
.scan_live()
.map(|(_, row)| if let SqlValue::Integer(id) = row.get(0).unwrap() { *id } else { 0 })
.collect();
assert!(!remaining_ids.contains(&1)); assert!(remaining_ids.contains(&2)); assert!(remaining_ids.contains(&3)); assert!(remaining_ids.contains(&4)); } else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_with_nested_exists() {
let mut db = Database::new();
let users_schema = TableSchema::new(
"USERS".to_string(),
vec![
ColumnSchema::new("ID".to_string(), DataType::Integer, false),
ColumnSchema::new(
"NAME".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
);
db.create_table(users_schema).unwrap();
let posts_schema = TableSchema::new(
"POSTS".to_string(),
vec![
ColumnSchema::new("ID".to_string(), DataType::Integer, false),
ColumnSchema::new("USER_ID".to_string(), DataType::Integer, false),
],
);
db.create_table(posts_schema).unwrap();
let comments_schema = TableSchema::new(
"COMMENTS".to_string(),
vec![
ColumnSchema::new("ID".to_string(), DataType::Integer, false),
ColumnSchema::new("POST_ID".to_string(), DataType::Integer, false),
],
);
db.create_table(comments_schema).unwrap();
db.insert_row(
"USERS",
Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))]),
)
.unwrap();
db.insert_row(
"USERS",
Row::new(vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Bob"))]),
)
.unwrap();
db.insert_row("POSTS", Row::new(vec![SqlValue::Integer(101), SqlValue::Integer(1)])).unwrap();
db.insert_row("COMMENTS", Row::new(vec![SqlValue::Integer(1001), SqlValue::Integer(101)]))
.unwrap();
let sql = "DELETE FROM users WHERE EXISTS (SELECT 1 FROM posts WHERE user_id = users.id AND EXISTS (SELECT 1 FROM comments WHERE post_id = posts.id));";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 1, "Should delete 1 user with posts that have comments");
let users = db.get_table("USERS").unwrap();
assert_eq!(users.row_count(), 1);
let remaining_id =
if let SqlValue::Integer(id) = users.scan_live().next().unwrap().1.get(0).unwrap() {
*id
} else {
0
};
assert_eq!(remaining_id, 2); } else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_with_or_exists() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE id = 3 OR EXISTS (SELECT 1 FROM orders WHERE customer_id = customers.id);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 3, "Should delete 3 customers (Charlie + Alice + Bob)");
let customers = db.get_table("CUSTOMERS").unwrap();
assert_eq!(customers.row_count(), 1);
let remaining_id = if let SqlValue::Integer(id) =
customers.scan_live().next().unwrap().1.get(0).unwrap()
{
*id
} else {
0
};
assert_eq!(remaining_id, 4); } else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_exists_with_select_star() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE EXISTS (SELECT * FROM orders WHERE customer_id = customers.id);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 2, "Should delete 2 customers with orders");
} else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_delete_exists_with_select_multiple_columns() {
let mut db = setup_customers_orders_db();
let sql = "DELETE FROM customers WHERE EXISTS (SELECT id, total FROM orders WHERE customer_id = customers.id);";
let stmt = Parser::parse_sql(sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 2, "Should delete 2 customers with orders");
} else {
panic!("Expected DELETE statement");
}
}
#[test]
fn test_select_excludes_deleted_rows_after_delete() {
use vibesql_executor::SelectExecutor;
let mut db = setup_customers_orders_db();
let select_stmt = Parser::parse_sql("SELECT * FROM customers").unwrap();
let rows = if let vibesql_ast::Statement::Select(ref select) = select_stmt {
SelectExecutor::new(&db).execute(select).unwrap()
} else {
panic!("Expected SELECT statement");
};
assert_eq!(rows.len(), 4, "Should have 4 customers initially");
let delete_sql = "DELETE FROM customers WHERE EXISTS (SELECT 1 FROM orders WHERE customer_id = customers.id)";
let stmt = Parser::parse_sql(delete_sql).unwrap();
if let vibesql_ast::Statement::Delete(delete_stmt) = stmt {
let deleted = DeleteExecutor::execute(&delete_stmt, &mut db).unwrap();
assert_eq!(deleted, 2, "Should delete 2 customers");
}
let select_stmt = Parser::parse_sql("SELECT * FROM customers").unwrap();
let rows = if let vibesql_ast::Statement::Select(ref select) = select_stmt {
SelectExecutor::new(&db).execute(select).unwrap()
} else {
panic!("Expected SELECT statement");
};
assert_eq!(
rows.len(),
2,
"SELECT should return only 2 non-deleted customers (Charlie, Diana), got {}",
rows.len()
);
let ids: Vec<i64> =
rows.iter()
.filter_map(|row| {
if let SqlValue::Integer(id) = row.get(0).unwrap() {
Some(*id)
} else {
None
}
})
.collect();
assert!(ids.contains(&3), "Charlie (id=3) should be in results");
assert!(ids.contains(&4), "Diana (id=4) should be in results");
assert!(!ids.contains(&1), "Alice (id=1) was deleted, should NOT be in results");
assert!(!ids.contains(&2), "Bob (id=2) was deleted, should NOT be in results");
}