Skip to main content

uqa_sql/routines/
regclass.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Creation-time relation identities in SQL-standard bodies and argument defaults.
8
9use 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(&parameter.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}