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 value: Value::Int(oid),
63 ty: "regclass".into(),
64 };
65 Ok(true)
66 }
67
68 fn bind_regclass_expression(&self, expression: &mut Expr) -> Result<bool, SQLError> {
69 match expression {
70 Expr::Cast { expr, ty } if is_regclass(ty) => self.bind_regclass_literal(expr),
71 Expr::Func {
72 name,
73 binding,
74 args,
75 ..
76 } if binding.as_ref().is_none_or(|binding| binding.builtin)
77 && matches!(
78 name.strip_prefix("pg_catalog.").unwrap_or(name),
79 "nextval" | "currval" | "setval"
80 ) =>
81 {
82 args.first_mut()
83 .map_or(Ok(false), |argument| self.bind_regclass_literal(argument))
84 }
85 Expr::Func {
86 binding: Some(binding),
87 args,
88 ..
89 } => {
90 let targets = binding
91 .invocation
92 .as_ref()
93 .map_or(binding.argument_types.as_slice(), |invocation| {
94 invocation.argument_targets.as_slice()
95 });
96 let mut changed = false;
97 for (argument, target) in args.iter_mut().zip(targets) {
98 if self.regclass_base_type(target) {
99 changed |= self.bind_regclass_literal(argument)?;
100 }
101 }
102 Ok(changed)
103 }
104 _ => Ok(false),
105 }
106 }
107
108 fn bind_routine_constants(&self, definition: &mut CreateFunction) -> Result<bool, SQLError> {
109 let mut changed = false;
110 for parameter in &mut definition.params {
111 if let Some(default) = &mut parameter.default {
112 if self.regclass_base_type(¶meter.type_name) {
113 changed |= self.bind_regclass_literal(default)?;
114 }
115 crate::catalog::stored_ast::visit_stored_expression(default, &mut |expression| {
116 changed |= self.bind_regclass_expression(expression)?;
117 Ok(())
118 })?;
119 }
120 }
121 if let FunctionBody::Statements(statements) = &mut definition.body {
122 for statement in statements {
123 crate::catalog::stored_ast::visit_stored_statement_expressions(
124 statement,
125 &mut |expression| {
126 changed |= self.bind_regclass_expression(expression)?;
127 Ok(())
128 },
129 )?;
130 }
131 }
132 Ok(changed)
133 }
134}