uqa_sql/routines/lifecycle/
relations.rs1use crate::{
10 ast::{CreateFunction, Expr, FunctionBody},
11 SQLError,
12};
13use std::collections::BTreeSet;
14use uqa_core::Value;
15
16pub trait RoutineRelationOids {
17 fn bound_regclass_oid(&self, name: &str) -> Result<Option<i64>, SQLError>;
18}
19
20pub fn is_regclass(name: &str) -> bool {
21 name.eq_ignore_ascii_case("regclass") || name.eq_ignore_ascii_case("pg_catalog.regclass")
22}
23
24pub fn regclass_oid(expression: &Expr) -> Option<i64> {
25 match expression {
26 Expr::TypedLiteral {
27 value: Value::Int(oid),
28 ty,
29 ..
30 } if is_regclass(ty) => Some(*oid),
31 Expr::Cast { expr, ty, .. } if is_regclass(ty) => regclass_oid(expr),
32 _ => None,
33 }
34}
35
36pub fn stored_routine_references_relations(
37 catalog: &dyn RoutineRelationOids,
38 definition: &CreateFunction,
39 relations: &BTreeSet<String>,
40) -> Result<bool, SQLError> {
41 if relations.is_empty() {
42 return Ok(false);
43 }
44 let mut oids = BTreeSet::new();
45 for relation in relations {
46 if let Some(oid) = catalog.bound_regclass_oid(relation)? {
47 oids.insert(oid);
48 }
49 }
50 let mut depends = false;
51 let mut visit = |expression: &mut crate::ast::Expr| {
52 depends |= regclass_oid(expression).is_some_and(|oid| oids.contains(&oid));
53 Ok(())
54 };
55 for default in definition
56 .params
57 .iter()
58 .filter_map(|parameter| parameter.default.as_ref())
59 {
60 crate::catalog::stored_ast::visit_stored_expression(&mut default.clone(), &mut visit)?;
61 }
62 if let FunctionBody::Statements(statements) = &definition.body {
63 for statement in statements {
64 if crate::catalog::stored_ast::stored_statement_relation_names(statement)?
65 .iter()
66 .any(|name| relations.contains(name))
67 {
68 return Ok(true);
69 }
70 crate::catalog::stored_ast::visit_stored_statement_expressions(
71 &mut statement.clone(),
72 &mut visit,
73 )?;
74 }
75 }
76 Ok(depends)
77}