Skip to main content

uqa_sql/ast/
routines.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use super::{
8    Deserialize, Expr, FunctionParallel, RoutineAclEntry, RoutineAttributeClauses,
9    RoutineConfigAction, RoutineSecurityAttributes, Serialize, Statement,
10};
11
12/// Parameter mode of a `CREATE FUNCTION` / `CREATE PROCEDURE`
13/// argument. Mirrors `PostgreSQL`'s `FunctionParameterMode`.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15pub enum FunctionParamMode {
16    /// `IN` (also the default when no mode is written).
17    In,
18    /// `OUT` - shapes the result row, not part of a function's call
19    /// signature (but part of a procedure's).
20    Out,
21    /// `INOUT` - accepted as input and returned in the result row.
22    InOut,
23    /// `VARIADIC` - a trailing array parameter that accepts either expanded element arguments or one explicit `VARIADIC` array argument.
24    Variadic,
25    /// `RETURNS TABLE (col type, ...)` column. Behaves like an `OUT`
26    /// parameter of a set-returning function.
27    Table,
28}
29
30/// An input parameter as a SQL-standard body refers to it: the name the body uses, which follows `PostgreSQL`'s positional naming, and where the parameter's value arrives in a call.
31#[derive(Debug, Clone, Copy)]
32pub struct SQLBodyParameter<'a> {
33    /// The name the body uses, empty when the declared parameter at this position is unnamed.
34    pub name: &'a str,
35    pub parameter: &'a FunctionParam,
36    /// The parameter's 1-based position among the call's arguments, which include a procedure's output parameters.
37    pub call_position: usize,
38}
39
40/// One declared parameter of a user-defined function or procedure.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct FunctionParam {
43    /// Parameter name. Empty for unnamed parameters (`f(integer)`),
44    /// which are only addressable as `$n`.
45    pub name: String,
46    /// Raw type name as written (last segment, lower-cased by the
47    /// compiler; e.g. `int4`, `text`, `numeric`).
48    pub type_name: String,
49    /// Parsed relation and column identity for `%TYPE`; ordinary types have no reference.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub type_reference: Option<RoutineColumnTypeReference>,
52    /// The type as the statement wrote it, as `TypeNameToString` spells it, which names a missing type until registration resolves the type.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub written_type: Option<String>,
55    pub mode: FunctionParamMode,
56    /// `DEFAULT <expr>` for trailing input parameters.
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub default: Option<Expr>,
59    /// The analyzed default expression type, used when an omitted polymorphic input supplies call typing.
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub default_type: Option<RoutineDefaultType>,
62}
63
64/// The type of a stored routine default. Some NULL inputs retain a polymorphic
65/// pseudo-type without claiming that it is a concrete SQL value carrier.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub enum RoutineDefaultType {
68    Concrete(super::ColumnType),
69    Polymorphic(String),
70}
71
72/// Structured relation-column identity carried by a routine `%TYPE` declaration until catalog binding resolves it to a concrete SQL type.
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct RoutineColumnTypeReference {
75    pub schema: Option<String>,
76    pub relation: String,
77    pub column: String,
78}
79
80impl RoutineColumnTypeReference {
81    pub fn new(schema: Option<String>, relation: String, column: String) -> Self {
82        Self {
83            schema,
84            relation,
85            column,
86        }
87    }
88
89    pub fn relation_reference(&self) -> String {
90        match self.schema.as_deref() {
91            Some(schema) => format!(
92                "{}.{}",
93                render_identifier_component(schema),
94                render_identifier_component(&self.relation)
95            ),
96            None => render_identifier_component(&self.relation),
97        }
98    }
99
100    pub fn type_reference(&self) -> String {
101        format!(
102            "{}.{}%type",
103            self.relation_reference(),
104            render_identifier_component(&self.column)
105        )
106    }
107}
108
109fn render_identifier_component(component: &str) -> String {
110    let can_render_bare = component
111        .bytes()
112        .enumerate()
113        .all(|(index, byte)| match byte {
114            b'a'..=b'z' | b'_' => true,
115            b'0'..=b'9' | b'$' => index != 0,
116            _ => false,
117        });
118    if can_render_bare && !component.is_empty() {
119        component.to_string()
120    } else {
121        format!("\"{}\"", component.replace('"', "\"\""))
122    }
123}
124
125/// Declared result shape of a user-defined function.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub enum FunctionReturns {
128    /// Procedures and functions whose result is shaped purely by
129    /// `OUT` parameters carry no explicit `RETURNS` clause.
130    None,
131    /// `RETURNS <type>` - includes `RETURNS void` and `RETURNS record`.
132    Scalar { type_name: String },
133    /// `RETURNS SETOF <type>`.
134    SetOf { type_name: String },
135    /// `RETURNS TABLE (...)`. The column list lives in
136    /// [`CreateFunction::params`] as [`FunctionParamMode::Table`]
137    /// entries; this variant just records the set-returning shape.
138    Table,
139}
140
141/// `IMMUTABLE` / `STABLE` / `VOLATILE` marker.
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
143pub enum FunctionVolatility {
144    Immutable,
145    Stable,
146    #[default]
147    Volatile,
148}
149
150/// Body of a user-defined routine.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub enum FunctionBody {
153    /// `AS $$ ... $$` - raw source text, parsed per language at
154    /// registration time.
155    Source(String),
156    /// SQL-standard body (`BEGIN ATOMIC ... END` / `RETURN expr`)
157    /// compiled straight to statements.
158    Statements(Vec<Statement>),
159}
160
161/// How a SQL-standard body was written; `pg_get_function_sqlbody` reproduces it.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
163pub enum SQLBodyForm {
164    /// `RETURN expr`, stored as one `SELECT expr`.
165    Return,
166    /// `BEGIN ATOMIC stmt; ... END`.
167    Atomic,
168}
169
170/// `CREATE [OR REPLACE] FUNCTION | PROCEDURE`.
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct CreateFunction {
173    /// Stable catalog identity. The engine assigns this once when the routine is created and preserves it across replacement and rename.
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub object_id: Option<[u8; 16]>,
176    /// The catalog tuple's replacement identity. A replacement changes this token while keeping `object_id`; older definitions use their object identity until first replacement.
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub catalog_revision: Option<[u8; 16]>,
179    /// The public OID allocated when the routine was created; replacement and rename keep it. Routines created before OIDs were recorded derive it from their identity.
180    #[serde(default, skip_serializing_if = "Option::is_none")]
181    pub catalog_oid: Option<u32>,
182    pub name: String,
183    pub or_replace: bool,
184    pub is_procedure: bool,
185    pub params: Vec<FunctionParam>,
186    pub returns: FunctionReturns,
187    /// Parsed `%TYPE` identity for a scalar or set return declaration until registration resolves it.
188    #[serde(default, skip_serializing_if = "Option::is_none")]
189    pub return_type_reference: Option<RoutineColumnTypeReference>,
190    /// The declared result type as the statement wrote it, without SETOF, until registration resolves it.
191    #[serde(default, skip_serializing_if = "Option::is_none")]
192    pub return_written_type: Option<String>,
193    /// Lower-cased language name (`plpgsql`, `sql`).
194    pub language: String,
195    pub body: FunctionBody,
196    /// The written form of a SQL-standard body. Definitions stored before the form was recorded omit it.
197    #[serde(default, skip_serializing_if = "Option::is_none")]
198    pub sql_body_form: Option<SQLBodyForm>,
199    /// Effective schema search path captured when a SQL-standard body or parameter default is catalog-bound. String and PL/pgSQL bodies keep dynamic lookup, but their parameter defaults still use this captured path.
200    #[serde(default, skip_serializing_if = "Vec::is_empty")]
201    pub creation_search_path: Vec<String>,
202    pub volatility: FunctionVolatility,
203    /// `STRICT` / `RETURNS NULL ON NULL INPUT` - the function is not
204    /// invoked when any input argument is NULL; the result is NULL.
205    pub strict: bool,
206    /// Catalog owner incarnation. Parsed declarations and anonymous blocks are unbound; registration binds the effective current role before publication.
207    #[serde(
208        default,
209        deserialize_with = "super::routine_security::deserialize_routine_owner"
210    )]
211    pub owner: Option<uqa_core::catalog_role::RoleIdentity>,
212    /// Execution identity and leakproofness, flattened to retain the catalog-definition wire shape.
213    #[serde(default, flatten)]
214    pub security: RoutineSecurityAttributes,
215    /// Parallel-safety classification.
216    #[serde(default)]
217    pub parallel: FunctionParallel,
218    /// Optional planner support routine identity.
219    #[serde(default, skip_serializing_if = "Option::is_none")]
220    pub support: Option<String>,
221    /// `COST`, the estimated execution cost in units of `cpu_operator_cost` that `pg_proc.procost` reports, a `float4` as `PostgreSQL` stores it; without one the language's default applies.
222    #[serde(
223        default,
224        skip_serializing_if = "Option::is_none",
225        with = "super::routine_estimate"
226    )]
227    pub cost: Option<f32>,
228    /// `ROWS`, the estimated number of rows a set-returning routine returns, which `pg_proc.prorows` reports; without one the default applies.
229    #[serde(
230        default,
231        skip_serializing_if = "Option::is_none",
232        with = "super::routine_estimate"
233    )]
234    pub rows: Option<f32>,
235    /// Effective per-routine configuration as `name=value` pairs in declaration order.
236    #[serde(default, skip_serializing_if = "Vec::is_empty")]
237    pub config: Vec<(String, String)>,
238    /// Creation-time configuration actions awaiting engine/session resolution. Registration consumes this list before persistence.
239    #[serde(default, skip_serializing_if = "Vec::is_empty")]
240    pub config_actions: Vec<RoutineConfigAction>,
241    /// The statement's attribute clauses in written order, which registration checks and consumes before persistence.
242    #[serde(default, skip_serializing_if = "RoutineAttributeClauses::is_empty")]
243    pub attribute_clauses: RoutineAttributeClauses,
244    /// Explicit execution privileges, including the owner's revocable EXECUTE. `None` means the `PostgreSQL` default (PUBLIC and owner EXECUTE); ownership always retains implicit grant options.
245    #[serde(default)]
246    pub execute_acl: Option<Vec<RoutineAclEntry>>,
247}
248
249impl CreateFunction {
250    /// Parameters that define routine identity: `IN` + `INOUT` + `VARIADIC`, in declaration order.
251    pub fn identity_params(&self) -> Vec<&FunctionParam> {
252        self.params
253            .iter()
254            .filter(|param| Self::is_identity_param(param))
255            .collect()
256    }
257
258    /// Number of parameters that define routine identity.
259    pub fn identity_arity(&self) -> usize {
260        self.params
261            .iter()
262            .filter(|param| Self::is_identity_param(param))
263            .count()
264    }
265
266    fn is_identity_param(param: &FunctionParam) -> bool {
267        matches!(
268            param.mode,
269            FunctionParamMode::In | FunctionParamMode::InOut | FunctionParamMode::Variadic
270        )
271    }
272
273    /// Parameters supplied by a call: identity parameters for functions and every non-`TABLE` parameter for procedures.
274    pub fn call_params(&self) -> Vec<&FunctionParam> {
275        self.params
276            .iter()
277            .filter(|param| self.is_call_param(param))
278            .collect()
279    }
280
281    /// Number of declared call parameters; a variadic parameter can consume multiple actual arguments.
282    pub fn call_arity(&self) -> usize {
283        self.params
284            .iter()
285            .filter(|param| self.is_call_param(param))
286            .count()
287    }
288
289    /// Minimum number of actual arguments for ordinary expanded notation; a variadic parameter accepts zero elements.
290    pub fn required_call_arity(&self) -> usize {
291        self.params
292            .iter()
293            .filter(|param| {
294                self.is_call_param(param)
295                    && param.default.is_none()
296                    && param.mode != FunctionParamMode::Variadic
297            })
298            .count()
299    }
300
301    fn is_call_param(&self, param: &FunctionParam) -> bool {
302        match param.mode {
303            FunctionParamMode::In | FunctionParamMode::InOut | FunctionParamMode::Variadic => true,
304            FunctionParamMode::Out => self.is_procedure,
305            FunctionParamMode::Table => false,
306        }
307    }
308
309    /// The parameters a SQL-standard body refers to, as `interpret_AS_clause` gives them to its parser: the input parameters in order, the one at position `n` going by the name of the parameter declared at position `n` among all parameters. When an output parameter precedes an input, the body therefore names that input by the output parameter's name, as `PostgreSQL` does.
310    pub fn sql_body_parameters(&self) -> Vec<SQLBodyParameter<'_>> {
311        let call_params = self.call_params();
312        self.identity_params()
313            .into_iter()
314            .enumerate()
315            .map(|(index, parameter)| SQLBodyParameter {
316                name: self
317                    .params
318                    .get(index)
319                    .map_or("", |declared| declared.name.as_str()),
320                parameter,
321                call_position: call_params
322                    .iter()
323                    .position(|call| std::ptr::eq(*call, parameter))
324                    .map_or(index + 1, |position| position + 1),
325            })
326            .collect()
327    }
328
329    /// Backward-compatible alias for [`Self::call_arity`].
330    pub fn signature_arity(&self) -> usize {
331        self.call_arity()
332    }
333
334    /// Backward-compatible alias for [`Self::required_call_arity`].
335    pub fn required_arity(&self) -> usize {
336        self.required_call_arity()
337    }
338
339    /// Backward-compatible alias for [`Self::call_params`].
340    pub fn signature_params(&self) -> Vec<&FunctionParam> {
341        self.call_params()
342    }
343
344    /// Parameters that shape the result row: `OUT` + `INOUT` +
345    /// `RETURNS TABLE` columns, in declaration order.
346    pub fn output_params(&self) -> Vec<&FunctionParam> {
347        self.params
348            .iter()
349            .filter(|p| {
350                matches!(
351                    p.mode,
352                    FunctionParamMode::Out | FunctionParamMode::InOut | FunctionParamMode::Table
353                )
354            })
355            .collect()
356    }
357
358    /// True when the routine produces a row set (`RETURNS SETOF` /
359    /// `RETURNS TABLE`).
360    pub fn returns_set(&self) -> bool {
361        matches!(
362            self.returns,
363            FunctionReturns::SetOf { .. } | FunctionReturns::Table
364        )
365    }
366}
367
368/// One `DROP FUNCTION` / `DROP PROCEDURE` target.
369#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct DropFunctionItem {
371    pub name: String,
372    /// `Some(types)` when the statement spelled an argument list
373    /// (`DROP FUNCTION f(int, int)` - matched by canonical argument
374    /// types); `None` for the bare-name form
375    /// (`DROP FUNCTION f`).
376    pub arg_types: Option<Vec<String>>,
377}
378
379/// `DROP FUNCTION [IF EXISTS] name[(argtypes)] [, ...]` and the
380/// `DROP PROCEDURE` equivalent.
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct DropFunctionStmt {
383    pub is_procedure: bool,
384    pub if_exists: bool,
385    #[serde(default)]
386    pub cascade: bool,
387    pub items: Vec<DropFunctionItem>,
388}