Skip to main content

uqa_sql/schema/columns/
removal_metadata.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Validate column dependencies against retained relation metadata and remove local declarations.
8use crate::ast::{ColumnDef, ForeignKey, TableCheck, TableKeyConstraint};
9use crate::schema::dependencies::{
10    rewrites::stored_relation_reference_matches, schema_expr_references_column,
11};
12use std::ops::Deref;
13use uqa_core::RelationIdentity;
14pub type ColumnsRead<'a> = Box<dyn Deref<Target = Vec<ColumnDef>> + 'a>;
15pub type ForeignKeysRead<'a> = Box<dyn Deref<Target = Vec<ForeignKey>> + 'a>;
16pub trait ColumnDependencyState {
17    fn columns(&self) -> ColumnsRead<'_>;
18    fn foreign_keys(&self) -> ForeignKeysRead<'_>;
19}
20pub type ColumnDependencyEntries<'a> = Vec<(String, Box<dyn ColumnDependencyState + 'a>)>;
21pub fn validate_column_dependencies(
22    target: &RelationIdentity,
23    table_name: &str,
24    column: &str,
25    entries: &[(String, Box<dyn ColumnDependencyState + '_>)],
26) -> Result<(), String> {
27    let target_state = entries
28        .iter()
29        .find(|(name, _)| name == table_name)
30        .map(|(_, state)| state)
31        .ok_or_else(|| format!("table `{table_name}` does not exist"))?;
32    for candidate in target_state.columns().iter() {
33        if candidate.name == column {
34            continue;
35        }
36        if candidate
37            .default
38            .as_ref()
39            .is_some_and(|expr| schema_expr_references_column(expr, column))
40            || candidate.generated.as_ref().is_some_and(|generated| {
41                schema_expr_references_column(&generated.expression, column)
42            })
43        {
44            return Err(format!(
45                    "ALTER TABLE DROP COLUMN `{table_name}`.`{column}` rejected: column `{}` has a dependent DEFAULT/generation expression",
46                    candidate.name
47                ));
48        }
49    }
50
51    let mut inbound = Vec::new();
52    for (candidate_name, table) in entries {
53        for foreign_key in table.foreign_keys().iter() {
54            let local_dependency = candidate_name == table_name
55                && (foreign_key.local_columns.iter().any(|name| name == column)
56                    || foreign_key
57                        .on_delete_set_columns
58                        .iter()
59                        .any(|name| name == column));
60            let referenced_dependency =
61                stored_relation_reference_matches(&foreign_key.ref_table, target)
62                    && foreign_key.ref_columns.iter().any(|name| name == column);
63            if referenced_dependency && !local_dependency {
64                inbound.push(candidate_name.clone());
65            }
66        }
67        for candidate in table.columns().iter() {
68            if candidate_name == table_name && candidate.name == column {
69                continue;
70            }
71            if candidate.references.as_ref().is_some_and(|reference| {
72                stored_relation_reference_matches(&reference.table, target)
73                    && reference.column.as_deref() == Some(column)
74            }) {
75                inbound.push(candidate_name.clone());
76            }
77        }
78    }
79    inbound.sort_unstable();
80    inbound.dedup();
81    if !inbound.is_empty() {
82        return Err(format!(
83                "ALTER TABLE DROP COLUMN `{table_name}`.`{column}` rejected: referenced by foreign key(s) on `{}`",
84                inbound.join("`, `")
85            ));
86    }
87    Ok(())
88}
89pub fn remove_column_declarations(columns: &mut Vec<ColumnDef>, column: &str) {
90    for definition in columns.iter_mut() {
91        if definition
92            .check
93            .as_ref()
94            .is_some_and(|expression| schema_expr_references_column(expression, column))
95        {
96            definition.check = None;
97            definition.check_name = None;
98            definition.check_object_id = None;
99            definition.check_is_local = true;
100            definition.check_enforced = true;
101            definition.check_validated = true;
102            definition.check_no_inherit = false;
103        }
104    }
105    columns.retain(|c| c.name != column);
106}
107pub fn remove_column_checks(checks: &mut Vec<TableCheck>, column: &str) {
108    checks.retain(|constraint| !schema_expr_references_column(&constraint.expr, column));
109}
110pub fn remove_column_keys(keys: &mut Vec<TableKeyConstraint>, column: &str) {
111    keys.retain(|constraint| !constraint.columns.iter().any(|name| name == column));
112}
113pub fn remove_column_foreign_keys(keys: &mut Vec<ForeignKey>, column: &str) {
114    keys.retain(|foreign_key| {
115        !foreign_key.local_columns.iter().any(|name| name == column)
116            && !foreign_key
117                .on_delete_set_columns
118                .iter()
119                .any(|name| name == column)
120    });
121}