1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//
use super::{
Deserialize, Expr, FunctionParallel, RoutineAclEntry, RoutineConfigAction,
RoutineSecurityAttributes, Serialize, Statement,
};
/// Parameter mode of a `CREATE FUNCTION` / `CREATE PROCEDURE`
/// argument. Mirrors `PostgreSQL`'s `FunctionParameterMode`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FunctionParamMode {
/// `IN` (also the default when no mode is written).
In,
/// `OUT` - shapes the result row, not part of a function's call
/// signature (but part of a procedure's).
Out,
/// `INOUT` - accepted as input and returned in the result row.
InOut,
/// `VARIADIC` - a trailing array parameter that accepts either expanded element arguments or one explicit `VARIADIC` array argument.
Variadic,
/// `RETURNS TABLE (col type, ...)` column. Behaves like an `OUT`
/// parameter of a set-returning function.
Table,
}
/// One declared parameter of a user-defined function or procedure.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionParam {
/// Parameter name. Empty for unnamed parameters (`f(integer)`),
/// which are only addressable as `$n`.
pub name: String,
/// Raw type name as written (last segment, lower-cased by the
/// compiler; e.g. `int4`, `text`, `numeric`).
pub type_name: String,
/// Parsed relation and column identity for `%TYPE`; ordinary types have no reference.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub type_reference: Option<RoutineColumnTypeReference>,
pub mode: FunctionParamMode,
/// `DEFAULT <expr>` for trailing input parameters.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default: Option<Expr>,
}
/// Structured relation-column identity carried by a routine `%TYPE` declaration until catalog binding resolves it to a concrete SQL type.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoutineColumnTypeReference {
pub schema: Option<String>,
pub relation: String,
pub column: String,
}
impl RoutineColumnTypeReference {
pub fn new(schema: Option<String>, relation: String, column: String) -> Self {
Self {
schema,
relation,
column,
}
}
pub fn relation_reference(&self) -> String {
match self.schema.as_deref() {
Some(schema) => format!(
"{}.{}",
render_identifier_component(schema),
render_identifier_component(&self.relation)
),
None => render_identifier_component(&self.relation),
}
}
pub fn type_reference(&self) -> String {
format!(
"{}.{}%type",
self.relation_reference(),
render_identifier_component(&self.column)
)
}
}
fn render_identifier_component(component: &str) -> String {
let can_render_bare = component
.bytes()
.enumerate()
.all(|(index, byte)| match byte {
b'a'..=b'z' | b'_' => true,
b'0'..=b'9' | b'$' => index != 0,
_ => false,
});
if can_render_bare && !component.is_empty() {
component.to_string()
} else {
format!("\"{}\"", component.replace('"', "\"\""))
}
}
/// Declared result shape of a user-defined function.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FunctionReturns {
/// Procedures and functions whose result is shaped purely by
/// `OUT` parameters carry no explicit `RETURNS` clause.
None,
/// `RETURNS <type>` - includes `RETURNS void` and `RETURNS record`.
Scalar { type_name: String },
/// `RETURNS SETOF <type>`.
SetOf { type_name: String },
/// `RETURNS TABLE (...)`. The column list lives in
/// [`CreateFunction::params`] as [`FunctionParamMode::Table`]
/// entries; this variant just records the set-returning shape.
Table,
}
/// `IMMUTABLE` / `STABLE` / `VOLATILE` marker.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum FunctionVolatility {
Immutable,
Stable,
#[default]
Volatile,
}
/// Body of a user-defined routine.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FunctionBody {
/// `AS $$ ... $$` - raw source text, parsed per language at
/// registration time.
Source(String),
/// SQL-standard body (`BEGIN ATOMIC ... END` / `RETURN expr`)
/// compiled straight to statements.
Statements(Vec<Statement>),
}
/// `CREATE [OR REPLACE] FUNCTION | PROCEDURE`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateFunction {
/// Stable catalog identity. The engine assigns this once when the routine is created and preserves it across replacement and rename.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub object_id: Option<[u8; 16]>,
pub name: String,
pub or_replace: bool,
pub is_procedure: bool,
pub params: Vec<FunctionParam>,
pub returns: FunctionReturns,
/// Parsed `%TYPE` identity for a scalar or set return declaration until registration resolves it.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub return_type_reference: Option<RoutineColumnTypeReference>,
/// Lower-cased language name (`plpgsql`, `sql`).
pub language: String,
pub body: FunctionBody,
/// 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.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub creation_search_path: Vec<String>,
pub volatility: FunctionVolatility,
/// `STRICT` / `RETURNS NULL ON NULL INPUT` - the function is not
/// invoked when any input argument is NULL; the result is NULL.
pub strict: bool,
/// Catalog owner. The compiler leaves this empty and registration captures the effective current user; persisted definitions always carry a role name.
#[serde(default)]
pub owner: String,
/// Execution identity and leakproofness, flattened to retain the catalog-definition wire shape.
#[serde(default, flatten)]
pub security: RoutineSecurityAttributes,
/// Parallel-safety classification.
#[serde(default)]
pub parallel: FunctionParallel,
/// Optional planner support routine identity.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub support: Option<String>,
/// Effective per-routine configuration as `name=value` pairs in declaration order.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub config: Vec<(String, String)>,
/// Creation-time configuration actions awaiting engine/session resolution. Registration consumes this list before persistence.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub config_actions: Vec<RoutineConfigAction>,
/// Explicit execution privileges. `None` means the `PostgreSQL` default (`PUBLIC=EXECUTE`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub execute_acl: Option<Vec<RoutineAclEntry>>,
}
impl CreateFunction {
/// Parameters that define routine identity: `IN` + `INOUT` + `VARIADIC`, in declaration order.
pub fn identity_params(&self) -> Vec<&FunctionParam> {
self.params
.iter()
.filter(|param| Self::is_identity_param(param))
.collect()
}
/// Number of parameters that define routine identity.
pub fn identity_arity(&self) -> usize {
self.params
.iter()
.filter(|param| Self::is_identity_param(param))
.count()
}
fn is_identity_param(param: &FunctionParam) -> bool {
matches!(
param.mode,
FunctionParamMode::In | FunctionParamMode::InOut | FunctionParamMode::Variadic
)
}
/// Parameters supplied by a call: identity parameters for functions and every non-`TABLE` parameter for procedures.
pub fn call_params(&self) -> Vec<&FunctionParam> {
self.params
.iter()
.filter(|param| self.is_call_param(param))
.collect()
}
/// Number of declared call parameters; a variadic parameter can consume multiple actual arguments.
pub fn call_arity(&self) -> usize {
self.params
.iter()
.filter(|param| self.is_call_param(param))
.count()
}
/// Minimum number of actual arguments for ordinary expanded notation; a variadic parameter accepts zero elements.
pub fn required_call_arity(&self) -> usize {
self.params
.iter()
.filter(|param| {
self.is_call_param(param)
&& param.default.is_none()
&& param.mode != FunctionParamMode::Variadic
})
.count()
}
fn is_call_param(&self, param: &FunctionParam) -> bool {
match param.mode {
FunctionParamMode::In | FunctionParamMode::InOut | FunctionParamMode::Variadic => true,
FunctionParamMode::Out => self.is_procedure,
FunctionParamMode::Table => false,
}
}
/// Backward-compatible alias for [`Self::call_arity`].
pub fn signature_arity(&self) -> usize {
self.call_arity()
}
/// Backward-compatible alias for [`Self::required_call_arity`].
pub fn required_arity(&self) -> usize {
self.required_call_arity()
}
/// Backward-compatible alias for [`Self::call_params`].
pub fn signature_params(&self) -> Vec<&FunctionParam> {
self.call_params()
}
/// Parameters that shape the result row: `OUT` + `INOUT` +
/// `RETURNS TABLE` columns, in declaration order.
pub fn output_params(&self) -> Vec<&FunctionParam> {
self.params
.iter()
.filter(|p| {
matches!(
p.mode,
FunctionParamMode::Out | FunctionParamMode::InOut | FunctionParamMode::Table
)
})
.collect()
}
/// True when the routine produces a row set (`RETURNS SETOF` /
/// `RETURNS TABLE`).
pub fn returns_set(&self) -> bool {
matches!(
self.returns,
FunctionReturns::SetOf { .. } | FunctionReturns::Table
)
}
}
/// One `DROP FUNCTION` / `DROP PROCEDURE` target.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DropFunctionItem {
pub name: String,
/// `Some(types)` when the statement spelled an argument list
/// (`DROP FUNCTION f(int, int)` - matched by canonical argument
/// types); `None` for the bare-name form
/// (`DROP FUNCTION f`).
pub arg_types: Option<Vec<String>>,
}
/// `DROP FUNCTION [IF EXISTS] name[(argtypes)] [, ...]` and the
/// `DROP PROCEDURE` equivalent.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DropFunctionStmt {
pub is_procedure: bool,
pub if_exists: bool,
#[serde(default)]
pub cascade: bool,
pub items: Vec<DropFunctionItem>,
}