uqa_sql/routines/
registration.rs1use super::{builtin_routine_support_oid, lifecycle::ensure_routine_owner_as, routine_kind};
10use crate::catalog::roles::identity::RoleSubject;
11use crate::{
12 ast::{AlterRoutineStmt, CreateFunction},
13 catalog::roles::{role_inherits, RoleDefinition, RoleMembership, RoleMembershipKey},
14 type_resolution::canonical_routine_type_name,
15 SQLError,
16};
17use std::collections::BTreeMap;
18
19pub trait RoutineSupportAuthority {
20 fn current_user_is_superuser(&self) -> bool;
21}
22pub fn validate_routine_support(
23 authority: &dyn RoutineSupportAuthority,
24 support: &str,
25) -> Result<(), SQLError> {
26 if builtin_routine_support_oid(support).is_none() {
27 return Err(SQLError::Routine {
28 sqlstate: "42883".into(),
29 message: format!("function {support}(internal) does not exist"),
30 });
31 }
32 if !authority.current_user_is_superuser() {
33 return Err(SQLError::Routine {
34 sqlstate: "42501".into(),
35 message: "must be superuser to specify a support function".into(),
36 });
37 }
38 Ok(())
39}
40
41pub fn validate_routine_security_attributes(
42 def: &CreateFunction,
43 current_user_is_superuser: bool,
44) -> Result<(), SQLError> {
45 if (def.security.leakproof || def.support.is_some()) && !current_user_is_superuser {
46 return Err(SQLError::Routine {
47 sqlstate: "42501".into(),
48 message: if def.security.leakproof {
49 "only superuser can define a leakproof function".into()
50 } else {
51 "must be superuser to specify a support function".into()
52 },
53 });
54 }
55 Ok(())
56}
57
58pub fn prepare_routine_replacement(
59 existing: &CreateFunction,
60 def: &mut CreateFunction,
61 requested_name: &str,
62 current_user: &(impl RoleSubject + ?Sized),
63 roles: &BTreeMap<String, RoleDefinition>,
64 memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
65) -> Result<(), SQLError> {
66 if !def.or_replace {
67 let kind = routine_kind(def);
68 return Err(SQLError::Routine {
69 sqlstate: "42723".into(),
70 message: format!("{kind} \"{requested_name}\" already exists with same argument types"),
71 });
72 }
73 ensure_routine_owner_as(
74 existing,
75 role_inherits(
76 roles,
77 memberships,
78 current_user,
79 &crate::routines::security::bound_routine_owner(existing)?,
80 ),
81 )?;
82 if existing.is_procedure != def.is_procedure {
83 return Err(SQLError::Routine {
84 sqlstate: "42809".into(),
85 message: "cannot change routine kind".into(),
86 });
87 }
88 if !same_return_shape(existing, def) {
89 return Err(SQLError::Routine {
90 sqlstate: "42P13".into(),
91 message: "cannot change return type of existing function".into(),
92 });
93 }
94 def.object_id = Some(existing.object_id.ok_or_else(|| {
96 SQLError::Internal(format!(
97 "existing routine `{}` has no catalog object identity",
98 existing.name,
99 ))
100 })?);
101 def.owner = existing.owner;
102 def.execute_acl.clone_from(&existing.execute_acl);
103 Ok(())
104}
105
106pub fn alter_routine_attributes(
107 existing: &CreateFunction,
108 stmt: &AlterRoutineStmt,
109 current_user_is_superuser: bool,
110 authority: &dyn RoutineSupportAuthority,
111) -> Result<CreateFunction, SQLError> {
112 if existing.is_procedure
113 && (stmt.volatility.is_some()
114 || stmt.strict.is_some()
115 || stmt.leakproof.is_some()
116 || stmt.parallel.is_some()
117 || stmt.support.is_some())
118 {
119 return Err(SQLError::Routine {
120 sqlstate: "42P13".into(),
121 message: "invalid attribute in procedure definition".into(),
122 });
123 }
124
125 let mut def = existing.clone();
126 if let Some(volatility) = stmt.volatility {
127 def.volatility = volatility;
128 }
129 if let Some(strict) = stmt.strict {
130 def.strict = strict;
131 }
132 if let Some(security_definer) = stmt.security_definer {
133 def.security.security_definer = security_definer;
134 }
135 if let Some(leakproof) = stmt.leakproof {
136 if leakproof && !current_user_is_superuser {
137 return Err(SQLError::Routine {
138 sqlstate: "42501".into(),
139 message: "only superuser can define a leakproof function".into(),
140 });
141 }
142 def.security.leakproof = leakproof;
143 }
144 if let Some(parallel) = stmt.parallel {
145 def.parallel = parallel;
146 }
147 if let Some(support) = &stmt.support {
148 validate_routine_support(authority, support)?;
149 def.support = Some(support.clone());
150 }
151 def.config_actions.clone_from(&stmt.config_actions);
152 Ok(def)
153}
154
155fn same_return_shape(a: &CreateFunction, b: &CreateFunction) -> bool {
156 use crate::ast::FunctionReturns;
157 let same_outputs = {
158 let a_outs = a.output_params();
159 let b_outs = b.output_params();
160 a_outs.len() == b_outs.len()
161 && a_outs.iter().zip(&b_outs).all(|(x, y)| {
162 x.name == y.name
163 && canonical_routine_type_name(&x.type_name)
164 == canonical_routine_type_name(&y.type_name)
165 && x.mode == y.mode
166 })
167 };
168 let same_kind = match (&a.returns, &b.returns) {
169 (FunctionReturns::None, FunctionReturns::None)
170 | (FunctionReturns::Table, FunctionReturns::Table) => true,
171 (FunctionReturns::Scalar { type_name: x }, FunctionReturns::Scalar { type_name: y })
172 | (FunctionReturns::SetOf { type_name: x }, FunctionReturns::SetOf { type_name: y }) => {
173 canonical_routine_type_name(x) == canonical_routine_type_name(y)
174 }
175 _ => false,
176 };
177 same_kind && same_outputs
178}