1pub mod attributes;
10pub mod body_parameters;
11pub mod body_validation;
12pub mod compilation;
13pub mod configuration;
14pub mod declaration;
15mod defaults;
16pub mod definition_output;
17pub mod dependencies;
18pub mod inlining;
19pub mod lifecycle;
20pub mod merge_columns;
21pub mod privilege_inquiry;
22pub mod regclass;
23pub mod registration;
24pub mod resolution;
25pub mod result_check;
26pub mod security;
27
28use crate::ast::{
29 ColumnType, CreateFunction, FunctionBinding, FunctionReturns, RoutineInvocationBinding,
30};
31use crate::plan::UnifiedPlan;
32use crate::type_resolution::{
33 canonical_routine_type_name, BuiltinFunctionOverload, FunctionTypeResolver,
34 RankedFunctionMatch, ResolvedFunctionOverload,
35};
36use crate::SQLError;
37use std::sync::{Arc, OnceLock};
38
39#[derive(Clone)]
41pub struct SQLUserFunction {
42 pub def: CreateFunction,
43 pub body: RoutineBody,
44 version: OnceLock<u64>,
45}
46
47impl SQLUserFunction {
48 #[must_use]
49 pub const fn new(def: CreateFunction, body: RoutineBody) -> Self {
50 Self {
51 def,
52 body,
53 version: OnceLock::new(),
54 }
55 }
56
57 #[must_use]
59 pub fn catalog_revision(&self) -> Option<[u8; 16]> {
60 self.def.catalog_revision.or(self.def.object_id)
61 }
62
63 pub fn definition_version(&self) -> Result<u64, SQLError> {
65 if let Some(version) = self.version.get() {
66 return Ok(*version);
67 }
68 let encoded = serde_json::to_vec(&self.def).map_err(|error| {
69 SQLError::Internal(format!(
70 "encode routine `{}` definition: {error}",
71 self.def.name
72 ))
73 })?;
74 let version = encoded
76 .iter()
77 .fold(0xcbf2_9ce4_8422_2325_u64, |hash, byte| {
78 (hash ^ u64::from(*byte)).wrapping_mul(0x0000_0100_0000_01b3)
79 });
80 Ok(*self.version.get_or_init(|| version))
81 }
82}
83
84#[derive(Clone)]
87pub enum RoutineBody {
88 Bound(Arc<CompiledFunctionBody>),
90 Source,
92}
93
94pub fn analyzable_routine_body(
96 resolution: &(impl RoutineResolution + ?Sized),
97 function: &SQLUserFunction,
98) -> Result<Option<Arc<CompiledFunctionBody>>, SQLError> {
99 match resolution.routine_analysis_body(function) {
100 Ok(body) => Ok(Some(body)),
101 Err(SQLError::Internal(message)) => Err(SQLError::Internal(message)),
102 Err(_) => Ok(None),
103 }
104}
105
106#[allow(clippy::upper_case_acronyms)]
109#[derive(Clone)]
110pub enum CompiledFunctionBody {
111 PLpgSQL(crate::plpgsql::PLpgSQLFunction),
112 SQL(Vec<UnifiedPlan>),
113}
114pub fn is_routine_namespace_lookup_error(error: &SQLError) -> bool {
115 matches!(
116 error,
117 SQLError::Routine { sqlstate, message }
118 if sqlstate == "3F000"
119 || (sqlstate == "42501"
120 && message.starts_with("permission denied for schema "))
121 )
122}
123
124pub trait RoutineResolution: FunctionTypeResolver {
126 fn has_registered_scalar_function(&self, _name: &str) -> bool {
127 false
128 }
129
130 fn routine_body(
132 &self,
133 function: &SQLUserFunction,
134 ) -> Result<Arc<CompiledFunctionBody>, SQLError> {
135 match &function.body {
136 RoutineBody::Bound(body) => Ok(Arc::clone(body)),
137 RoutineBody::Source => Err(SQLError::Internal(format!(
138 "routine `{}` has no session to compile its body",
139 function.def.name
140 ))),
141 }
142 }
143
144 fn routine_analysis_body(
148 &self,
149 function: &SQLUserFunction,
150 ) -> Result<Arc<CompiledFunctionBody>, SQLError> {
151 self.routine_body(function)
152 }
153
154 fn has_registered_table_function(&self, _name: &str) -> bool {
155 false
156 }
157
158 fn has_registered_aggregate_function(&self, _name: &str) -> bool {
159 false
160 }
161
162 fn lookup_visible_sql_functions(
163 &self,
164 _name: &str,
165 ) -> Result<Option<Vec<Arc<SQLUserFunction>>>, SQLError> {
166 Ok(None)
167 }
168
169 fn lookup_visible_sql_functions_for_analysis(
171 &self,
172 name: &str,
173 ) -> Result<Option<Vec<Arc<SQLUserFunction>>>, SQLError> {
174 self.lookup_visible_sql_functions(name)
175 }
176
177 fn lookup_bound_sql_functions(&self, _name: &str) -> Option<Vec<Arc<SQLUserFunction>>> {
179 None
180 }
181
182 fn lookup_bound_sql_functions_by_binding(
183 &self,
184 _binding: &FunctionBinding,
185 ) -> Option<Vec<Arc<SQLUserFunction>>> {
186 None
187 }
188
189 fn resolve_static_sql_function(
190 &self,
191 _name: &str,
192 _binding: Option<&FunctionBinding>,
193 _argument_names: &[Option<String>],
194 _argument_types: &[Option<ColumnType>],
195 _explicit_variadic: bool,
196 ) -> Result<Option<Arc<SQLUserFunction>>, SQLError> {
197 Ok(None)
198 }
199
200 fn resolve_static_sql_function_match(
201 &self,
202 _name: &str,
203 _binding: Option<&FunctionBinding>,
204 _argument_names: &[Option<String>],
205 _argument_types: &[Option<ColumnType>],
206 _explicit_variadic: bool,
207 ) -> Result<Option<StaticFunctionMatch>, SQLError> {
208 Ok(None)
209 }
210
211 fn resolve_table_function_overload_with_builtins(
212 &self,
213 _name: &str,
214 _binding: Option<&FunctionBinding>,
215 _argument_names: &[Option<String>],
216 _argument_types: &[Option<ColumnType>],
217 _explicit_variadic: bool,
218 _builtins: &[BuiltinFunctionOverload],
219 ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
220 Ok(None)
221 }
222}
223
224pub fn routine_signature_types(def: &CreateFunction) -> Vec<String> {
225 def.identity_params()
226 .iter()
227 .map(|parameter| canonical_routine_type_name(¶meter.type_name))
228 .collect()
229}
230
231pub fn routine_returns_anonymous_record(def: &CreateFunction) -> bool {
232 def.output_params().is_empty()
233 && matches!(
234 &def.returns,
235 FunctionReturns::Scalar { type_name } | FunctionReturns::SetOf { type_name }
236 if canonical_routine_type_name(type_name) == "record"
237 )
238}
239
240pub struct StaticFunctionMatch {
241 pub function: Arc<SQLUserFunction>,
242 pub invocation: Box<RoutineInvocationBinding>,
243 pub argument_types: Vec<String>,
244 pub raw_exact_matches: usize,
245 pub exact_matches: usize,
246 pub preferred_matches: usize,
247 pub variadic_expansion: bool,
248 pub default_error: Option<crate::type_resolution::RoutineSignatureMatchError>,
249}
250
251impl StaticFunctionMatch {
252 pub fn binding(&self) -> FunctionBinding {
253 FunctionBinding {
254 object_id: self.function.def.object_id,
255 name: self.function.def.name.clone(),
256 argument_types: routine_signature_types(&self.function.def),
257 builtin: false,
258 dispatch: None,
259 invocation: Some(self.invocation.clone()),
260 composite_field: None,
261 resolution_error: None,
262 }
263 }
264}
265
266impl RankedFunctionMatch for StaticFunctionMatch {
267 fn argument_types(&self) -> &[String] {
268 &self.argument_types
269 }
270
271 fn raw_exact_matches(&self) -> usize {
272 self.raw_exact_matches
273 }
274
275 fn exact_matches(&self) -> usize {
276 self.exact_matches
277 }
278
279 fn preferred_matches(&self) -> usize {
280 self.preferred_matches
281 }
282
283 fn is_variadic_expansion(&self) -> bool {
284 self.variadic_expansion
285 }
286}
287
288pub fn builtin_routine_support_oid(name: &str) -> Option<i64> {
289 Some(match name.strip_prefix("pg_catalog.").unwrap_or(name) {
290 "textlike_support" => 1023,
291 "texticregexeq_support" => 1024,
292 "texticlike_support" => 1025,
293 "network_subset_support" => 1173,
294 "textregexeq_support" => 1364,
295 "varchar_support" => 3097,
296 "numeric_support" => 3157,
297 _ => return None,
298 })
299}
300
301pub fn function_binding_matches(binding: &FunctionBinding, target: &FunctionBinding) -> bool {
302 if binding.builtin || target.builtin {
303 return false;
304 }
305 match (binding.object_id, target.object_id) {
306 (Some(binding), Some(target)) => binding == target,
307 (None, None) => {
308 binding.name == target.name && binding.argument_types == target.argument_types
309 }
310 _ => false,
311 }
312}
313
314pub fn routine_local_name(name: &str) -> Result<String, SQLError> {
315 uqa_core::RelationIdentity::from_legacy_name(name)
316 .map(|relation| relation.name)
317 .map_err(|error| SQLError::Internal(format!("invalid routine name `{name}`: {error}")))
318}
319
320pub fn routine_kind(def: &CreateFunction) -> &'static str {
321 if def.is_procedure {
322 "procedure"
323 } else {
324 "function"
325 }
326}
327
328pub mod anonymous_block;
329pub mod call;
330pub mod invocation;