uqa_sql/routines/
regclass.rs1use super::{declaration::RoutineTypeCatalog, lifecycle::relations::is_regclass};
10use crate::{
11 ast::{ColumnType, CreateFunction, Expr, FunctionBody},
12 SQLError,
13};
14use uqa_core::Value;
15
16pub trait RoutineRegclassCatalog {
17 fn resolve_routine_regclass(&self, reference: &str) -> Result<Option<i64>, SQLError>;
18}
19pub fn bind_routine_regclass_constants(
20 types: &dyn RoutineTypeCatalog,
21 relations: &dyn RoutineRegclassCatalog,
22 definition: &mut CreateFunction,
23) -> Result<bool, SQLError> {
24 RegclassBinding { types, relations }.bind_routine_constants(definition)
25}
26struct RegclassBinding<'a> {
27 types: &'a dyn RoutineTypeCatalog,
28 relations: &'a dyn RoutineRegclassCatalog,
29}
30impl RegclassBinding<'_> {
31 fn regclass_base_type(&self, name: &str) -> bool {
32 let Some(mut ty) = self.types.resolve_catalog_column_type(name) else {
33 return false;
34 };
35 while let ColumnType::Domain { base, .. } = ty {
36 ty = *base;
37 }
38 matches!(ty, ColumnType::Regclass)
39 }
40
41 fn bind_regclass_literal(&self, expression: &mut Expr) -> Result<bool, SQLError> {
42 if let Expr::Func { binding, args, .. } = expression {
43 if binding.as_ref().and_then(|binding| binding.dispatch)
44 == Some(crate::ast::FunctionDispatch::NamedArgument)
45 {
46 return args
47 .get_mut(1)
48 .map_or(Ok(false), |argument| self.bind_regclass_literal(argument));
49 }
50 }
51 let Expr::Literal(Value::Str(reference)) = expression else {
52 return Ok(false);
53 };
54 let oid = self
55 .relations
56 .resolve_routine_regclass(reference)?
57 .ok_or_else(|| SQLError::Routine {
58 sqlstate: "42P01".into(),
59 message: format!("relation \"{reference}\" does not exist"),
60 })?;
61 *expression = Expr::TypedLiteral {
62 collation_oid: None,
63 composite_source: None,
64 value: Value::Int(oid),
65 ty: ColumnType::Regclass.catalog_name(),
66 };
67 Ok(true)
68 }
69
70 fn bind_regclass_expression(&self, expression: &mut Expr) -> Result<bool, SQLError> {
71 match expression {
72 Expr::Cast { expr, ty, .. } if is_regclass(ty) => self.bind_regclass_literal(expr),
73 Expr::Func {
74 name,
75 binding,
76 args,
77 ..
78 } if binding.as_ref().is_none_or(|binding| binding.builtin)
79 && matches!(
80 name.strip_prefix("pg_catalog.").unwrap_or(name),
81 "nextval" | "currval" | "setval"
82 ) =>
83 {
84 args.first_mut()
85 .map_or(Ok(false), |argument| self.bind_regclass_literal(argument))
86 }
87 Expr::Func {
88 binding: Some(binding),
89 args,
90 ..
91 } => {
92 let targets = binding
93 .invocation
94 .as_ref()
95 .map_or(binding.argument_types.as_slice(), |invocation| {
96 invocation.argument_targets.as_slice()
97 });
98 let mut changed = false;
99 for (argument, target) in args.iter_mut().zip(targets) {
100 if self.regclass_base_type(target) {
101 changed |= self.bind_regclass_literal(argument)?;
102 }
103 }
104 Ok(changed)
105 }
106 _ => Ok(false),
107 }
108 }
109
110 fn bind_routine_constants(&self, definition: &mut CreateFunction) -> Result<bool, SQLError> {
111 let mut changed = false;
112 for parameter in &mut definition.params {
113 if let Some(default) = &mut parameter.default {
114 if self.regclass_base_type(¶meter.type_name) {
115 changed |= self.bind_regclass_literal(default)?;
116 }
117 crate::catalog::stored_ast::visit_stored_expression(default, &mut |expression| {
118 changed |= self.bind_regclass_expression(expression)?;
119 Ok(())
120 })?;
121 }
122 }
123 if let FunctionBody::Statements(statements) = &mut definition.body {
124 for statement in statements {
125 crate::catalog::stored_ast::visit_stored_statement_expressions(
126 statement,
127 &mut |expression| {
128 changed |= self.bind_regclass_expression(expression)?;
129 Ok(())
130 },
131 )?;
132 }
133 }
134 Ok(changed)
135 }
136}