use super::plan::DeletionPlan;
use super::CatalogRemovalContext;
use crate::catalog::projection::{CatalogObject, RelationKind};
use std::collections::BTreeSet;
use uqa_core::RelationIdentity;
use uqa_sql::ast::{DropRule, DropTrigger, FunctionBinding};
use uqa_sql::routines::lifecycle::RoutineDropTarget;
use uqa_sql::SQLError;
use uqa_storage::StorageBackendError;
impl DeletionPlan {
pub(super) fn execute(self, context: &CatalogRemovalContext<'_>) -> Result<(), SQLError> {
for object in &self.steps {
if let CatalogObject::Relation {
identity,
kind: RelationKind::Table,
..
} = object
{
context
.events
.ensure_no_pending_trigger_events(&identity.qualified_name(), "DROP TABLE")?;
}
}
let routines = routine_targets(context, &self.steps)?;
let mut rewritten = Some(routine_alias_rewrites(context, &self.steps, &routines)?);
let last_column = self
.steps
.iter()
.rposition(|object| matches!(object, CatalogObject::Column { .. }));
let mut tables_removed = false;
let mut routines = routines.into_iter();
for (position, object) in self.steps.iter().enumerate() {
match object {
CatalogObject::Relation { identity, kind, .. } => {
tables_removed |= *kind == RelationKind::Table;
remove_relation(context, identity, *kind)?;
}
CatalogObject::Column {
relation,
kind,
name,
} => remove_column(context, relation, *kind, name)?,
CatalogObject::Type(oid) => {
crate::schema::domains::dependencies::remove_types(
&context.domains,
&BTreeSet::from([*oid]),
)?;
}
CatalogObject::RelationConstraint {
relation,
kind,
name,
..
} => remove_relation_constraint(context, relation, *kind, name)?,
CatalogObject::DomainConstraint { domain, name, .. } => {
crate::schema::domains::dependencies::remove_domain_constraint(
&context.domains,
*domain,
name,
)?;
}
CatalogObject::ColumnDefault {
relation,
kind,
column,
} => remove_column_default(context, relation, *kind, column)?,
CatalogObject::Routine { .. } => {
let routine = routines.next().ok_or_else(|| {
SQLError::Internal("deletion plan lost a routine target".into())
})?;
crate::routines::removal::commit_routine_registry_drop(
&context.tables.routines,
std::slice::from_ref(&routine),
)?;
}
CatalogObject::Rule { relation, name, .. } => {
context.tables.events.drop_rule(&DropRule {
name: name.clone(),
table: relation.qualified_name(),
if_exists: false,
cascade: false,
})?;
}
CatalogObject::Trigger { relation, name, .. } => {
context.tables.events.drop_trigger(&DropTrigger {
name: name.clone(),
table: relation.qualified_name(),
if_exists: false,
cascade: false,
})?;
}
CatalogObject::Schema(name) => remove_schema(context, name)?,
CatalogObject::ForeignServer { name, object_id } => {
context.foreign_servers.remove(name, *object_id)?;
}
CatalogObject::ForeignWrapper { name, object_id } => {
context.foreign_wrappers.remove(name, *object_id)?;
}
CatalogObject::ArrayType { .. } | CatalogObject::RowType { .. } => {}
}
if Some(position) == last_column {
publish_routine_rewrites(context, rewritten.take())?;
}
}
if tables_removed {
context.tables.publication.prune_constraint_modes()?;
}
publish_routine_rewrites(context, rewritten.take())
}
}
fn publish_routine_rewrites(
context: &CatalogRemovalContext<'_>,
rewritten: Option<Vec<uqa_sql::ast::CreateFunction>>,
) -> Result<(), SQLError> {
let Some(rewritten) = rewritten else {
return Ok(());
};
crate::routines::rewrites::publish_stored_routine_body_rewrites(
&context.tables.routines.bodies,
rewritten,
)?;
crate::routines::rewrites::refresh_stored_merge_target_plans(&context.tables.routines.bodies)
}
fn remove_relation(
context: &CatalogRemovalContext<'_>,
identity: &RelationIdentity,
kind: RelationKind,
) -> Result<(), SQLError> {
let name = identity.qualified_name();
match kind {
RelationKind::Table => context
.tables
.drop_table_state_inner(&name)
.map_err(|error| storage_error("DROP TABLE", &error)),
RelationKind::View | RelationKind::MaterializedView => {
crate::schema::view_removal::drop_view_state_inner(&context.tables.views, &name)
}
RelationKind::ForeignTable => {
if context
.foreign_tables
.drop_foreign_table_inner(&name)
.map_err(SQLError::Internal)?
{
Ok(())
} else {
Err(disappeared("foreign table", &name))
}
}
RelationKind::Sequence => {
context
.tables
.sequences
.dependencies
.detach_sequence_provenance(&name)
.map_err(|error| storage_error("DROP SEQUENCE", &error))?;
if context
.tables
.sequences
.publication
.remove_state(&name)
.map_err(SQLError::Internal)?
{
Ok(())
} else {
Err(disappeared("sequence", &name))
}
}
RelationKind::Index => {
crate::schema::indexes::removal::drop_index_dependency(&context.indexes, identity)
}
RelationKind::CompositeType => Ok(()),
}
}
fn remove_column(
context: &CatalogRemovalContext<'_>,
relation: &RelationIdentity,
kind: RelationKind,
column: &str,
) -> Result<(), SQLError> {
if kind == RelationKind::CompositeType {
return crate::schema::composites::attributes::drop_composite_attribute(
&context.composites,
relation,
column,
);
}
let table = relation.qualified_name();
let removed = if kind == RelationKind::ForeignTable {
context
.tables
.sequences
.dependencies
.foreign
.drop_foreign_table_column_dependency(&table, column)
.map_err(|error| storage_error("ALTER FOREIGN TABLE DROP COLUMN", &error))?
== Some(true)
} else {
crate::schema::publication::removal::drop_column(
&context.tables.sequences.dependencies.columns,
&table,
column,
)
.map_err(|error| storage_error("ALTER TABLE DROP COLUMN", &error))?
};
if removed {
Ok(())
} else {
Err(disappeared("column", &format!("{table}.{column}")))
}
}
fn remove_relation_constraint(
context: &CatalogRemovalContext<'_>,
relation: &RelationIdentity,
kind: RelationKind,
name: &str,
) -> Result<(), SQLError> {
let table = relation.qualified_name();
if kind == RelationKind::ForeignTable {
return match context
.tables
.sequences
.dependencies
.foreign
.drop_foreign_table_check_dependency(&table, name)
.map_err(|error| storage_error("ALTER FOREIGN TABLE DROP CONSTRAINT", &error))?
{
Some(true) => Ok(()),
_ => Err(disappeared("constraint", &format!("{name} on {table}"))),
};
}
crate::schema::constraints::drop::drop_constraint_dependency(
&context.tables.sequences.dependencies.constraints,
&table,
name,
)
}
fn remove_column_default(
context: &CatalogRemovalContext<'_>,
relation: &RelationIdentity,
kind: RelationKind,
column: &str,
) -> Result<(), SQLError> {
let table = relation.qualified_name();
let removed = if kind == RelationKind::ForeignTable {
context
.tables
.sequences
.dependencies
.foreign
.clear_foreign_table_default_dependency(&table, column)
.map_err(|error| storage_error("ALTER FOREIGN TABLE DROP DEFAULT", &error))?
== Some(true)
} else {
crate::schema::publication::columns::set_column_default(
&context.tables.sequences.dependencies.schema,
&table,
column,
None,
)
.map_err(|error| storage_error("ALTER TABLE DROP DEFAULT", &error))?
};
if removed {
Ok(())
} else {
Err(disappeared("default", &format!("{table}.{column}")))
}
}
fn remove_schema(context: &CatalogRemovalContext<'_>, name: &str) -> Result<(), SQLError> {
if crate::schema::namespaces::removal::drop_empty_schema(&context.schemas, name)
.map_err(|error| storage_error("DROP SCHEMA", &error))?
{
Ok(())
} else {
Err(disappeared("schema", name))
}
}
fn routine_targets(
context: &CatalogRemovalContext<'_>,
steps: &[CatalogObject],
) -> Result<Vec<RoutineDropTarget>, SQLError> {
let registry = context.tables.routines.registry.routine_snapshot();
steps
.iter()
.filter_map(|object| match object {
CatalogObject::Routine { object_id } => Some(object_id),
_ => None,
})
.map(|object_id| {
registry
.iter()
.flat_map(|(name, overloads)| {
overloads.iter().map(move |function| (name, function))
})
.find(|(_, function)| function.def.object_id == Some(*object_id))
.map(|(name, function)| RoutineDropTarget {
object_id: function.def.object_id,
name: name.clone(),
argument_types: uqa_sql::routines::routine_signature_types(&function.def),
is_procedure: function.def.is_procedure,
})
.ok_or_else(|| SQLError::Internal("routine disappeared before DROP".into()))
})
.collect()
}
fn routine_alias_rewrites(
context: &CatalogRemovalContext<'_>,
steps: &[CatalogObject],
routines: &[RoutineDropTarget],
) -> Result<Vec<uqa_sql::ast::CreateFunction>, SQLError> {
let columns = steps
.iter()
.filter_map(|object| match object {
CatalogObject::Column { relation, name, .. } => {
Some((relation.qualified_name(), name.clone()))
}
_ => None,
})
.collect::<BTreeSet<_>>();
if columns.is_empty() {
return Ok(Vec::new());
}
let removed = routines
.iter()
.map(RoutineDropTarget::binding)
.collect::<Vec<FunctionBinding>>();
let dependencies =
uqa_sql::routines::lifecycle::rewrites::routine_column_drop_dependencies(columns)?;
let bodies = &context.tables.routines.bodies;
let registry = bodies.registry.routine_snapshot();
uqa_sql::routines::lifecycle::rewrites::routine_column_alias_drop_candidates(
bodies.columns,
®istry,
&dependencies,
&removed,
)
}
fn storage_error(action: &str, error: &StorageBackendError) -> SQLError {
uqa_sql::catalog::errors::storage_error(action, error)
}
fn disappeared(kind: &str, name: &str) -> SQLError {
SQLError::Internal(format!("{kind} {name} disappeared before its removal"))
}