uqa_sql/routines/lifecycle/
dependencies.rs1use super::{RoutineDropResolution, RoutineDropTarget, RoutineRegistry, RoutineSchemaDependents};
10use crate::{
11 ast::{CreateFunction, FunctionBody},
12 routines::{routine_signature_types, SQLUserFunction},
13 SQLError,
14};
15use std::{collections::BTreeMap, sync::Arc};
16
17pub fn append_schema_function_dependents(
18 table_name: &str,
19 columns: &[crate::ast::ColumnDef],
20 checks: &[crate::ast::TableCheck],
21 target: &crate::ast::FunctionBinding,
22 foreign: bool,
23 dependents: &mut RoutineSchemaDependents,
24) -> Result<(), SQLError> {
25 let relation = if foreign {
26 format!("foreign table `{table_name}`")
27 } else {
28 format!("`{table_name}`")
29 };
30 for column in columns {
31 if let Some(generated) = &column.generated {
32 let referenced =
33 generated.function_dependencies.iter().any(|dependency| {
34 crate::routines::function_binding_matches(dependency, target)
35 }) || crate::catalog::stored_ast::expression_references_routine_identity(
36 &generated.expression,
37 target,
38 )?;
39 if referenced {
40 dependents
41 .columns
42 .push((table_name.to_string(), column.name.clone(), foreign));
43 }
44 }
45 if let Some(default) = &column.default {
46 if crate::catalog::stored_ast::expression_references_routine_identity(default, target)?
47 {
48 dependents
49 .defaults
50 .push((table_name.to_string(), column.name.clone(), foreign));
51 }
52 }
53 if let Some(check) = &column.check {
54 if crate::catalog::stored_ast::expression_references_routine_identity(check, target)? {
55 let name = column.check_name.clone().ok_or_else(|| {
56 SQLError::Internal(format!(
57 "CHECK constraint on {relation}.`{}` has no catalog name",
58 column.name
59 ))
60 })?;
61 dependents
62 .checks
63 .push((table_name.to_string(), name, foreign));
64 }
65 }
66 }
67 for check in checks {
68 if crate::catalog::stored_ast::expression_references_routine_identity(&check.expr, target)?
69 {
70 let name = check.name.clone().ok_or_else(|| {
71 SQLError::Internal(format!(
72 "table CHECK constraint on {relation} has no catalog name"
73 ))
74 })?;
75 dependents
76 .checks
77 .push((table_name.to_string(), name, foreign));
78 }
79 }
80 Ok(())
81}
82
83pub fn stored_routine_dependents(
84 registry: &BTreeMap<String, Vec<Arc<SQLUserFunction>>>,
85 target: &RoutineDropTarget,
86) -> Result<Vec<RoutineDropTarget>, SQLError> {
87 let binding = target.binding();
88 let mut dependents = Vec::new();
89 for (name, overloads) in registry {
90 for function in overloads {
91 if routine_definition_references(&function.def, &binding)? {
92 dependents.push(RoutineDropTarget {
93 object_id: function.def.object_id,
94 name: name.clone(),
95 argument_types: routine_signature_types(&function.def),
96 is_procedure: function.def.is_procedure,
97 });
98 }
99 }
100 }
101 dependents.retain(|dependent| dependent != target);
102 dependents.sort();
103 dependents.dedup();
104 Ok(dependents)
105}
106
107pub fn routine_definition_references(
108 def: &CreateFunction,
109 target: &crate::ast::FunctionBinding,
110) -> Result<bool, SQLError> {
111 for default in def
112 .params
113 .iter()
114 .filter_map(|parameter| parameter.default.as_ref())
115 {
116 if crate::catalog::stored_ast::expression_references_routine_identity(default, target)? {
117 return Ok(true);
118 }
119 }
120 let FunctionBody::Statements(statements) = &def.body else {
121 return Ok(false);
122 };
123 for statement in statements {
124 if crate::catalog::stored_ast::statement_references_routine_identity(statement, target)? {
125 return Ok(true);
126 }
127 }
128 Ok(false)
129}
130
131pub fn expand_stored_routine_drop_dependents(
132 registry: &BTreeMap<String, Vec<Arc<SQLUserFunction>>>,
133 cascade: bool,
134 resolution: &mut RoutineDropResolution,
135 display_label: &dyn Fn(&RoutineDropTarget) -> Result<String, SQLError>,
136) -> Result<Vec<RoutineDropTarget>, SQLError> {
137 let explicit_targets = resolution.seen_targets.clone();
138 let mut cascaded_routines = Vec::new();
139 let mut target_index = 0;
140 while target_index < resolution.targets.len() {
141 let target = resolution.targets[target_index].clone();
142 target_index += 1;
143 if target.is_procedure {
144 continue;
145 }
146 for dependent in stored_routine_dependents(registry, &target)? {
147 if explicit_targets.contains(&dependent) || resolution.seen_targets.contains(&dependent)
148 {
149 continue;
150 }
151 if !cascade {
152 return Err(SQLError::Routine {
153 sqlstate: "2BP01".into(),
154 message: format!(
155 "cannot drop function {} because other objects depend on it",
156 display_label(&target)?
157 ),
158 });
159 }
160 resolution.seen_targets.insert(dependent.clone());
161 cascaded_routines.push(dependent.clone());
162 resolution.targets.push(dependent);
163 }
164 }
165 Ok(cascaded_routines)
166}
167
168pub fn remove_routine_registry_targets(
169 next: &mut RoutineRegistry,
170 targets: &[RoutineDropTarget],
171) -> Result<(), SQLError> {
172 for target in targets {
174 let overloads = next.get(&target.name).ok_or_else(|| {
175 SQLError::Internal(format!(
176 "resolved {} registry entry `{}` disappeared before DROP",
177 target.kind(),
178 target.name
179 ))
180 })?;
181 if !overloads.iter().any(|function| {
182 function.def.is_procedure == target.is_procedure
183 && routine_signature_types(&function.def) == target.argument_types
184 }) {
185 return Err(SQLError::Internal(format!(
186 "resolved {} {} disappeared before DROP",
187 target.kind(),
188 target.label()
189 )));
190 }
191 }
192
193 for target in targets.iter().rev() {
194 let overloads = next.get_mut(&target.name).ok_or_else(|| {
195 SQLError::Internal(format!(
196 "resolved {} registry entry `{}` disappeared while applying DROP",
197 target.kind(),
198 target.name
199 ))
200 })?;
201 let position = overloads
202 .iter()
203 .position(|function| {
204 function.def.is_procedure == target.is_procedure
205 && routine_signature_types(&function.def) == target.argument_types
206 })
207 .ok_or_else(|| {
208 SQLError::Internal(format!(
209 "resolved {} {} disappeared while applying DROP",
210 target.kind(),
211 target.label()
212 ))
213 })?;
214 overloads.remove(position);
215 if overloads.is_empty() {
216 next.remove(&target.name);
217 }
218 }
219 Ok(())
220}