use vibesql_ast::{
AddColumnStmt, AlterColumnStmt, AlterTableStmt, ChangeColumnStmt, ColumnDef, DropColumnStmt,
ModifyColumnStmt,
};
use vibesql_catalog::{ColumnSchema, TableSchema};
use vibesql_executor::{AlterTableExecutor, InsertExecutor};
use vibesql_storage::Database;
use vibesql_types::{DataType, SqlValue};
fn setup_test_table(db: &mut Database) {
let schema = TableSchema::with_primary_key(
"test_table".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("value".to_string(), DataType::Integer, true),
],
vec!["id".to_string()],
);
db.create_table(schema).unwrap();
}
fn insert_row(db: &mut Database, id: i64, name: &str, value: i64) {
let stmt = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "test_table".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(value)),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
InsertExecutor::execute(db, &stmt).unwrap();
}
#[test]
fn test_add_column_invalidates_columnar_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
insert_row(&mut db, 2, "Gadget", 200);
let initial_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(initial_columnar.row_count(), 2);
assert_eq!(initial_columnar.column_count(), 3, "Should have 3 columns initially");
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1, "Should have done one conversion");
let add_col_stmt = AlterTableStmt::AddColumn(AddColumnStmt {
table_name: "test_table".to_string(),
column_def: ColumnDef {
name: "new_col".to_string(),
data_type: DataType::Integer,
nullable: true,
default_value: Some(Box::new(vibesql_ast::Expression::Literal(SqlValue::Integer(42)))),
constraints: vec![],
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
});
AlterTableExecutor::execute(&add_col_stmt, &mut db).unwrap();
let updated_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(updated_columnar.column_count(), 4, "Should have 4 columns after ADD COLUMN");
assert_eq!(updated_columnar.row_count(), 2, "Should still have 2 rows");
let new_col = updated_columnar.get_column("new_col").expect("New column should exist");
for i in 0..2 {
match new_col.get(i) {
SqlValue::Integer(v) => assert_eq!(v, 42, "Default value should be 42"),
other => panic!("Expected Integer, got {:?}", other),
}
}
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after ADD COLUMN (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_drop_column_invalidates_columnar_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
insert_row(&mut db, 2, "Gadget", 200);
let initial_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(initial_columnar.column_count(), 3, "Should have 3 columns initially");
assert!(initial_columnar.get_column("value").is_some(), "value column should exist");
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1);
let drop_col_stmt = AlterTableStmt::DropColumn(DropColumnStmt {
table_name: "test_table".to_string(),
column_name: "value".to_string(),
if_exists: false,
});
AlterTableExecutor::execute(&drop_col_stmt, &mut db).unwrap();
let updated_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(updated_columnar.column_count(), 2, "Should have 2 columns after DROP COLUMN");
assert!(updated_columnar.get_column("value").is_none(), "value column should no longer exist");
assert!(updated_columnar.get_column("id").is_some(), "id column should still exist");
assert!(updated_columnar.get_column("name").is_some(), "name column should still exist");
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after DROP COLUMN (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_modify_column_invalidates_columnar_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
insert_row(&mut db, 2, "Gadget", 200);
let initial_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(initial_columnar.row_count(), 2);
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1);
let modify_col_stmt = AlterTableStmt::ModifyColumn(ModifyColumnStmt {
table_name: "test_table".to_string(),
column_name: "value".to_string(),
new_column_def: ColumnDef {
name: "value".to_string(),
data_type: DataType::Bigint,
nullable: true,
default_value: None,
constraints: vec![],
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
});
AlterTableExecutor::execute(&modify_col_stmt, &mut db).unwrap();
let updated_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(updated_columnar.row_count(), 2, "Should still have 2 rows");
let value_col = updated_columnar.get_column("value").expect("value column should exist");
let values: Vec<i64> = (0..2)
.filter_map(|i| match value_col.get(i) {
SqlValue::Integer(v) | SqlValue::Bigint(v) => Some(v),
_ => None,
})
.collect();
assert!(values.contains(&100), "Value 100 should exist");
assert!(values.contains(&200), "Value 200 should exist");
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after MODIFY COLUMN (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_change_column_invalidates_columnar_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
insert_row(&mut db, 2, "Gadget", 200);
let initial_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert!(initial_columnar.get_column("value").is_some(), "value column should exist");
assert!(initial_columnar.get_column("amount").is_none(), "amount column should not exist yet");
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1);
let change_col_stmt = AlterTableStmt::ChangeColumn(ChangeColumnStmt {
table_name: "test_table".to_string(),
old_column_name: "value".to_string(),
new_column_def: ColumnDef {
name: "amount".to_string(),
data_type: DataType::Bigint,
nullable: true,
default_value: None,
constraints: vec![],
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
});
AlterTableExecutor::execute(&change_col_stmt, &mut db).unwrap();
let updated_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert!(
updated_columnar.get_column("value").is_none(),
"old 'value' column should no longer exist"
);
assert!(updated_columnar.get_column("amount").is_some(), "new 'amount' column should exist");
let amount_col = updated_columnar.get_column("amount").expect("amount column should exist");
let amounts: Vec<i64> = (0..2)
.filter_map(|i| match amount_col.get(i) {
SqlValue::Integer(v) | SqlValue::Bigint(v) => Some(v),
_ => None,
})
.collect();
assert!(amounts.contains(&100), "Value 100 should exist in renamed column");
assert!(amounts.contains(&200), "Value 200 should exist in renamed column");
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after CHANGE COLUMN (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_alter_column_set_not_null_invalidates_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
insert_row(&mut db, 2, "Gadget", 200);
let _ = db.get_columnar("test_table").unwrap().expect("Table should exist");
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1);
let alter_stmt = AlterTableStmt::AlterColumn(AlterColumnStmt::SetNotNull {
table_name: "test_table".to_string(),
column_name: "value".to_string(),
});
AlterTableExecutor::execute(&alter_stmt, &mut db).unwrap();
let updated_columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(updated_columnar.row_count(), 2);
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after ALTER COLUMN SET NOT NULL (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_alter_column_drop_not_null_invalidates_cache() {
let mut db = Database::new();
let schema = TableSchema::with_primary_key(
"test_table".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new("required_val".to_string(), DataType::Integer, false), ],
vec!["id".to_string()],
);
db.create_table(schema).unwrap();
let stmt = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "test_table".to_string(),
columns: vec![],
source: vibesql_ast::InsertSource::Values(vec![vec![
vibesql_ast::Expression::Literal(SqlValue::Integer(1)),
vibesql_ast::Expression::Literal(SqlValue::Integer(100)),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
InsertExecutor::execute(&mut db, &stmt).unwrap();
let _ = db.get_columnar("test_table").unwrap().expect("Table should exist");
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1);
let alter_stmt = AlterTableStmt::AlterColumn(AlterColumnStmt::DropNotNull {
table_name: "test_table".to_string(),
column_name: "required_val".to_string(),
});
AlterTableExecutor::execute(&alter_stmt, &mut db).unwrap();
let _ = db.get_columnar("test_table").unwrap().expect("Table should exist");
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after ALTER COLUMN DROP NOT NULL (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_alter_column_set_default_invalidates_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
let _ = db.get_columnar("test_table").unwrap().expect("Table should exist");
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1);
let alter_stmt = AlterTableStmt::AlterColumn(AlterColumnStmt::SetDefault {
table_name: "test_table".to_string(),
column_name: "value".to_string(),
default: vibesql_ast::Expression::Literal(SqlValue::Integer(999)),
});
AlterTableExecutor::execute(&alter_stmt, &mut db).unwrap();
let _ = db.get_columnar("test_table").unwrap().expect("Table should exist");
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after ALTER COLUMN SET DEFAULT (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_alter_column_drop_default_invalidates_cache() {
let mut db = Database::new();
let mut col_schema = ColumnSchema::new("value".to_string(), DataType::Integer, true);
col_schema.set_default(vibesql_ast::Expression::Literal(SqlValue::Integer(0)));
let schema = TableSchema::with_primary_key(
"test_table".to_string(),
vec![ColumnSchema::new("id".to_string(), DataType::Integer, false), col_schema],
vec!["id".to_string()],
);
db.create_table(schema).unwrap();
let stmt = vibesql_ast::InsertStmt {
with_clause: None,
schema_name: None,
schema_quoted: false,
table_quoted: false,
table_name: "test_table".to_string(),
columns: vec![],
source: vibesql_ast::InsertSource::Values(vec![vec![
vibesql_ast::Expression::Literal(SqlValue::Integer(1)),
vibesql_ast::Expression::Literal(SqlValue::Integer(100)),
]]),
conflict_clause: None,
on_conflict: None,
on_duplicate_key_update: None,
returning: None,
};
InsertExecutor::execute(&mut db, &stmt).unwrap();
let _ = db.get_columnar("test_table").unwrap().expect("Table should exist");
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1);
let alter_stmt = AlterTableStmt::AlterColumn(AlterColumnStmt::DropDefault {
table_name: "test_table".to_string(),
column_name: "value".to_string(),
});
AlterTableExecutor::execute(&alter_stmt, &mut db).unwrap();
let _ = db.get_columnar("test_table").unwrap().expect("Table should exist");
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 2,
"Should have re-converted after ALTER COLUMN DROP DEFAULT (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_multiple_alter_operations_invalidate_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
insert_row(&mut db, 2, "Gadget", 200);
let _ = db.get_columnar("test_table").unwrap();
let add_stmt = AlterTableStmt::AddColumn(AddColumnStmt {
table_name: "test_table".to_string(),
column_def: ColumnDef {
name: "extra".to_string(),
data_type: DataType::Integer,
nullable: true,
default_value: Some(Box::new(vibesql_ast::Expression::Literal(SqlValue::Integer(0)))),
constraints: vec![],
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
});
AlterTableExecutor::execute(&add_stmt, &mut db).unwrap();
let columnar1 = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(columnar1.column_count(), 4);
let drop_stmt = AlterTableStmt::DropColumn(DropColumnStmt {
table_name: "test_table".to_string(),
column_name: "value".to_string(),
if_exists: false,
});
AlterTableExecutor::execute(&drop_stmt, &mut db).unwrap();
let columnar2 = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(columnar2.column_count(), 3);
assert!(columnar2.get_column("value").is_none());
assert!(columnar2.get_column("extra").is_some());
let final_stats = db.columnar_cache_stats();
assert!(
final_stats.conversions >= 3,
"Should have multiple conversions after multiple ALTER operations (conversions: {})",
final_stats.conversions
);
}
#[test]
fn test_rename_table_invalidates_columnar_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
insert_row(&mut db, 2, "Gadget", 200);
let _ = db.get_columnar("test_table").unwrap().expect("Table should exist");
let initial_stats = db.columnar_cache_stats();
assert_eq!(initial_stats.conversions, 1);
let rename_stmt = vibesql_ast::AlterTableStmt::RenameTable(vibesql_ast::RenameTableStmt {
table_name: "test_table".to_string(),
new_table_name: "renamed_table".to_string(),
});
AlterTableExecutor::execute(&rename_stmt, &mut db).unwrap();
assert!(db.get_columnar("test_table").unwrap().is_none());
let renamed_columnar =
db.get_columnar("renamed_table").unwrap().expect("Renamed table should exist");
assert_eq!(renamed_columnar.row_count(), 2);
assert_eq!(renamed_columnar.column_count(), 3);
let value_col = renamed_columnar.get_column("value").expect("value column should exist");
let values: Vec<i64> = (0..2)
.filter_map(|i| match value_col.get(i) {
SqlValue::Integer(v) => Some(v),
_ => None,
})
.collect();
assert!(values.contains(&100), "Value 100 should exist after rename");
assert!(values.contains(&200), "Value 200 should exist after rename");
}
#[test]
fn test_alter_invalidates_prewarmed_cache() {
let mut db = Database::new();
setup_test_table(&mut db);
insert_row(&mut db, 1, "Widget", 100);
let warmed = db.pre_warm_columnar_cache(&["test_table"]).unwrap();
assert_eq!(warmed, 1);
let _ = db.get_columnar("test_table").unwrap();
let stats_after_get = db.columnar_cache_stats();
assert_eq!(stats_after_get.hits, 1, "Second get should be a cache hit");
let add_stmt = AlterTableStmt::AddColumn(AddColumnStmt {
table_name: "test_table".to_string(),
column_def: ColumnDef {
name: "new_col".to_string(),
data_type: DataType::Integer,
nullable: true,
default_value: None,
constraints: vec![],
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
});
AlterTableExecutor::execute(&add_stmt, &mut db).unwrap();
let columnar = db.get_columnar("test_table").unwrap().expect("Table should exist");
assert_eq!(columnar.column_count(), 4, "Should have 4 columns after ADD");
assert!(columnar.get_column("new_col").is_some(), "New column should be visible");
}