1use crate::{
10 ast::{
11 ColumnDef, ColumnType, CreateFunction, Expr, FunctionBody, FunctionReturns, IndexKey,
12 TableCheck,
13 },
14 catalog::{domain::StoredDomain, index::IndexDefinition, stored_view::StoredView},
15 ir::ScalarExpr,
16 SQLError,
17};
18use std::collections::{BTreeMap, BTreeSet};
19use uqa_core::RelationIdentity;
20
21pub trait DomainTypeCatalog {
22 fn resolve_domain_type_reference(&self, name: &str) -> Option<ColumnType>;
23}
24
25#[derive(Default)]
26pub struct DomainDependents {
27 pub indexes: BTreeSet<RelationIdentity>,
28 pub columns: BTreeSet<(String, String, bool)>,
29 pub defaults: BTreeSet<(String, String, bool)>,
30 pub checks: BTreeSet<(String, String, bool)>,
31}
32
33pub fn references_domain(ty: &ColumnType, targets: &BTreeSet<u32>) -> bool {
34 match ty {
35 ColumnType::Domain { oid, base, .. } => {
36 targets.contains(oid) || references_domain(base, targets)
37 }
38 ColumnType::Array(element) => references_domain(element, targets),
39 _ => false,
40 }
41}
42
43pub fn type_name_references_domain(
44 types: &dyn DomainTypeCatalog,
45 name: &str,
46 targets: &BTreeSet<u32>,
47) -> bool {
48 types
49 .resolve_domain_type_reference(name)
50 .is_some_and(|ty| references_domain(&ty, targets))
51}
52
53pub fn expression_references_domain(
54 types: &dyn DomainTypeCatalog,
55 expression: &Expr,
56 targets: &BTreeSet<u32>,
57) -> Result<bool, SQLError> {
58 Ok(
59 crate::catalog::stored_ast::stored_expression_type_names(expression)?
60 .iter()
61 .any(|name| type_name_references_domain(types, name, targets)),
62 )
63}
64
65pub fn routine_references_domain(
66 types: &dyn DomainTypeCatalog,
67 definition: &CreateFunction,
68 targets: &BTreeSet<u32>,
69) -> Result<bool, SQLError> {
70 for param in &definition.params {
71 if type_name_references_domain(types, ¶m.type_name, targets) {
72 return Ok(true);
73 }
74 if let Some(default) = ¶m.default {
75 if expression_references_domain(types, default, targets)? {
76 return Ok(true);
77 }
78 }
79 }
80 if let FunctionReturns::Scalar { type_name } | FunctionReturns::SetOf { type_name } =
81 &definition.returns
82 {
83 if type_name_references_domain(types, type_name, targets) {
84 return Ok(true);
85 }
86 }
87 if let FunctionBody::Statements(statements) = &definition.body {
88 for statement in statements {
89 let mut merge_assignment_depends = false;
90 crate::catalog::stored_ast::visit_stored_statement_merges(
91 &mut statement.clone(),
92 &mut |merge| {
93 merge_assignment_depends |= merge
94 .target_column_bindings
95 .values()
96 .any(|binding| !binding.domain_dependencies.is_disjoint(targets));
97 Ok(())
98 },
99 )?;
100 if merge_assignment_depends {
101 return Ok(true);
102 }
103 if crate::catalog::stored_ast::stored_statement_type_names(statement)?
104 .iter()
105 .any(|name| type_name_references_domain(types, name, targets))
106 {
107 return Ok(true);
108 }
109 }
110 }
111 Ok(false)
112}
113
114pub fn expand_domain_drop_targets(
115 types: &dyn DomainTypeCatalog,
116 registry: &BTreeMap<String, StoredDomain>,
117 targets: &mut BTreeSet<u32>,
118 routines: &[crate::ast::FunctionBinding],
119) -> Result<(), SQLError> {
120 loop {
121 let previous = targets.len();
122 for domain in registry.values() {
123 let mut depends = references_domain(&domain.definition.base, targets);
124 if let Some(default) = &domain.definition.default {
125 depends |= expression_references_domain(types, default, targets)?;
126 for routine in routines {
127 depends |= crate::catalog::stored_ast::expression_references_routine_identity(
128 default, routine,
129 )?;
130 }
131 }
132 if depends {
133 targets.insert(domain.oid);
134 }
135 }
136 if previous == targets.len() {
137 break;
138 }
139 }
140 Ok(())
141}
142
143pub fn domain_checks_depending_on_routines(
144 registry: BTreeMap<String, StoredDomain>,
145 routines: &[crate::ast::FunctionBinding],
146) -> Result<Vec<(String, String)>, SQLError> {
147 let mut checks = BTreeSet::new();
148 for (name, domain) in registry {
149 for check in &domain.definition.checks {
150 for routine in routines {
151 if crate::catalog::stored_ast::expression_references_routine_identity(
152 &check.expression,
153 routine,
154 )? {
155 checks.insert((
156 name.clone(),
157 check
158 .name
159 .clone()
160 .ok_or_else(|| SQLError::Internal("domain CHECK has no name".into()))?,
161 ));
162 }
163 }
164 }
165 }
166 Ok(checks.into_iter().collect())
167}
168
169pub fn domain_schema_dependents(
170 types: &dyn DomainTypeCatalog,
171 table: &str,
172 columns: &[ColumnDef],
173 checks: &[TableCheck],
174 foreign: bool,
175 targets: &BTreeSet<u32>,
176 dependents: &mut DomainDependents,
177) -> Result<(), SQLError> {
178 for column in columns {
179 let target = (table.to_string(), column.name.clone(), foreign);
180 let mut drop_column = references_domain(&column.ty, targets);
181 if let Some(generated) = &column.generated {
182 drop_column |= expression_references_domain(types, &generated.expression, targets)?;
183 }
184 if drop_column {
185 dependents.columns.insert(target.clone());
186 }
187 if let Some(default) = &column.default {
188 if expression_references_domain(types, default, targets)? {
189 dependents.defaults.insert(target);
190 }
191 }
192 if let Some(check) = &column.check {
193 if expression_references_domain(types, check, targets)? {
194 let name = column.check_name.clone().ok_or_else(|| {
195 SQLError::Internal("domain dependent CHECK has no name".into())
196 })?;
197 dependents.checks.insert((table.to_string(), name, foreign));
198 }
199 }
200 }
201 for check in checks {
202 if expression_references_domain(types, &check.expr, targets)? {
203 let name = check
204 .name
205 .clone()
206 .ok_or_else(|| SQLError::Internal("domain dependent CHECK has no name".into()))?;
207 dependents.checks.insert((table.to_string(), name, foreign));
208 }
209 }
210 loop {
211 let previous = dependents.columns.len();
212 let removed = dependents
213 .columns
214 .iter()
215 .filter(|(name, _, _)| name == table)
216 .map(|(_, column, _)| column.clone())
217 .collect::<Vec<_>>();
218 for column in columns {
219 let Some(generated) = &column.generated else {
220 continue;
221 };
222 for removed in &removed {
223 if crate::schema::dependencies::schema_expr_references_column(
224 &generated.expression,
225 removed,
226 ) {
227 dependents
228 .columns
229 .insert((table.to_string(), column.name.clone(), foreign));
230 }
231 }
232 }
233 if previous == dependents.columns.len() {
234 break;
235 }
236 }
237 Ok(())
238}
239
240pub fn views_referencing_domains(
241 types: &dyn DomainTypeCatalog,
242 registry: BTreeMap<RelationIdentity, StoredView>,
243 targets: &BTreeSet<u32>,
244) -> BTreeSet<String> {
245 let mut views = BTreeSet::new();
246 for (identity, mut view) in registry {
247 let mut depends = false;
248 view.query.rewrite_scalar_expressions(&mut |expression| {
249 if let ScalarExpr::Cast { ty, .. } | ScalarExpr::TypedLiteral { ty, .. } = expression {
250 depends |= type_name_references_domain(types, ty, targets);
251 }
252 });
253 if depends {
254 views.insert(identity.qualified_name());
255 }
256 }
257 views
258}
259
260pub fn remove_domain_references(
261 types: &dyn DomainTypeCatalog,
262 registry: &mut BTreeMap<String, StoredDomain>,
263 targets: &BTreeSet<u32>,
264) -> Result<(), SQLError> {
265 registry.retain(|_, domain| !targets.contains(&domain.oid));
266 for domain in registry.values_mut() {
267 if let Some(default) = &domain.definition.default {
268 if expression_references_domain(types, default, targets)? {
269 domain.definition.default = None;
270 }
271 }
272 let mut checks = Vec::new();
273 for check in &domain.definition.checks {
274 if !expression_references_domain(types, &check.expression, targets)? {
275 checks.push(check.clone());
276 }
277 }
278 domain.definition.checks = checks;
279 }
280 Ok(())
281}
282
283pub fn remove_domain_routine_checks(
284 registry: &mut BTreeMap<String, StoredDomain>,
285 checks: Vec<(String, String)>,
286) -> Result<(), SQLError> {
287 for (domain, name) in checks {
288 let domain = registry
289 .get_mut(&domain)
290 .ok_or_else(|| SQLError::Internal("dependent domain disappeared".into()))?;
291 domain
292 .definition
293 .checks
294 .retain(|check| check.name.as_deref() != Some(&name));
295 }
296 Ok(())
297}
298
299pub fn parse_domain_index_keys(keys: &str) -> Result<Vec<IndexKey>, SQLError> {
300 serde_json::from_str(keys).map_err(|error| SQLError::Internal(error.to_string()))
301}
302
303pub fn index_references_domain(
304 types: &dyn DomainTypeCatalog,
305 definition: &IndexDefinition,
306 keys: &[IndexKey],
307 targets: &BTreeSet<u32>,
308) -> Result<bool, SQLError> {
309 let mut depends = definition
310 .key_types
311 .iter()
312 .any(|ty| references_domain(ty, targets));
313 for expression in keys
314 .iter()
315 .filter_map(|key| match key {
316 IndexKey::Expression(expression) => Some(expression.as_ref()),
317 IndexKey::Column(_) => None,
318 })
319 .chain(definition.predicate.as_deref())
320 {
321 depends |= expression_references_domain(types, expression, targets)?;
322 }
323 Ok(depends)
324}