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            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(&parameter.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}