use vibesql_ast::{DeleteStmt, TriggerAction, TriggerEvent, TriggerGranularity, TriggerTiming};
use vibesql_catalog::{
ColumnSchema, ForeignKeyConstraint, ReferentialAction, TableSchema, TriggerDefinition,
};
use vibesql_storage::{Database, Row};
use vibesql_types::{DataType, SqlValue};
use crate::DeleteExecutor;
#[test]
fn test_truncate_optimization_basic() {
let mut db = Database::new();
let schema = TableSchema::new(
"large_table".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"data".to_string(),
DataType::Varchar { max_length: Some(100) },
false,
),
],
);
db.create_table(schema).unwrap();
for i in 0..1000 {
db.insert_row(
"large_table",
Row::new(vec![
SqlValue::Integer(i),
SqlValue::Varchar(arcstr::ArcStr::from(format!("data_{}", i))),
]),
)
.unwrap();
}
assert_eq!(db.get_table("large_table").unwrap().row_count(), 1000);
let stmt = DeleteStmt {
with_clause: None,
only: false,
table_name: "large_table".to_string(),
quoted: false,
where_clause: None,
order_by: None,
limit: None,
offset: None,
returning: None,
};
let deleted = DeleteExecutor::execute(&stmt, &mut db).unwrap();
assert_eq!(deleted, 1000);
let table = db.get_table("large_table").unwrap();
assert_eq!(table.row_count(), 0);
}
#[test]
fn test_truncate_blocked_by_fk_reference() {
let mut db = Database::new();
db.set_foreign_keys_enabled(true);
let parent_schema = TableSchema::with_primary_key(
"parent".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(
"child".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new("parent_id".to_string(), DataType::Integer, false),
],
vec![ForeignKeyConstraint {
name: Some("fk_child_parent".to_string()),
column_names: vec!["parent_id".to_string()],
column_indices: vec![1],
parent_table: "parent".to_string(),
parent_column_names: vec!["id".to_string()],
parent_column_indices: vec![0],
on_delete: ReferentialAction::NoAction,
on_update: ReferentialAction::NoAction,
is_deferrable: false,
initially_deferred: false,
}],
);
db.create_table(child_schema).unwrap();
db.insert_row(
"parent",
Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Parent 1"))]),
)
.unwrap();
db.insert_row(
"parent",
Row::new(vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Parent 2"))]),
)
.unwrap();
db.insert_row("child", Row::new(vec![SqlValue::Integer(1), SqlValue::Integer(1)])).unwrap();
let stmt = DeleteStmt {
with_clause: None,
only: false,
table_name: "parent".to_string(),
quoted: false,
where_clause: None,
order_by: None,
limit: None,
offset: None,
returning: None,
};
let result = DeleteExecutor::execute(&stmt, &mut db);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("FOREIGN KEY constraint violation"));
assert_eq!(db.get_table("parent").unwrap().row_count(), 2);
}
#[test]
fn test_truncate_allowed_when_no_fk_references() {
let mut db = Database::new();
let parent_schema = TableSchema::with_primary_key(
"parent".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(
"child".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new("parent_id".to_string(), DataType::Integer, false),
],
vec![ForeignKeyConstraint {
name: Some("fk_child_parent".to_string()),
column_names: vec!["parent_id".to_string()],
column_indices: vec![1],
parent_table: "parent".to_string(),
parent_column_names: vec!["id".to_string()],
parent_column_indices: vec![0],
on_delete: ReferentialAction::NoAction,
on_update: ReferentialAction::NoAction,
is_deferrable: false,
initially_deferred: false,
}],
);
db.create_table(child_schema).unwrap();
for i in 1..=100 {
db.insert_row(
"parent",
Row::new(vec![
SqlValue::Integer(i),
SqlValue::Varchar(arcstr::ArcStr::from(format!("Parent {}", i))),
]),
)
.unwrap();
}
let stmt = DeleteStmt {
with_clause: None,
only: false,
table_name: "parent".to_string(),
quoted: false,
where_clause: None,
order_by: None,
limit: None,
offset: None,
returning: None,
};
let deleted = DeleteExecutor::execute(&stmt, &mut db).unwrap();
assert_eq!(deleted, 100);
assert_eq!(db.get_table("parent").unwrap().row_count(), 0);
}
#[test]
fn test_truncate_blocked_by_delete_trigger() {
let mut db = Database::new();
let schema = TableSchema::new(
"test_table".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"data".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
);
db.create_table(schema).unwrap();
let trigger = TriggerDefinition::new(
"audit_delete".to_string(),
TriggerTiming::After,
TriggerEvent::Delete,
"test_table".to_string(),
TriggerGranularity::Row,
None,
TriggerAction::RawSql("-- audit logic here".to_string()),
);
db.catalog.create_trigger(trigger).unwrap();
for i in 0..10 {
db.insert_row(
"test_table",
Row::new(vec![
SqlValue::Integer(i),
SqlValue::Varchar(arcstr::ArcStr::from(format!("data_{}", i))),
]),
)
.unwrap();
}
let stmt = DeleteStmt {
with_clause: None,
only: false,
table_name: "test_table".to_string(),
quoted: false,
where_clause: None,
order_by: None,
limit: None,
offset: None,
returning: None,
};
let deleted = DeleteExecutor::execute(&stmt, &mut db).unwrap();
assert_eq!(deleted, 10);
assert_eq!(db.get_table("test_table").unwrap().row_count(), 0);
}
#[test]
fn test_truncate_allowed_with_insert_trigger() {
let mut db = Database::new();
let schema = TableSchema::new(
"test_table".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"data".to_string(),
DataType::Varchar { max_length: Some(50) },
false,
),
],
);
db.create_table(schema).unwrap();
let trigger = TriggerDefinition::new(
"audit_insert".to_string(),
TriggerTiming::After,
TriggerEvent::Insert,
"test_table".to_string(),
TriggerGranularity::Row,
None,
TriggerAction::RawSql("-- audit logic here".to_string()),
);
db.catalog.create_trigger(trigger).unwrap();
for i in 0..100 {
db.insert_row(
"test_table",
Row::new(vec![
SqlValue::Integer(i),
SqlValue::Varchar(arcstr::ArcStr::from(format!("data_{}", i))),
]),
)
.unwrap();
}
let stmt = DeleteStmt {
with_clause: None,
only: false,
table_name: "test_table".to_string(),
quoted: false,
where_clause: None,
order_by: None,
limit: None,
offset: None,
returning: None,
};
let deleted = DeleteExecutor::execute(&stmt, &mut db).unwrap();
assert_eq!(deleted, 100);
assert_eq!(db.get_table("test_table").unwrap().row_count(), 0);
}
#[test]
fn test_truncate_performance() {
let mut db = Database::new();
let schema = TableSchema::new(
"large_table".to_string(),
vec![
ColumnSchema::new("id".to_string(), DataType::Integer, false),
ColumnSchema::new(
"data".to_string(),
DataType::Varchar { max_length: Some(100) },
false,
),
],
);
db.create_table(schema).unwrap();
for i in 0..10_000 {
db.insert_row(
"large_table",
Row::new(vec![
SqlValue::Integer(i),
SqlValue::Varchar(arcstr::ArcStr::from(format!("data_{}", i))),
]),
)
.unwrap();
}
let stmt = DeleteStmt {
with_clause: None,
only: false,
table_name: "large_table".to_string(),
quoted: false,
where_clause: None,
order_by: None,
limit: None,
offset: None,
returning: None,
};
let start = std::time::Instant::now();
let deleted = DeleteExecutor::execute(&stmt, &mut db).unwrap();
let duration = start.elapsed();
assert_eq!(deleted, 10_000);
assert_eq!(db.get_table("large_table").unwrap().row_count(), 0);
println!("Deleted 10,000 rows in {:?}", duration);
}