use alopex_core::kv::KVStore;
use crate::ast::ddl::IndexMethod;
use crate::catalog::{Catalog, IndexMetadata};
use crate::executor::hnsw_bridge::HnswBridge;
use crate::executor::{ExecutionResult, ExecutorError, Result};
use crate::storage::{KeyEncoder, SqlTxn};
use super::persistence::{delete_index, delete_table};
pub fn execute_drop_table<'txn, S: KVStore + 'txn, C: Catalog + ?Sized>(
txn: &mut impl SqlTxn<'txn, S>,
catalog: &mut C,
table_name: &str,
if_exists: bool,
) -> Result<ExecutionResult> {
let table_meta = match catalog.get_table(table_name) {
Some(table) => table.clone(),
None => {
return if if_exists {
Ok(ExecutionResult::Success)
} else {
Err(ExecutorError::TableNotFound(table_name.to_string()))
};
}
};
if table_meta.catalog_name != "default" || table_meta.namespace_name != "default" {
return if if_exists {
Ok(ExecutionResult::Success)
} else {
Err(ExecutorError::TableNotFound(table_name.to_string()))
};
}
let indexes: Vec<IndexMetadata> = catalog
.get_indexes_for_table(table_name)
.into_iter()
.cloned()
.collect();
for index in &indexes {
if matches!(index.method, Some(IndexMethod::Hnsw)) {
HnswBridge::drop_index(txn, index, false)?;
} else {
let prefix = KeyEncoder::index_prefix(index.index_id);
txn.delete_prefix(&prefix)?;
}
}
let table_prefix = KeyEncoder::table_prefix(table_meta.table_id);
txn.delete_prefix(&table_prefix)?;
let seq_key = KeyEncoder::sequence_key(table_meta.table_id);
txn.delete_prefix(&seq_key)?;
catalog.drop_table(table_name)?;
delete_table(txn.inner_mut(), &table_meta)?;
for index in &indexes {
delete_index(txn.inner_mut(), index)?;
}
Ok(ExecutionResult::Success)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TableMetadata;
use crate::catalog::{ColumnMetadata, MemoryCatalog};
use crate::executor::ddl::create_table::execute_create_table;
use crate::planner::types::ResolvedType;
use crate::storage::{SqlValue, TxnBridge};
use alopex_core::kv::memory::MemoryKV;
use std::sync::Arc;
fn setup() -> (TxnBridge<MemoryKV>, MemoryCatalog, TableMetadata) {
let bridge = TxnBridge::new(Arc::new(MemoryKV::new()));
let mut catalog = MemoryCatalog::new();
let table = TableMetadata::new(
"users",
vec![
ColumnMetadata::new("id", ResolvedType::Integer).with_primary_key(true),
ColumnMetadata::new("name", ResolvedType::Text),
],
)
.with_primary_key(vec!["id".into()]);
let mut txn = bridge.begin_write().unwrap();
execute_create_table(&mut txn, &mut catalog, table.clone(), vec![], false).unwrap();
txn.commit().unwrap();
let stored = catalog.get_table("users").unwrap().clone();
(bridge, catalog, stored)
}
#[test]
fn drop_table_removes_rows_and_indexes() {
let (bridge, mut catalog, table_meta) = setup();
let pk_index = catalog.get_index("__pk_users").expect("pk index").clone();
let row = vec![SqlValue::Integer(1), SqlValue::Text("alice".into())];
{
let mut txn = bridge.begin_write().unwrap();
let mut table = txn.table_storage(&table_meta);
table.insert(1, &row).unwrap();
let mut index = txn.index_storage(
pk_index.index_id,
pk_index.unique,
pk_index.column_indices.clone(),
);
index.insert(&row, 1).unwrap();
txn.commit().unwrap();
}
let mut txn = bridge.begin_write().unwrap();
let result = execute_drop_table(&mut txn, &mut catalog, "users", false);
assert!(matches!(result, Ok(ExecutionResult::Success)));
txn.commit().unwrap();
assert!(!catalog.table_exists("users"));
assert!(!catalog.index_exists("__pk_users"));
let mut txn = bridge.begin_write().unwrap();
let mut table = txn.table_storage(&table_meta);
assert!(table.scan().unwrap().next().is_none());
let mut index = txn.index_storage(
pk_index.index_id,
pk_index.unique,
pk_index.column_indices.clone(),
);
assert!(index.lookup(&SqlValue::Integer(1)).unwrap().is_empty());
txn.commit().unwrap();
}
#[test]
fn drop_table_if_exists_succeeds_on_missing() {
let (bridge, mut catalog, _) = setup();
let mut txn = bridge.begin_write().unwrap();
let result = execute_drop_table(&mut txn, &mut catalog, "missing", true);
assert!(matches!(result, Ok(ExecutionResult::Success)));
txn.commit().unwrap();
}
#[test]
fn drop_table_if_exists_ignores_non_default_namespace() {
let bridge = TxnBridge::new(Arc::new(MemoryKV::new()));
let mut catalog = MemoryCatalog::new();
let mut table = TableMetadata::new(
"users",
vec![
ColumnMetadata::new("id", ResolvedType::Integer).with_primary_key(true),
ColumnMetadata::new("name", ResolvedType::Text),
],
)
.with_primary_key(vec!["id".into()]);
table.catalog_name = "main".to_string();
table.namespace_name = "analytics".to_string();
let mut txn = bridge.begin_write().unwrap();
execute_create_table(&mut txn, &mut catalog, table, vec![], false).unwrap();
txn.commit().unwrap();
let mut txn = bridge.begin_write().unwrap();
let result = execute_drop_table(&mut txn, &mut catalog, "users", true);
assert!(matches!(result, Ok(ExecutionResult::Success)));
txn.commit().unwrap();
assert!(catalog.table_exists("users"));
}
#[test]
fn drop_table_rejects_non_default_namespace_without_if_exists() {
let bridge = TxnBridge::new(Arc::new(MemoryKV::new()));
let mut catalog = MemoryCatalog::new();
let mut table = TableMetadata::new(
"users",
vec![
ColumnMetadata::new("id", ResolvedType::Integer).with_primary_key(true),
ColumnMetadata::new("name", ResolvedType::Text),
],
)
.with_primary_key(vec!["id".into()]);
table.catalog_name = "main".to_string();
table.namespace_name = "analytics".to_string();
let mut txn = bridge.begin_write().unwrap();
execute_create_table(&mut txn, &mut catalog, table, vec![], false).unwrap();
txn.commit().unwrap();
let mut txn = bridge.begin_write().unwrap();
let err = execute_drop_table(&mut txn, &mut catalog, "users", false).unwrap_err();
txn.rollback().unwrap();
assert!(matches!(err, ExecutorError::TableNotFound(_)));
assert!(catalog.table_exists("users"));
}
}