uqa_sql/schema/columns/
removal_metadata.rs1use crate::ast::{ColumnDef, ForeignKey, TableCheck, TableKeyConstraint};
9use crate::schema::dependencies::{
10 rewrites::stored_relation_reference_matches, schema_expr_depends_on_column,
11 schema_expr_references_column,
12};
13use std::ops::Deref;
14use uqa_core::RelationIdentity;
15pub type ColumnsRead<'a> = Box<dyn Deref<Target = Vec<ColumnDef>> + 'a>;
16pub type ForeignKeysRead<'a> = Box<dyn Deref<Target = Vec<ForeignKey>> + 'a>;
17pub type KeyConstraintsRead<'a> = Box<dyn Deref<Target = Vec<TableKeyConstraint>> + 'a>;
18pub trait ColumnDependencyState {
19 fn columns(&self) -> ColumnsRead<'_>;
20 fn foreign_keys(&self) -> ForeignKeysRead<'_>;
21 fn key_constraints(&self) -> KeyConstraintsRead<'_>;
22}
23pub type ColumnDependencyEntries<'a> = Vec<(String, Box<dyn ColumnDependencyState + 'a>)>;
24pub fn validate_column_dependencies(
25 target: &RelationIdentity,
26 table_name: &str,
27 column: &str,
28 entries: &[(String, Box<dyn ColumnDependencyState + '_>)],
29) -> Result<(), String> {
30 let target_state = entries
31 .iter()
32 .find(|(name, _)| name == table_name)
33 .map(|(_, state)| state)
34 .ok_or_else(|| format!("table `{table_name}` does not exist"))?;
35 for candidate in target_state.columns().iter() {
36 if candidate.name == column {
37 continue;
38 }
39 if candidate
40 .default
41 .as_ref()
42 .is_some_and(|expr| schema_expr_references_column(expr, column))
43 || candidate.generated.as_ref().is_some_and(|generated| {
44 schema_expr_references_column(&generated.expression, column)
45 })
46 {
47 return Err(format!(
48 "ALTER TABLE DROP COLUMN `{table_name}`.`{column}` rejected: column `{}` has a dependent DEFAULT/generation expression",
49 candidate.name
50 ));
51 }
52 }
53
54 let included_keys = target_state
56 .key_constraints()
57 .iter()
58 .filter(|constraint| {
59 constraint
60 .included_columns
61 .iter()
62 .any(|name| name == column)
63 })
64 .map(|constraint| (constraint.kind, constraint.columns.clone()))
65 .collect::<Vec<_>>();
66 let references_included_key = |referenced: Option<&[String]>| {
67 included_keys.iter().any(|(kind, key)| match referenced {
68 Some(referenced) => key.as_slice() == referenced,
69 None => *kind == crate::ast::TableKeyConstraintKind::PrimaryKey,
71 })
72 };
73 let mut inbound = Vec::new();
74 for (candidate_name, table) in entries {
75 for foreign_key in table.foreign_keys().iter() {
76 let local_dependency = candidate_name == table_name
77 && (foreign_key.local_columns.iter().any(|name| name == column)
78 || foreign_key
79 .on_delete_set_columns
80 .iter()
81 .any(|name| name == column));
82 let referenced_dependency =
83 stored_relation_reference_matches(&foreign_key.ref_table, target)
84 && (foreign_key.ref_columns.iter().any(|name| name == column)
85 || references_included_key(
86 (!foreign_key.ref_columns.is_empty())
87 .then_some(foreign_key.ref_columns.as_slice()),
88 ));
89 if referenced_dependency && !local_dependency {
90 inbound.push(candidate_name.clone());
91 }
92 }
93 for candidate in table.columns().iter() {
94 if candidate_name == table_name && candidate.name == column {
95 continue;
96 }
97 if candidate.references.as_ref().is_some_and(|reference| {
98 stored_relation_reference_matches(&reference.table, target)
99 && (reference.column.as_deref() == Some(column)
100 || references_included_key(
101 reference.column.as_ref().map(std::slice::from_ref),
102 ))
103 }) {
104 inbound.push(candidate_name.clone());
105 }
106 }
107 }
108 inbound.sort_unstable();
109 inbound.dedup();
110 if !inbound.is_empty() {
111 return Err(format!(
112 "ALTER TABLE DROP COLUMN `{table_name}`.`{column}` rejected: referenced by foreign key(s) on `{}`",
113 inbound.join("`, `")
114 ));
115 }
116 Ok(())
117}
118pub fn remove_column_declarations(columns: &mut Vec<ColumnDef>, column: &str) -> Vec<TableCheck> {
120 let mut retained = Vec::new();
121 for definition in columns.iter_mut() {
122 if definition
123 .check
124 .as_ref()
125 .is_some_and(|expression| schema_expr_depends_on_column(expression, column))
126 {
127 let _ = crate::schema::constraint_changes::take_column_check(definition);
128 } else if definition.name == column {
129 retained.extend(crate::schema::constraint_changes::take_column_check(
130 definition,
131 ));
132 }
133 }
134 columns.retain(|c| c.name != column);
135 retained
136}
137pub fn remove_column_checks(checks: &mut Vec<TableCheck>, column: &str) {
138 checks.retain(|constraint| !schema_expr_depends_on_column(&constraint.expr, column));
139}
140pub fn remove_column_keys(
142 columns: &mut [ColumnDef],
143 keys: &mut Vec<TableKeyConstraint>,
144 column: &str,
145) {
146 let mut removed = Vec::new();
147 keys.retain(|constraint| {
148 let goes = constraint
149 .columns
150 .iter()
151 .chain(&constraint.included_columns)
152 .any(|name| name == column);
153 if goes {
154 removed.push(constraint.clone());
155 }
156 !goes
157 });
158 for constraint in removed {
159 let [key] = constraint.columns.as_slice() else {
160 continue;
161 };
162 if keys
163 .iter()
164 .any(|kept| kept.kind == constraint.kind && kept.columns.as_slice() == [key.as_str()])
165 {
166 continue;
167 }
168 if let Some(definition) = columns
169 .iter_mut()
170 .find(|definition| definition.name == *key)
171 {
172 match constraint.kind {
173 crate::ast::TableKeyConstraintKind::PrimaryKey => definition.primary_key = false,
174 crate::ast::TableKeyConstraintKind::Unique => definition.unique = false,
175 }
176 }
177 }
178}
179pub fn remove_column_foreign_keys(keys: &mut Vec<ForeignKey>, column: &str) {
180 keys.retain(|foreign_key| {
181 !foreign_key.local_columns.iter().any(|name| name == column)
182 && !foreign_key
183 .on_delete_set_columns
184 .iter()
185 .any(|name| name == column)
186 });
187}
188
189#[cfg(test)]
190mod tests;