use vibesql_ast::{TruncateCascadeOption, TruncateTableStmt};
use vibesql_catalog::{ColumnSchema, ForeignKeyConstraint, ReferentialAction, TableSchema};
use vibesql_executor::{InsertExecutor, TruncateTableExecutor};
use vibesql_storage::Database;
use vibesql_types::{DataType, SqlValue};
fn setup_products_table(db: &mut Database) {
let schema = TableSchema::with_primary_key(
"products".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new("name".to_string(), DataType::Varchar { max_length: Some(50) }, true),
ColumnSchema::new("price".to_string(), DataType::Integer, true),
],
vec!["id".to_string()], );
db.create_table(schema).unwrap();
}
fn insert_product(db: &mut Database, id: i64, name: &str, price: i64) {
let stmt = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "products".to_string(),
columns: vec![],
source: vibesql_ast::InsertSource::Values(vec![vec![
vibesql_ast::Expression::Literal(SqlValue::Integer(id)),
vibesql_ast::Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from(name))),
vibesql_ast::Expression::Literal(SqlValue::Integer(price)),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
InsertExecutor::execute(db, &stmt).unwrap();
}
#[test]
fn test_truncate_invalidates_columnar_cache() {
let mut db = Database::new();
setup_products_table(&mut db);
insert_product(&mut db, 1, "Widget", 100);
insert_product(&mut db, 2, "Gadget", 200);
insert_product(&mut db, 3, "Gizmo", 300);
let initial_columnar = db.get_columnar("products").unwrap().expect("Table should exist");
assert_eq!(initial_columnar.row_count(), 3);
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1, "Should have done one conversion");
let price_col = initial_columnar.get_column("price").expect("Column should exist");
let prices: Vec<i64> = (0..3)
.map(|i| match price_col.get(i) {
SqlValue::Integer(v) => v,
other => panic!("Expected Integer, got {:?}", other),
})
.collect();
assert_eq!(prices, vec![100, 200, 300], "Initial prices should be 100, 200, 300");
let stmt = TruncateTableStmt {
table_names: vec!["products".to_string()],
if_exists: false,
cascade: None,
};
let deleted = TruncateTableExecutor::execute(&stmt, &mut db).unwrap();
assert_eq!(deleted, 3, "Should have truncated 3 rows");
let updated_columnar = db.get_columnar("products").unwrap().expect("Table should exist");
assert_eq!(
updated_columnar.row_count(),
0,
"Columnar cache should return 0 rows after TRUNCATE, not stale data"
);
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after TRUNCATE (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_truncate_invalidates_prewarmed_cache() {
let mut db = Database::new();
setup_products_table(&mut db);
insert_product(&mut db, 1, "Item A", 50);
insert_product(&mut db, 2, "Item B", 75);
let warmed = db.pre_warm_columnar_cache(&["products"]).unwrap();
assert_eq!(warmed, 1, "Should warm 1 table");
let initial_columnar = db.get_columnar("products").unwrap().expect("Table should exist");
assert_eq!(initial_columnar.row_count(), 2);
let stats_after_get = db.columnar_cache_stats();
assert_eq!(stats_after_get.hits, 1, "Second get should be a cache hit");
let stmt = TruncateTableStmt {
table_names: vec!["products".to_string()],
if_exists: false,
cascade: None,
};
TruncateTableExecutor::execute(&stmt, &mut db).unwrap();
let updated_columnar = db.get_columnar("products").unwrap().expect("Table should exist");
assert_eq!(
updated_columnar.row_count(),
0,
"Prewarmed cache should be invalidated after TRUNCATE"
);
}
#[test]
fn test_truncate_then_insert_cache_coherence() {
let mut db = Database::new();
setup_products_table(&mut db);
insert_product(&mut db, 1, "Old Item", 100);
let _ = db.get_columnar("products").unwrap();
let stmt = TruncateTableStmt {
table_names: vec!["products".to_string()],
if_exists: false,
cascade: None,
};
TruncateTableExecutor::execute(&stmt, &mut db).unwrap();
insert_product(&mut db, 2, "New Item", 999);
let columnar = db.get_columnar("products").unwrap().expect("Table should exist");
assert_eq!(columnar.row_count(), 1, "Should have 1 new row");
let price_col = columnar.get_column("price").expect("Column should exist");
let price = match price_col.get(0) {
SqlValue::Integer(v) => v,
other => panic!("Expected Integer, got {:?}", other),
};
assert_eq!(price, 999, "New price 999 should be visible");
}
#[test]
fn test_truncate_empty_table_cache() {
let mut db = Database::new();
setup_products_table(&mut db);
let initial_columnar = db.get_columnar("products").unwrap().expect("Table should exist");
assert_eq!(initial_columnar.row_count(), 0);
let stmt = TruncateTableStmt {
table_names: vec!["products".to_string()],
if_exists: false,
cascade: None,
};
let deleted = TruncateTableExecutor::execute(&stmt, &mut db).unwrap();
assert_eq!(deleted, 0, "Truncating empty table should delete 0 rows");
let updated_columnar = db.get_columnar("products").unwrap().expect("Table should exist");
assert_eq!(updated_columnar.row_count(), 0, "Should still have 0 rows");
}
#[test]
fn test_truncate_cascade_invalidates_columnar_cache() {
let mut db = Database::new();
let parent_schema = TableSchema::with_primary_key(
"categories".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"name".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
vec!["id".to_string()],
);
db.create_table(parent_schema).unwrap();
let child_schema = TableSchema::with_foreign_keys(
"items".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new("category_id".to_string(), DataType::Integer, false),
ColumnSchema::new("price".to_string(), DataType::Integer, true),
],
vec![ForeignKeyConstraint {
name: Some("fk_items_categories".to_string()),
column_names: vec!["category_id".to_string()],
column_indices: vec![1],
parent_table: "categories".to_string(),
parent_column_names: vec!["id".to_string()],
parent_column_indices: vec![0],
on_delete: ReferentialAction::Cascade,
on_update: ReferentialAction::NoAction,
is_deferrable: false,
initially_deferred: false,
}],
);
db.create_table(child_schema).unwrap();
let parent_stmt = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "categories".to_string(),
columns: vec![],
source: vibesql_ast::InsertSource::Values(vec![vec![
vibesql_ast::Expression::Literal(SqlValue::Integer(1)),
vibesql_ast::Expression::Literal(SqlValue::Varchar(arcstr::ArcStr::from(
"Electronics",
))),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
InsertExecutor::execute(&mut db, &parent_stmt).unwrap();
let child_stmt1 = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "items".to_string(),
columns: vec![],
source: vibesql_ast::InsertSource::Values(vec![vec![
vibesql_ast::Expression::Literal(SqlValue::Integer(1)),
vibesql_ast::Expression::Literal(SqlValue::Integer(1)),
vibesql_ast::Expression::Literal(SqlValue::Integer(500)),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
InsertExecutor::execute(&mut db, &child_stmt1).unwrap();
let child_stmt2 = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "items".to_string(),
columns: vec![],
source: vibesql_ast::InsertSource::Values(vec![vec![
vibesql_ast::Expression::Literal(SqlValue::Integer(2)),
vibesql_ast::Expression::Literal(SqlValue::Integer(1)),
vibesql_ast::Expression::Literal(SqlValue::Integer(300)),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
InsertExecutor::execute(&mut db, &child_stmt2).unwrap();
let parent_columnar = db.get_columnar("categories").unwrap().expect("Table should exist");
assert_eq!(parent_columnar.row_count(), 1);
let child_columnar = db.get_columnar("items").unwrap().expect("Table should exist");
assert_eq!(child_columnar.row_count(), 2);
let stmt = TruncateTableStmt {
table_names: vec!["categories".to_string()],
if_exists: false,
cascade: Some(TruncateCascadeOption::Cascade),
};
TruncateTableExecutor::execute(&stmt, &mut db).unwrap();
let parent_after = db.get_columnar("categories").unwrap().expect("Table should exist");
assert_eq!(
parent_after.row_count(),
0,
"Parent columnar cache should be empty after CASCADE TRUNCATE"
);
let child_after = db.get_columnar("items").unwrap().expect("Table should exist");
assert_eq!(
child_after.row_count(),
0,
"Child columnar cache should be empty after CASCADE TRUNCATE"
);
}