1pub mod compilation;
10pub mod configuration;
11pub mod declaration;
12pub mod dependencies;
13pub mod lifecycle;
14pub mod merge_columns;
15pub mod privilege_inquiry;
16pub mod regclass;
17pub mod registration;
18pub mod resolution;
19pub mod security;
20
21use crate::ast::{
22 ColumnType, CreateFunction, FunctionBinding, FunctionReturns, RoutineInvocationBinding,
23};
24use crate::plan::UnifiedPlan;
25use crate::type_resolution::{
26 canonical_routine_type_name, BuiltinFunctionOverload, FunctionTypeResolver,
27 RankedFunctionMatch, ResolvedFunctionOverload,
28};
29use crate::SQLError;
30use std::sync::Arc;
31
32#[derive(Clone)]
35pub struct SQLUserFunction {
36 pub def: CreateFunction,
37 pub compiled: CompiledFunctionBody,
38}
39
40#[allow(clippy::upper_case_acronyms)]
43#[derive(Clone)]
44pub enum CompiledFunctionBody {
45 PLpgSQL(crate::plpgsql::PLpgSQLFunction),
46 SQL(Vec<UnifiedPlan>),
47}
48pub fn is_routine_namespace_lookup_error(error: &SQLError) -> bool {
49 matches!(
50 error,
51 SQLError::Routine { sqlstate, message }
52 if sqlstate == "3F000"
53 || (sqlstate == "42501"
54 && message.starts_with("permission denied for schema "))
55 )
56}
57
58pub trait RoutineResolution: FunctionTypeResolver {
60 fn has_registered_scalar_function(&self, _name: &str) -> bool {
61 false
62 }
63
64 fn has_registered_table_function(&self, _name: &str) -> bool {
65 false
66 }
67
68 fn has_registered_aggregate_function(&self, _name: &str) -> bool {
69 false
70 }
71
72 fn lookup_visible_sql_functions(
73 &self,
74 _name: &str,
75 ) -> Result<Option<Vec<Arc<SQLUserFunction>>>, SQLError> {
76 Ok(None)
77 }
78
79 fn lookup_visible_sql_functions_for_analysis(
81 &self,
82 name: &str,
83 ) -> Result<Option<Vec<Arc<SQLUserFunction>>>, SQLError> {
84 self.lookup_visible_sql_functions(name)
85 }
86
87 fn lookup_bound_sql_functions(&self, _name: &str) -> Option<Vec<Arc<SQLUserFunction>>> {
89 None
90 }
91
92 fn lookup_bound_sql_functions_by_binding(
93 &self,
94 _binding: &FunctionBinding,
95 ) -> Option<Vec<Arc<SQLUserFunction>>> {
96 None
97 }
98
99 fn resolve_static_sql_function(
100 &self,
101 _name: &str,
102 _binding: Option<&FunctionBinding>,
103 _argument_names: &[Option<String>],
104 _argument_types: &[Option<ColumnType>],
105 _explicit_variadic: bool,
106 ) -> Result<Option<Arc<SQLUserFunction>>, SQLError> {
107 Ok(None)
108 }
109
110 fn resolve_static_sql_function_match(
111 &self,
112 _name: &str,
113 _binding: Option<&FunctionBinding>,
114 _argument_names: &[Option<String>],
115 _argument_types: &[Option<ColumnType>],
116 _explicit_variadic: bool,
117 ) -> Result<Option<StaticFunctionMatch>, SQLError> {
118 Ok(None)
119 }
120
121 fn resolve_table_function_overload_with_builtins(
122 &self,
123 _name: &str,
124 _binding: Option<&FunctionBinding>,
125 _argument_names: &[Option<String>],
126 _argument_types: &[Option<ColumnType>],
127 _explicit_variadic: bool,
128 _builtins: &[BuiltinFunctionOverload],
129 ) -> Result<Option<ResolvedFunctionOverload>, SQLError> {
130 Ok(None)
131 }
132}
133
134pub fn routine_signature_types(def: &CreateFunction) -> Vec<String> {
135 def.identity_params()
136 .iter()
137 .map(|parameter| canonical_routine_type_name(¶meter.type_name))
138 .collect()
139}
140
141pub fn routine_returns_anonymous_record(def: &CreateFunction) -> bool {
142 def.output_params().is_empty()
143 && matches!(
144 &def.returns,
145 FunctionReturns::Scalar { type_name } | FunctionReturns::SetOf { type_name }
146 if canonical_routine_type_name(type_name) == "record"
147 )
148}
149
150pub struct StaticFunctionMatch {
151 pub function: Arc<SQLUserFunction>,
152 pub invocation: Box<RoutineInvocationBinding>,
153 pub argument_types: Vec<String>,
154 pub raw_exact_matches: usize,
155 pub exact_matches: usize,
156 pub preferred_matches: usize,
157 pub variadic_expansion: bool,
158}
159
160impl StaticFunctionMatch {
161 pub fn binding(&self) -> FunctionBinding {
162 FunctionBinding {
163 object_id: self.function.def.object_id,
164 name: self.function.def.name.clone(),
165 argument_types: routine_signature_types(&self.function.def),
166 builtin: false,
167 dispatch: None,
168 invocation: Some(self.invocation.clone()),
169 resolution_error: None,
170 }
171 }
172}
173
174impl RankedFunctionMatch for StaticFunctionMatch {
175 fn argument_types(&self) -> &[String] {
176 &self.argument_types
177 }
178
179 fn raw_exact_matches(&self) -> usize {
180 self.raw_exact_matches
181 }
182
183 fn exact_matches(&self) -> usize {
184 self.exact_matches
185 }
186
187 fn preferred_matches(&self) -> usize {
188 self.preferred_matches
189 }
190
191 fn is_variadic_expansion(&self) -> bool {
192 self.variadic_expansion
193 }
194}
195
196pub fn builtin_routine_support_oid(name: &str) -> Option<i64> {
197 Some(match name.strip_prefix("pg_catalog.").unwrap_or(name) {
198 "textlike_support" => 1023,
199 "texticregexeq_support" => 1024,
200 "texticlike_support" => 1025,
201 "network_subset_support" => 1173,
202 "textregexeq_support" => 1364,
203 "varchar_support" => 3097,
204 "numeric_support" => 3157,
205 _ => return None,
206 })
207}
208
209pub fn function_binding_matches(binding: &FunctionBinding, target: &FunctionBinding) -> bool {
210 if binding.builtin || target.builtin {
211 return false;
212 }
213 match (binding.object_id, target.object_id) {
214 (Some(binding), Some(target)) => binding == target,
215 (None, None) => {
216 binding.name == target.name && binding.argument_types == target.argument_types
217 }
218 _ => false,
219 }
220}
221
222pub fn routine_local_name(name: &str) -> Result<String, SQLError> {
223 uqa_core::RelationIdentity::from_legacy_name(name)
224 .map(|relation| relation.name)
225 .map_err(|error| SQLError::Internal(format!("invalid routine name `{name}`: {error}")))
226}
227
228pub fn routine_kind(def: &CreateFunction) -> &'static str {
229 if def.is_procedure {
230 "procedure"
231 } else {
232 "function"
233 }
234}
235
236pub mod anonymous_block;
237pub mod call;
238pub mod invocation;