Skip to main content

uqa_sql/ast/
function_binding.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use serde::{Deserialize, Serialize};
8
9use super::{EnumFunctionOperation, RangeSubtype};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct FunctionBinding {
13    /// Stable identity of a bound user routine. Built-ins and unbound calls leave this unset.
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub object_id: Option<[u8; 16]>,
16    pub name: String,
17    pub argument_types: Vec<String>,
18    #[serde(default)]
19    pub builtin: bool,
20    /// Executor operation selected structurally during parsing or overload binding. SQL-visible routine lookup never consults display-name conventions for these operations.
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub dispatch: Option<FunctionDispatch>,
23    /// Concrete invocation contract selected during routine overload resolution.
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub invocation: Option<Box<RoutineInvocationBinding>>,
26    /// A typed overload-resolution failure retained until the expression reaches a fallible planning or execution boundary. This never reuses the SQL function-name namespace as an error channel.
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub resolution_error: Option<FunctionResolutionError>,
29}
30
31/// Static function-call failure discovered while binding declared argument types.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub enum FunctionResolutionError {
34    UndefinedFunction { signature: String },
35    Operator(Box<OperatorResolutionError>),
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct OperatorResolutionError {
40    pub sqlstate: String,
41    pub message: String,
42}
43
44impl FunctionResolutionError {
45    #[must_use]
46    pub fn sql_error(&self) -> crate::SQLError {
47        match self {
48            Self::UndefinedFunction { signature } => {
49                crate::SQLError::undefined_function_call(signature)
50            }
51            Self::Operator(error) => crate::SQLError::Routine {
52                sqlstate: error.sqlstate.clone(),
53                message: error.message.clone(),
54            },
55        }
56    }
57}
58
59/// Structural identity for parser-owned expressions and overload-specific built-in implementations. These variants occupy no SQL function-name namespace.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61pub enum FunctionDispatch {
62    NumericOperator(NumericOperator),
63    NamedArgument,
64    VariadicArgument,
65    ArraySubscripts,
66    ArraySlices,
67    Subscript,
68    Slice,
69    /// `(expression).field`: the field of a composite value, or the key of a document map.
70    FieldSelect,
71    AnyOperator,
72    AllOperator,
73    IsDistinct,
74    BetweenSymmetric,
75    ToBinInt4,
76    ToBinInt8,
77    ToHexInt4,
78    ToHexInt8,
79    ToOctInt4,
80    ToOctInt8,
81    RandomInt4Range,
82    RandomInt8Range,
83    RandomNumericRange,
84    ArraySortJson,
85    /// The selected array `||` overload, retained even when its arguments evaluate to NULL.
86    ArrayConcat,
87    ArrayAppend,
88    ArrayPrepend,
89    JsonExtract {
90        as_text: bool,
91        path: bool,
92    },
93    Range {
94        operation: RangeFunctionOperation,
95        subtype: RangeSubtype,
96        multirange: bool,
97    },
98    /// An `anyenum` support function bound to one concrete enum type.
99    Enum {
100        operation: EnumFunctionOperation,
101        type_oid: u32,
102    },
103}
104
105/// Operation selected for one typed range or multirange call.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
107pub enum RangeFunctionOperation {
108    Lower,
109    Upper,
110    IsEmpty,
111    LowerInclusive,
112    UpperInclusive,
113    LowerInfinite,
114    UpperInfinite,
115    Merge,
116    Multirange,
117    Overlap,
118    Contains,
119    ContainedBy,
120    Adjacent,
121}
122
123impl FunctionDispatch {
124    /// Human-readable expression label used only in diagnostics and serialized plans; dispatch is always selected by the enum variant.
125    #[must_use]
126    pub const fn label(self) -> &'static str {
127        match self {
128            Self::NumericOperator(operator) => operator.symbol(),
129            Self::NamedArgument => "named argument",
130            Self::VariadicArgument => "VARIADIC argument",
131            Self::ArraySubscripts | Self::Subscript => "subscript",
132            Self::ArraySlices | Self::Slice => "slice",
133            Self::FieldSelect => "field selection",
134            Self::AnyOperator => "ANY operator",
135            Self::AllOperator => "ALL operator",
136            Self::IsDistinct => "IS DISTINCT FROM",
137            Self::BetweenSymmetric => "BETWEEN SYMMETRIC",
138            Self::ToBinInt4 | Self::ToBinInt8 => "pg_catalog.to_bin",
139            Self::ToHexInt4 | Self::ToHexInt8 => "pg_catalog.to_hex",
140            Self::ToOctInt4 | Self::ToOctInt8 => "pg_catalog.to_oct",
141            Self::RandomInt4Range | Self::RandomInt8Range | Self::RandomNumericRange => {
142                "pg_catalog.random"
143            }
144            Self::ArraySortJson => "pg_catalog.array_sort",
145            Self::ArrayConcat => "pg_catalog.array_cat",
146            Self::ArrayAppend => "pg_catalog.array_append",
147            Self::ArrayPrepend => "pg_catalog.array_prepend",
148            Self::JsonExtract { as_text: false, .. } => "JSON extraction operator",
149            Self::JsonExtract { as_text: true, .. } => "JSON text extraction operator",
150            Self::Range { operation, .. } => operation.label(),
151            Self::Enum { operation, .. } => operation.label(),
152        }
153    }
154
155    #[must_use]
156    pub const fn is_call_argument_marker(self) -> bool {
157        matches!(self, Self::NamedArgument | Self::VariadicArgument)
158    }
159
160    /// Decode the compiler-private function spellings written into durable expressions by releases through 0.1.6. This is a catalog migration primitive, never a SQL routine lookup path.
161    #[doc(hidden)]
162    #[must_use]
163    pub fn from_legacy_serialized_name(name: &str) -> Option<Self> {
164        let fixed = match name {
165            "__named_arg" => Self::NamedArgument,
166            "__variadic_arg" => Self::VariadicArgument,
167            "__array_subscripts" => Self::ArraySubscripts,
168            "__array_slices" => Self::ArraySlices,
169            "__subscript" => Self::Subscript,
170            "__slice" => Self::Slice,
171            "__any_op" => Self::AnyOperator,
172            "__all_op" => Self::AllOperator,
173            "__is_distinct" => Self::IsDistinct,
174            "__between_symmetric" => Self::BetweenSymmetric,
175            "__to_bin_int4" => Self::ToBinInt4,
176            "__to_bin_int8" => Self::ToBinInt8,
177            "__to_hex_int4" => Self::ToHexInt4,
178            "__to_hex_int8" => Self::ToHexInt8,
179            "__to_oct_int4" => Self::ToOctInt4,
180            "__to_oct_int8" => Self::ToOctInt8,
181            "__random_int4_range" => Self::RandomInt4Range,
182            "__random_int8_range" => Self::RandomInt8Range,
183            "__random_numeric_range" => Self::RandomNumericRange,
184            "__array_sort_json" => Self::ArraySortJson,
185            _ => return Self::legacy_range_dispatch(name),
186        };
187        Some(fixed)
188    }
189
190    fn legacy_range_dispatch(name: &str) -> Option<Self> {
191        let encoded = name.strip_prefix("__range_")?;
192        let subtypes = [
193            RangeSubtype::Integer,
194            RangeSubtype::BigInteger,
195            RangeSubtype::Numeric,
196            RangeSubtype::Date,
197            RangeSubtype::Timestamp,
198            RangeSubtype::TimestampTz,
199        ];
200        for subtype in subtypes {
201            for (type_name, multirange) in [
202                (subtype.multirange_name(), true),
203                (subtype.range_name(), false),
204            ] {
205                let Some(operation) = encoded.strip_suffix(type_name) else {
206                    continue;
207                };
208                let operation = match operation.trim_end_matches('_') {
209                    "lower" => RangeFunctionOperation::Lower,
210                    "upper" => RangeFunctionOperation::Upper,
211                    "isempty" => RangeFunctionOperation::IsEmpty,
212                    "lower_inc" => RangeFunctionOperation::LowerInclusive,
213                    "upper_inc" => RangeFunctionOperation::UpperInclusive,
214                    "lower_inf" => RangeFunctionOperation::LowerInfinite,
215                    "upper_inf" => RangeFunctionOperation::UpperInfinite,
216                    "merge" => RangeFunctionOperation::Merge,
217                    "multirange" => RangeFunctionOperation::Multirange,
218                    "overlap" => RangeFunctionOperation::Overlap,
219                    "contains" => RangeFunctionOperation::Contains,
220                    "contained_by" => RangeFunctionOperation::ContainedBy,
221                    "adjacent" => RangeFunctionOperation::Adjacent,
222                    _ => continue,
223                };
224                return Some(Self::Range {
225                    operation,
226                    subtype,
227                    multirange,
228                });
229            }
230        }
231        None
232    }
233}
234
235/// Numeric operator syntax, kept separate from ordinary calls such as `mod` or `abs`.
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
237pub enum NumericOperator {
238    Modulo,
239    Power,
240    Plus,
241    SquareRoot,
242    CubeRoot,
243    Absolute,
244}
245
246impl NumericOperator {
247    #[must_use]
248    pub const fn symbol(self) -> &'static str {
249        match self {
250            Self::Modulo => "%",
251            Self::Power => "^",
252            Self::Plus => "+",
253            Self::SquareRoot => "|/",
254            Self::CubeRoot => "||/",
255            Self::Absolute => "@",
256        }
257    }
258
259    #[must_use]
260    pub const fn arity(self) -> usize {
261        match self {
262            Self::Modulo | Self::Power => 2,
263            Self::Plus | Self::SquareRoot | Self::CubeRoot | Self::Absolute => 1,
264        }
265    }
266}
267
268impl RangeFunctionOperation {
269    #[must_use]
270    pub const fn label(self) -> &'static str {
271        match self {
272            Self::Lower => "pg_catalog.lower",
273            Self::Upper => "pg_catalog.upper",
274            Self::IsEmpty => "pg_catalog.isempty",
275            Self::LowerInclusive => "pg_catalog.lower_inc",
276            Self::UpperInclusive => "pg_catalog.upper_inc",
277            Self::LowerInfinite => "pg_catalog.lower_inf",
278            Self::UpperInfinite => "pg_catalog.upper_inf",
279            Self::Merge => "pg_catalog.range_merge",
280            Self::Multirange => "pg_catalog.multirange",
281            Self::Overlap => "range overlap operator",
282            Self::Contains => "range contains operator",
283            Self::ContainedBy => "range contained-by operator",
284            Self::Adjacent => "range adjacent operator",
285        }
286    }
287}
288
289/// Concrete parameter and result types selected for one routine invocation.
290#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
291pub struct RoutineInvocationBinding {
292    /// Zero-based declared parameter index for each call argument, aligned with the call argument list.
293    pub argument_positions: Vec<usize>,
294    /// Concrete coercion target for each call argument, aligned with the call argument list.
295    pub argument_targets: Vec<String>,
296    /// Declared source types before argument coercion; absent only in legacy bindings.
297    #[serde(default, skip_serializing_if = "Vec::is_empty")]
298    pub argument_sources: Vec<Option<String>>,
299    /// Concrete type for each declared parameter, aligned with [`crate::ast::CreateFunction::params`].
300    pub parameter_types: Vec<String>,
301    /// Concrete invocation result type after polymorphic substitution.
302    pub return_type: Option<String>,
303    /// Whether and where the declared variadic parameter participates in this invocation.
304    pub variadic_mode: RoutineVariadicMode,
305}
306
307/// Call syntax selected for a routine's variadic parameter.
308#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
309pub enum RoutineVariadicMode {
310    /// The invocation does not use a variadic parameter.
311    #[default]
312    None,
313    /// Trailing call arguments are expanded into the declared variadic array parameter.
314    Expanded {
315        /// Zero-based index in [`crate::ast::CreateFunction::params`].
316        parameter_index: usize,
317    },
318    /// An explicit `VARIADIC` array argument supplies the declared variadic parameter.
319    Explicit {
320        /// Zero-based index in [`crate::ast::CreateFunction::params`].
321        parameter_index: usize,
322    },
323}
324
325impl FunctionBinding {
326    /// Construct the identity marker used when `PostgreSQL` parses a polymorphic syntax expression instead of an ordinary function call.
327    #[must_use]
328    pub fn polymorphic_builtin_syntax(name: &str) -> Self {
329        assert!(Self::is_polymorphic_builtin_syntax_name(name));
330        Self {
331            object_id: None,
332            name: name.into(),
333            argument_types: Vec::new(),
334            builtin: true,
335            dispatch: None,
336            invocation: None,
337            resolution_error: None,
338        }
339    }
340
341    /// Construct a parser- or binder-owned expression with an identity that cannot collide with a SQL routine name.
342    #[must_use]
343    pub fn dispatched(dispatch: FunctionDispatch) -> Self {
344        Self::dispatched_with_control(
345            dispatch,
346            &uqa_core::memory::ProductionControl::uncontrolled(),
347        )
348        .expect("ordinary dispatch constructor cannot be cancelled or limited")
349        .into_uncontrolled()
350        .expect("ordinary dispatch owner")
351    }
352
353    pub fn dispatched_with_control(
354        dispatch: FunctionDispatch,
355        control: &uqa_core::memory::ProductionControl<'_>,
356    ) -> Result<uqa_core::memory::Produced<Self>, uqa_core::ValueRetentionError> {
357        let (name, memory) = control.copy_text(dispatch.label())?.into_parts();
358        control.finish(
359            Self {
360                object_id: None,
361                name,
362                argument_types: Vec::new(),
363                builtin: true,
364                dispatch: Some(dispatch),
365                invocation: None,
366                resolution_error: None,
367            },
368            memory,
369        )
370    }
371
372    /// Preserve an undefined-overload error structurally without fabricating a dispatch name.
373    #[must_use]
374    pub fn undefined_function(name: impl Into<String>, signature: impl Into<String>) -> Self {
375        Self {
376            object_id: None,
377            name: name.into(),
378            argument_types: Vec::new(),
379            builtin: false,
380            dispatch: None,
381            invocation: None,
382            resolution_error: Some(FunctionResolutionError::UndefinedFunction {
383                signature: signature.into(),
384            }),
385        }
386    }
387
388    /// Upgrade one function node deserialized from the catalog format written by releases through 0.1.6. Bound user routines are deliberately left untouched even when their SQL names resemble an old compiler marker.
389    #[doc(hidden)]
390    pub fn upgrade_legacy_serialized_dispatch(
391        display_name: &mut String,
392        binding: &mut Option<Self>,
393    ) -> bool {
394        if binding
395            .as_ref()
396            .is_some_and(|binding| binding.dispatch.is_some() || !binding.builtin)
397        {
398            return false;
399        }
400        let Some(dispatch) = FunctionDispatch::from_legacy_serialized_name(display_name) else {
401            return false;
402        };
403        if let Some(binding) = binding {
404            binding.dispatch = Some(dispatch);
405            display_name.clone_from(&binding.name);
406        } else {
407            let upgraded = Self::dispatched(dispatch);
408            display_name.clone_from(&upgraded.name);
409            *binding = Some(upgraded);
410        }
411        true
412    }
413
414    /// Return whether this binding marks a polymorphic syntax expression whose argument types must be inferred from its operands.
415    #[must_use]
416    pub fn is_polymorphic_builtin_syntax(&self) -> bool {
417        self.builtin
418            && self.argument_types.is_empty()
419            && Self::is_polymorphic_builtin_syntax_name(&self.name)
420    }
421
422    /// Return whether an unqualified local name belongs to `PostgreSQL`'s polymorphic function-like syntax expressions.
423    #[must_use]
424    pub fn is_polymorphic_builtin_syntax_name(name: &str) -> bool {
425        matches!(name, "coalesce" | "greatest" | "least" | "nullif")
426    }
427}
428
429pub type GeneratedFunctionDependency = FunctionBinding;