use vibesql_ast::*;
use vibesql_catalog::ForeignKeyConstraint;
use vibesql_storage::Database;
use crate::errors::ExecutorError;
pub(super) fn execute_add_constraint(
stmt: &AddConstraintStmt,
database: &mut Database,
) -> Result<String, ExecutorError> {
let table = database
.get_table_mut(&stmt.table_name)
.ok_or_else(|| ExecutorError::TableNotFound(stmt.table_name.clone()))?;
match &stmt.constraint.kind {
TableConstraintKind::PrimaryKey { columns, .. } => {
let column_names: Vec<String> =
columns.iter().map(|c| c.expect_column_name().to_string()).collect();
for col_name in &column_names {
if !table.schema.has_column(col_name) {
return Err(ExecutorError::ColumnNotFound {
column_name: col_name.to_string(),
table_name: stmt.table_name.clone(),
searched_tables: vec![stmt.table_name.clone()],
available_columns: table
.schema
.columns
.iter()
.map(|c| c.name.clone())
.collect(),
});
}
}
if table.schema.primary_key.is_some() {
return Err(ExecutorError::ConstraintViolation(
"Table already has a PRIMARY KEY constraint".to_string(),
));
}
table.schema_mut().primary_key = Some(column_names);
table.rebuild_indexes();
let updated_schema = table.schema.clone();
database.catalog.drop_table(&stmt.table_name)?;
database.catalog.create_table(updated_schema)?;
Ok(format!("PRIMARY KEY constraint added to table '{}'", stmt.table_name))
}
TableConstraintKind::Unique { columns, .. } => {
let column_names: Vec<String> =
columns.iter().map(|c| c.expect_column_name().to_string()).collect();
table.schema_mut().add_unique_constraint(column_names)?;
table.rebuild_indexes();
let updated_schema = table.schema.clone();
database.catalog.drop_table(&stmt.table_name)?;
database.catalog.create_table(updated_schema)?;
Ok(format!("UNIQUE constraint added to table '{}'", stmt.table_name))
}
TableConstraintKind::Check { expr } => {
let constraint_name = stmt
.constraint
.name
.clone()
.unwrap_or_else(|| format!("check_{}", table.schema.check_constraints.len()));
table.schema_mut().add_check_constraint(constraint_name.clone(), *expr.clone())?;
Ok(format!(
"CHECK constraint '{}' added to table '{}'",
constraint_name, stmt.table_name
))
}
TableConstraintKind::ForeignKey {
columns,
references_table,
references_columns,
on_delete,
on_update,
deferral,
} => {
let convert_action = |action: &Option<vibesql_ast::ReferentialAction>| match action {
Some(vibesql_ast::ReferentialAction::Cascade) => {
vibesql_catalog::ReferentialAction::Cascade
}
Some(vibesql_ast::ReferentialAction::SetNull) => {
vibesql_catalog::ReferentialAction::SetNull
}
Some(vibesql_ast::ReferentialAction::SetDefault) => {
vibesql_catalog::ReferentialAction::SetDefault
}
Some(vibesql_ast::ReferentialAction::Restrict) => {
vibesql_catalog::ReferentialAction::Restrict
}
Some(vibesql_ast::ReferentialAction::NoAction) | None => {
vibesql_catalog::ReferentialAction::NoAction
}
};
let mut column_indices = Vec::new();
for col_name in columns {
let idx = table.schema.get_column_index(col_name).ok_or_else(|| {
ExecutorError::ColumnNotFound {
column_name: col_name.to_string(),
table_name: stmt.table_name.clone(),
searched_tables: vec![stmt.table_name.clone()],
available_columns: table
.schema
.columns
.iter()
.map(|c| c.name.clone())
.collect(),
}
})?;
column_indices.push(idx);
}
let ref_table = database
.get_table(references_table)
.ok_or_else(|| ExecutorError::TableNotFound(references_table.clone()))?;
let mut parent_column_indices = Vec::new();
for col_name in references_columns {
let idx = ref_table.schema.get_column_index(col_name).ok_or_else(|| {
ExecutorError::ColumnNotFound {
column_name: col_name.to_string(),
table_name: references_table.clone(),
searched_tables: vec![references_table.clone()],
available_columns: ref_table
.schema
.columns
.iter()
.map(|c| c.name.clone())
.collect(),
}
})?;
parent_column_indices.push(idx);
}
let (is_deferrable, initially_deferred) = deferral
.map(|d| (d.is_deferrable, d.initially_deferred))
.unwrap_or((false, false));
let fk = ForeignKeyConstraint {
name: stmt.constraint.name.clone(),
column_names: columns.clone(),
column_indices,
parent_table: references_table.clone(),
parent_column_names: references_columns.clone(),
parent_column_indices,
on_delete: convert_action(on_delete),
on_update: convert_action(on_update),
is_deferrable,
initially_deferred,
};
let table = database
.get_table_mut(&stmt.table_name)
.ok_or_else(|| ExecutorError::TableNotFound(stmt.table_name.clone()))?;
table.schema_mut().add_foreign_key(fk)?;
let updated_schema = table.schema.clone();
database.catalog.drop_table(&stmt.table_name)?;
database.catalog.create_table(updated_schema)?;
Ok(format!("FOREIGN KEY constraint added to table '{}'", stmt.table_name))
}
TableConstraintKind::Fulltext { index_name: _, columns: _ } => {
Err(ExecutorError::UnsupportedFeature(
"FULLTEXT index support is not yet implemented".to_string(),
))
}
}
}
pub(super) fn execute_drop_constraint(
stmt: &DropConstraintStmt,
database: &mut Database,
) -> Result<String, ExecutorError> {
let table = database
.get_table_mut(&stmt.table_name)
.ok_or_else(|| ExecutorError::TableNotFound(stmt.table_name.clone()))?;
if table.schema_mut().drop_check_constraint(&stmt.constraint_name).is_ok() {
let updated_schema = table.schema.clone();
database.catalog.drop_table(&stmt.table_name)?;
database.catalog.create_table(updated_schema)?;
return Ok(format!(
"CHECK constraint '{}' dropped from table '{}'",
stmt.constraint_name, stmt.table_name
));
}
if table.schema_mut().drop_foreign_key(&stmt.constraint_name).is_ok() {
let updated_schema = table.schema.clone();
database.catalog.drop_table(&stmt.table_name)?;
database.catalog.create_table(updated_schema)?;
return Ok(format!(
"FOREIGN KEY constraint '{}' dropped from table '{}'",
stmt.constraint_name, stmt.table_name
));
}
Err(ExecutorError::ConstraintNotFound {
constraint_name: stmt.constraint_name.clone(),
table_name: stmt.table_name.clone(),
})
}