use vibesql_ast::DropTableStmt;
use vibesql_storage::Database;
use crate::{errors::ExecutorError, privilege_checker::PrivilegeChecker};
pub struct DropTableExecutor;
impl DropTableExecutor {
pub fn execute(stmt: &DropTableStmt, database: &mut Database) -> Result<String, ExecutorError> {
if crate::sqlite_schema::is_sqlite_schema_table(&stmt.table_name) {
return Err(ExecutorError::SqliteCompatError(
"table sqlite_master may not be dropped".to_string(),
));
}
let table_exists = database.catalog.table_exists(&stmt.table_name);
if stmt.if_exists && !table_exists {
return Ok(format!("Table '{}' does not exist (IF EXISTS specified)", stmt.table_name));
}
if !table_exists {
return Err(ExecutorError::TableNotFound(stmt.table_name.clone()));
}
PrivilegeChecker::check_drop(database, &stmt.table_name)?;
let dropped_indexes = database.catalog.drop_table_indexes(&stmt.table_name);
let index_count = dropped_indexes.len();
for index in &dropped_indexes {
let qualified = format!("{}.{}", index.schema(), index.name);
let _ = database.drop_index(&qualified);
let _ = database.drop_spatial_index(&qualified);
}
let dropped_triggers = database.catalog.drop_table_triggers(&stmt.table_name);
let trigger_count = dropped_triggers.len();
database
.drop_table(&stmt.table_name)
.map_err(|e| ExecutorError::StorageError(e.to_string()))?;
match (index_count, trigger_count) {
(0, 0) => Ok(format!("Table '{}' dropped successfully", stmt.table_name)),
(i, 0) => Ok(format!(
"Table '{}' and {} associated index(es) dropped successfully",
stmt.table_name, i
)),
(0, t) => Ok(format!(
"Table '{}' and {} associated trigger(s) dropped successfully",
stmt.table_name, t
)),
(i, t) => Ok(format!(
"Table '{}', {} associated index(es), and {} associated trigger(s) dropped successfully",
stmt.table_name, i, t
)),
}
}
}
#[cfg(test)]
mod tests {
use vibesql_ast::{ColumnDef, CreateTableStmt};
use vibesql_types::DataType;
use super::*;
use crate::CreateTableExecutor;
#[test]
fn test_drop_existing_table() {
let mut db = Database::new();
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: "users".to_string(),
columns: vec![ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
}],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
assert!(db.catalog.table_exists("users"));
let drop_stmt =
DropTableStmt { table_name: "users".to_string(), if_exists: false, quoted: false };
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Table 'users' dropped successfully");
assert!(!db.catalog.table_exists("users"));
assert!(db.get_table("users").is_none());
}
#[test]
fn test_drop_nonexistent_table_without_if_exists() {
let mut db = Database::new();
let drop_stmt = DropTableStmt {
table_name: "nonexistent".to_string(),
if_exists: false,
quoted: false,
};
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_err());
assert!(matches!(result, Err(ExecutorError::TableNotFound(_))));
}
#[test]
fn test_drop_nonexistent_table_with_if_exists() {
let mut db = Database::new();
let drop_stmt =
DropTableStmt { table_name: "nonexistent".to_string(), if_exists: true, quoted: false };
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Table 'nonexistent' does not exist (IF EXISTS specified)");
}
#[test]
fn test_drop_existing_table_with_if_exists() {
let mut db = Database::new();
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: "products".to_string(),
columns: vec![ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
}],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
let drop_stmt =
DropTableStmt { table_name: "products".to_string(), if_exists: true, quoted: false };
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Table 'products' dropped successfully");
assert!(!db.catalog.table_exists("products"));
}
#[test]
fn test_drop_table_with_data() {
let mut db = Database::new();
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: "customers".to_string(),
columns: vec![
ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
ColumnDef {
name: "name".to_string(),
data_type: DataType::Varchar { max_length: Some(100) },
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
use vibesql_storage::Row;
use vibesql_types::SqlValue;
let row =
Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))]);
db.insert_row("customers", row).unwrap();
assert_eq!(db.get_table("customers").unwrap().row_count(), 1);
let drop_stmt =
DropTableStmt { table_name: "customers".to_string(), if_exists: false, quoted: false };
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_ok());
assert!(!db.catalog.table_exists("customers"));
assert!(db.get_table("customers").is_none());
}
#[test]
fn test_drop_and_recreate_table() {
let mut db = Database::new();
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: "temp".to_string(),
columns: vec![ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
}],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
let drop_stmt =
DropTableStmt { table_name: "temp".to_string(), if_exists: false, quoted: false };
DropTableExecutor::execute(&drop_stmt, &mut db).unwrap();
let result = CreateTableExecutor::execute(&create_stmt, &mut db);
assert!(result.is_ok());
assert!(db.catalog.table_exists("temp"));
}
#[test]
fn test_drop_multiple_tables() {
let mut db = Database::new();
for name in &["table1", "table2", "table3"] {
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: name.to_string(),
columns: vec![ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
}],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
}
assert_eq!(db.list_tables().len(), 3);
for name in &["table1", "table2", "table3"] {
let drop_stmt =
DropTableStmt { table_name: name.to_string(), if_exists: false, quoted: false };
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_ok());
}
assert_eq!(db.list_tables().len(), 0);
}
#[test]
fn test_drop_table_case_sensitivity() {
let mut db = Database::new();
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: "MyTable".to_string(),
columns: vec![ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
}],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
let drop_stmt =
DropTableStmt { table_name: "MyTable".to_string(), if_exists: false, quoted: false };
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_ok());
}
#[test]
fn test_drop_table_cascades_to_indexes() {
use vibesql_ast::{CreateIndexStmt, IndexColumn, OrderDirection};
use crate::CreateIndexExecutor;
let mut db = Database::new();
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: "users".to_string(),
columns: vec![
ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
ColumnDef {
name: "email".to_string(),
data_type: DataType::Varchar { max_length: Some(255) },
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
let index1_stmt = CreateIndexStmt {
index_name: "idx_users_email".to_string(),
if_not_exists: false,
table_name: "users".to_string(),
index_type: vibesql_ast::IndexType::BTree { unique: false },
columns: vec![IndexColumn::Column {
column_name: "email".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
where_clause: None,
};
CreateIndexExecutor::execute(&index1_stmt, &mut db).unwrap();
let index2_stmt = CreateIndexStmt {
index_name: "idx_users_id".to_string(),
if_not_exists: false,
table_name: "users".to_string(),
index_type: vibesql_ast::IndexType::BTree { unique: false },
columns: vec![IndexColumn::Column {
column_name: "id".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
where_clause: None,
};
CreateIndexExecutor::execute(&index2_stmt, &mut db).unwrap();
assert!(db.index_exists("idx_users_email"));
assert!(db.index_exists("idx_users_id"));
let drop_stmt =
DropTableStmt { table_name: "users".to_string(), if_exists: false, quoted: false };
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_ok());
assert!(!db.catalog.table_exists("users"));
assert!(!db.index_exists("idx_users_email"));
assert!(!db.index_exists("idx_users_id"));
}
#[test]
fn test_drop_and_recreate_table_with_same_index_names() {
use vibesql_ast::{CreateIndexStmt, IndexColumn, OrderDirection};
use crate::CreateIndexExecutor;
let mut db = Database::new();
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: "products".to_string(),
columns: vec![
ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
ColumnDef {
name: "name".to_string(),
data_type: DataType::Varchar { max_length: Some(100) },
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
},
],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
let index_stmt = CreateIndexStmt {
index_name: "idx_products_name".to_string(),
if_not_exists: false,
table_name: "products".to_string(),
index_type: vibesql_ast::IndexType::BTree { unique: false },
columns: vec![IndexColumn::Column {
column_name: "name".to_string(),
prefix_length: None,
direction: OrderDirection::Asc,
}],
where_clause: None,
};
CreateIndexExecutor::execute(&index_stmt, &mut db).unwrap();
assert!(db.index_exists("idx_products_name"));
let drop_stmt =
DropTableStmt { table_name: "products".to_string(), if_exists: false, quoted: false };
DropTableExecutor::execute(&drop_stmt, &mut db).unwrap();
assert!(!db.catalog.table_exists("products"));
assert!(!db.index_exists("idx_products_name"));
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
let result = CreateIndexExecutor::execute(&index_stmt, &mut db);
assert!(result.is_ok(), "Should be able to recreate index with same name after table drop");
assert!(db.index_exists("idx_products_name"));
}
#[test]
fn test_drop_table_without_indexes() {
let mut db = Database::new();
let create_stmt = CreateTableStmt {
temporary: false,
if_not_exists: false,
table_name: "simple_table".to_string(),
columns: vec![ColumnDef {
name: "id".to_string(),
data_type: DataType::Integer,
nullable: false,
constraints: vec![],
default_value: None,
comment: None,
generated_expr: None,
is_exact_integer_type: false,
}],
table_constraints: vec![],
table_options: vec![],
quoted: false,
name_source: None,
as_query: None,
without_rowid: false,
};
CreateTableExecutor::execute(&create_stmt, &mut db).unwrap();
let drop_stmt = DropTableStmt {
table_name: "simple_table".to_string(),
if_exists: false,
quoted: false,
};
let result = DropTableExecutor::execute(&drop_stmt, &mut db);
assert!(result.is_ok(), "Dropping table without indexes should still work");
assert!(!db.catalog.table_exists("simple_table"));
}
}