uqa_sql/routines/lifecycle/
relations.rs1use super::{RoutineDropResolution, RoutineDropTarget, RoutineRegistry};
10use crate::{
11 ast::{CreateFunction, Expr, FunctionBody},
12 routines::routine_signature_types,
13 SQLError,
14};
15use std::collections::BTreeSet;
16use uqa_core::Value;
17
18pub trait RoutineRelationOids {
19 fn bound_regclass_oid(&self, name: &str) -> Result<Option<i64>, SQLError>;
20}
21
22pub fn is_regclass(name: &str) -> bool {
23 name.eq_ignore_ascii_case("regclass") || name.eq_ignore_ascii_case("pg_catalog.regclass")
24}
25
26pub fn regclass_oid(expression: &Expr) -> Option<i64> {
27 match expression {
28 Expr::TypedLiteral {
29 value: Value::Int(oid),
30 ty,
31 } if is_regclass(ty) => Some(*oid),
32 Expr::Cast { expr, ty } if is_regclass(ty) => regclass_oid(expr),
33 _ => None,
34 }
35}
36
37pub fn stored_routine_references_columns(
38 catalog: crate::binding::stored_columns::StoredColumnBindingContext<'_>,
39 definition: &CreateFunction,
40 columns: &BTreeSet<(String, String)>,
41) -> Result<bool, SQLError> {
42 let FunctionBody::Statements(statements) = &definition.body else {
43 return Ok(false);
44 };
45 if columns.is_empty() {
46 return Ok(false);
47 }
48 for statement in statements {
49 let dependencies = crate::binding::stored_columns::stored_statement_column_dependencies(
50 catalog, statement,
51 )?;
52 if dependencies.iter().any(|dependency| {
53 columns.contains(&(
54 dependency.relation.qualified_name(),
55 dependency.column.clone(),
56 ))
57 }) {
58 return Ok(true);
59 }
60 }
61 Ok(false)
62}
63
64pub fn stored_routine_references_relations(
65 catalog: &dyn RoutineRelationOids,
66 definition: &CreateFunction,
67 relations: &BTreeSet<String>,
68) -> Result<bool, SQLError> {
69 if relations.is_empty() {
70 return Ok(false);
71 }
72 let mut oids = BTreeSet::new();
73 for relation in relations {
74 if let Some(oid) = catalog.bound_regclass_oid(relation)? {
75 oids.insert(oid);
76 }
77 }
78 let mut depends = false;
79 let mut visit = |expression: &mut crate::ast::Expr| {
80 depends |= regclass_oid(expression).is_some_and(|oid| oids.contains(&oid));
81 Ok(())
82 };
83 for default in definition
84 .params
85 .iter()
86 .filter_map(|parameter| parameter.default.as_ref())
87 {
88 crate::catalog::stored_ast::visit_stored_expression(&mut default.clone(), &mut visit)?;
89 }
90 if let FunctionBody::Statements(statements) = &definition.body {
91 for statement in statements {
92 if crate::catalog::stored_ast::stored_statement_relation_names(statement)?
93 .iter()
94 .any(|name| relations.contains(name))
95 {
96 return Ok(true);
97 }
98 crate::catalog::stored_ast::visit_stored_statement_expressions(
99 &mut statement.clone(),
100 &mut visit,
101 )?;
102 }
103 }
104 Ok(depends)
105}
106
107pub fn schema_routine_drop_targets(
108 registry: &RoutineRegistry,
109 schemas: &BTreeSet<String>,
110) -> Result<RoutineDropResolution, SQLError> {
111 let mut resolution = RoutineDropResolution {
112 targets: Vec::new(),
113 seen_targets: BTreeSet::new(),
114 notices: Vec::new(),
115 };
116 for (name, overloads) in registry {
117 for function in overloads {
118 let identity =
119 uqa_core::RelationIdentity::from_legacy_name(name).map_err(SQLError::Internal)?;
120 let mut depends = schemas.contains(&identity.schema);
121 if let crate::ast::FunctionBody::Statements(statements) = &function.def.body {
122 for statement in statements {
123 for relation in
124 crate::catalog::stored_ast::stored_statement_relation_names(statement)?
125 {
126 let relation = uqa_core::RelationIdentity::from_legacy_name(&relation)
127 .map_err(SQLError::Internal)?;
128 depends |= schemas.contains(&relation.schema);
129 }
130 }
131 }
132 if depends {
133 let target = RoutineDropTarget {
134 object_id: function.def.object_id,
135 name: name.clone(),
136 argument_types: routine_signature_types(&function.def),
137 is_procedure: function.def.is_procedure,
138 };
139 resolution.seen_targets.insert(target.clone());
140 resolution.targets.push(target);
141 }
142 }
143 }
144 Ok(resolution)
145}