pub mod context;
mod dependencies;
use context::{TableDropCandidate, TableRemovalContext, TableRemovalEntry};
use uqa_core::RelationIdentity;
use uqa_sql::schema::removal::tables::detach_inbound_foreign_keys;
use uqa_storage::{StorageBackendError, StorageBackendResult};
fn resolved_relation_identity(name: &str) -> StorageBackendResult<RelationIdentity> {
RelationIdentity::from_legacy_name(name).map_err(StorageBackendError::Other)
}
fn table_not_found(table: &str) -> StorageBackendError {
StorageBackendError::Other(format!("table `{table}` does not exist"))
}
impl TableRemovalContext<'_> {
pub fn drop_table(&self, name: &str) -> StorageBackendResult<bool> {
let Some(name) = self.resolve_table_ddl_target(name, "DROP TABLE")? else {
return Ok(false);
};
self.try_drop_tables_inner(&[name], false)?;
Ok(true)
}
pub fn try_drop_tables(&self, names: &[String], cascade: bool) -> StorageBackendResult<()> {
self.transactions
.with_table_removal_write(Box::new(move |context| {
context.try_drop_tables_inner(names, cascade)
}))
}
fn resolve_table_ddl_target(
&self,
name: &str,
action: &str,
) -> StorageBackendResult<Option<String>> {
uqa_sql::schema::removal::tables::resolved_table_ddl_target(
self.catalog.relation_kind(name)?,
action,
)
.map_err(StorageBackendError::Other)
}
pub fn hierarchy_drop_targets(
&self,
roots: &[String],
cascade: bool,
) -> (Vec<String>, Vec<String>) {
uqa_sql::schema::removal::hierarchy::hierarchy_drop_targets(self.hierarchy, roots, cascade)
}
pub fn try_drop_tables_inner(
&self,
names: &[String],
cascade: bool,
) -> StorageBackendResult<()> {
let canonical_names = self.canonical_hierarchy_drop_targets(names, cascade)?;
crate::routines::removal::drop_relation_routine_dependents(
&self.routines,
&canonical_names,
cascade,
"table",
)
.map_err(|error| StorageBackendError::Other(error.to_string()))?;
let (target_names, targets) = Self::drop_target_sets(&canonical_names)?;
let entries = self.catalog.table_entries();
if !cascade {
self.ensure_no_drop_restrict_dependents(
&canonical_names,
&target_names,
&targets,
&entries,
)?;
}
if !cascade {
self.ensure_no_drop_view_dependencies(&canonical_names)?;
}
Self::ensure_drop_targets_unreferenced(&target_names, &targets, &entries)?;
let owned_sequences = self.owned_sequences_for_drop(&target_names, &entries)?;
let (mut inbound, updates) =
Self::prepare_inbound_candidates(entries, &target_names, &targets);
if !cascade && !inbound.is_empty() {
inbound.sort_unstable();
inbound.dedup();
return Err(StorageBackendError::Other(format!(
"DROP TABLE rejected: still referenced by foreign key(s) on `{}`; use CASCADE",
inbound.join("`, `")
)));
}
if cascade {
self.events
.drop_rules_depending_on_relations_inner(&canonical_names)?;
crate::schema::view_removal::drop_views_depending_on_relations(
&self.views,
&canonical_names,
)?;
for (_, table, columns, checks, foreign_keys, key_constraints) in &updates {
table.persist_constraints(columns, checks, foreign_keys, key_constraints)?;
}
for (_, table, columns, checks, foreign_keys, _) in updates {
**table.columns_write() = columns;
**table.checks_write() = checks;
**table.foreign_keys_write() = foreign_keys;
}
}
for name in canonical_names {
self.drop_table_state_inner(&name)?;
}
self.publication
.prune_constraint_modes()
.map_err(|error| StorageBackendError::Other(error.to_string()))?;
for sequence in owned_sequences {
self.sequences.drop_owned_sequence(&sequence, cascade)?;
}
Ok(())
}
fn prepare_inbound_candidates<'a>(
entries: Vec<TableRemovalEntry<'a>>,
target_names: &std::collections::BTreeSet<String>,
targets: &[RelationIdentity],
) -> (Vec<String>, Vec<TableDropCandidate<'a>>) {
let mut inbound = Vec::new();
let mut updates = Vec::new();
for (candidate_name, table) in entries {
if target_names.contains(&candidate_name) {
continue;
}
let mut columns = table.columns().clone();
let checks = table.table_checks().clone();
let mut foreign_keys = table.foreign_keys().clone();
let key_constraints = table.key_constraints().clone();
let changed = detach_inbound_foreign_keys(&mut columns, &mut foreign_keys, targets);
if changed {
inbound.push(candidate_name.clone());
updates.push((
candidate_name,
table,
columns,
checks,
foreign_keys,
key_constraints,
));
}
}
(inbound, updates)
}
pub fn drop_temporary_table_on_commit_inner(&self, name: &str) -> StorageBackendResult<()> {
crate::schema::view_removal::drop_temporary_views_depending_on_relation_inner(
&self.views,
name,
)?;
self.try_drop_tables_inner(&[name.to_string()], true)
}
fn drop_table_state_inner(&self, name: &str) -> StorageBackendResult<()> {
let relation = resolved_relation_identity(name)?;
if !self.catalog.contains_relation(&relation) {
return Err(table_not_found(name));
}
self.events.drop_relation_events_inner(&relation)?;
self.publication.remove_state(name, &relation)
}
}