Skip to main content

uqa_sql/schema/columns/
removal.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Discover foreign keys that depend on a deleted column in catalog order.
8use crate::{
9    assignment::columns::ColumnCatalogError,
10    ast::{ForeignKey, TableKeyConstraint},
11    SQLError,
12};
13pub trait ColumnRemovalCatalog {
14    fn try_resolve_table_name(&self, table: &str) -> Result<Option<String>, ColumnCatalogError>;
15    fn table_names(&self) -> Result<Vec<String>, ColumnCatalogError>;
16    fn try_foreign_keys(&self, table: &str) -> Result<Vec<ForeignKey>, ColumnCatalogError>;
17    fn try_key_constraints(
18        &self,
19        table: &str,
20    ) -> Result<Vec<TableKeyConstraint>, ColumnCatalogError>;
21}
22/// Foreign keys that reference `column`, or that reference the key of a constraint whose supporting index includes it: dropping an included column drops that constraint.
23pub fn foreign_keys_referencing_column(
24    catalog: &dyn ColumnRemovalCatalog,
25    table: &str,
26    column: &str,
27) -> Result<Vec<(String, String)>, SQLError> {
28    let canonical = catalog
29        .try_resolve_table_name(table)
30        .map_err(|error| {
31            crate::catalog::errors::storage_error("ALTER TABLE DROP COLUMN", error.as_ref())
32        })?
33        .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
34    let included_keys = catalog
35        .try_key_constraints(&canonical)
36        .map_err(|error| {
37            crate::catalog::errors::storage_error("ALTER TABLE DROP COLUMN", error.as_ref())
38        })?
39        .into_iter()
40        .filter(|constraint| {
41            constraint
42                .included_columns
43                .iter()
44                .any(|name| name == column)
45        })
46        .map(|constraint| constraint.columns)
47        .collect::<Vec<_>>();
48    let mut dependents = Vec::new();
49    for referrer in catalog.table_names().map_err(|error| {
50        crate::catalog::errors::storage_error("ALTER TABLE DROP COLUMN", error.as_ref())
51    })? {
52        for foreign_key in catalog.try_foreign_keys(&referrer).map_err(|error| {
53            crate::catalog::errors::storage_error("ALTER TABLE DROP COLUMN", error.as_ref())
54        })? {
55            if foreign_key.ref_table == canonical
56                && (foreign_key.ref_columns.iter().any(|name| name == column)
57                    || included_keys.contains(&foreign_key.ref_columns))
58            {
59                dependents.push((
60                    referrer.clone(),
61                    foreign_key.name.clone().ok_or_else(|| {
62                        SQLError::Internal("dependent FOREIGN KEY has no durable name".into())
63                    })?,
64                ));
65            }
66        }
67    }
68    Ok(dependents)
69}