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