mod aliases;
mod cascade;
mod columns;
pub mod context;
mod dependencies;
mod labels;
mod publication;
mod relations;
pub use aliases::prepare_routine_column_alias_drop;
pub use cascade::{drop_domain_types_and_routines, drop_schema_types_and_routines};
use cascade::{expand_routine_domain_column_drop, expand_routine_domain_drop};
pub use columns::{drop_column_routine_dependents, expand_column_drop_dependencies};
pub use context::RoutineRemovalContext;
use dependencies::routine_object_dependents;
pub use labels::relation_dependents_drop_error;
use labels::routine_drop_display_label;
pub use publication::commit_sql_function_drop;
use relations::relation_drop_closure;
pub use relations::{drop_relation_routine_dependents, sequence_drop_column_names};
use crate::schema::domains::dependencies as domain_dependencies;
use std::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
};
use uqa_sql::{
ast::DropFunctionStmt,
routines::{
lifecycle::{
binding as analysis_binding,
dependencies::{self as analysis_dependencies, append_schema_function_dependents},
diagnostics::{append_routine_cascade_notice, ensure_no_function_dependencies},
relations as analysis_relations, RoutineDropResolution, RoutineDropTarget,
RoutineObjectDependents, RoutineSchemaDependents, SQLFunctionDropPlan,
},
routine_signature_types, SQLUserFunction,
},
SQLError,
};
pub fn drop_sql_functions(
context: &RoutineRemovalContext<'_>,
statement: &DropFunctionStmt,
) -> Result<(), SQLError> {
let plan = preflight_sql_function_drop(context, statement)?;
commit_sql_function_drop(context, plan)
}
pub fn preflight_sql_function_drop(
context: &RoutineRemovalContext<'_>,
stmt: &DropFunctionStmt,
) -> Result<SQLFunctionDropPlan, SQLError> {
let kind = if stmt.is_procedure {
"procedure"
} else {
"function"
};
let registry = context.registry.routine_snapshot();
let mut resolution =
analysis_binding::resolve_sql_function_drop_targets(context.names, stmt, ®istry, kind)?;
ensure_routine_drop_owners(context, ®istry, &resolution.targets)?;
let cascaded_routines =
expand_stored_routine_drop_dependents(context, ®istry, stmt.cascade, &mut resolution)?;
let mut domains = BTreeSet::new();
if stmt.cascade {
expand_routine_domain_drop(context, ®istry, &mut resolution, &mut domains)?;
} else {
let bindings = resolution
.targets
.iter()
.map(RoutineDropTarget::binding)
.collect::<Vec<_>>();
domain_dependencies::expand_domain_drop_targets(&context.domains, &mut domains, &bindings)?;
if !domains.is_empty()
|| !domain_dependencies::domain_checks_depending_on_routines(
&context.domains,
&bindings,
)?
.is_empty()
{
let label = routine_drop_display_label(context, &resolution.targets[0])?;
return Err(SQLError::Routine {
sqlstate: "2BP01".into(),
message: format!("cannot drop function {label} because other objects depend on it"),
});
}
}
let dependents = routine_object_dependents(context, &resolution.targets, stmt.cascade)?;
if stmt.cascade {
append_routine_cascade_notice(&mut resolution.notices, &cascaded_routines, &dependents);
}
Ok(SQLFunctionDropPlan {
domains,
targets: resolution.targets,
dependents,
notices: resolution.notices,
})
}
pub fn ensure_routine_drop_owners(
context: &RoutineRemovalContext<'_>,
registry: &BTreeMap<String, Vec<Arc<SQLUserFunction>>>,
targets: &[RoutineDropTarget],
) -> Result<(), SQLError> {
let current_user = context.catalog.current_user_name();
let roles = context.roles.role_definitions();
let memberships = context.roles.role_memberships();
analysis_binding::ensure_routine_drop_owners(
registry,
targets,
¤t_user,
&roles,
&memberships,
)
}
pub fn expand_stored_routine_drop_dependents(
context: &RoutineRemovalContext<'_>,
registry: &BTreeMap<String, Vec<Arc<SQLUserFunction>>>,
cascade: bool,
resolution: &mut RoutineDropResolution,
) -> Result<Vec<RoutineDropTarget>, SQLError> {
analysis_dependencies::expand_stored_routine_drop_dependents(
registry,
cascade,
resolution,
&|target| routine_drop_display_label(context, target),
)
}