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