Skip to main content

uqa_sql/ast/
routine_attributes.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! The attribute clauses a `CREATE FUNCTION` or `ALTER FUNCTION` statement writes, kept in written order for the checks `PostgreSQL` makes once the routine's schema, or the altered routine, is known.
8
9use serde::{Deserialize, Serialize};
10
11/// The clauses of a routine statement that `compute_function_attributes` and `compute_common_attribute` examine, and what they found that a later stage rejects. A statement carries them until registration checks them; stored definitions carry none.
12#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
13pub struct RoutineAttributeClauses {
14    /// Each attribute clause in written order.
15    #[serde(default, skip_serializing_if = "Vec::is_empty")]
16    pub clauses: Vec<RoutineAttributeClause>,
17    /// A PARALLEL value other than SAFE, RESTRICTED or UNSAFE, which `interpret_func_parallel` rejects.
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub invalid_parallel: Option<String>,
20    /// The types `TRANSFORM FOR TYPE` names.
21    #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    pub transform_types: Vec<RoutineTransformType>,
23    /// The body form `interpret_AS_clause` rejects.
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub body_error: Option<RoutineBodyError>,
26}
27
28impl RoutineAttributeClauses {
29    pub fn is_empty(&self) -> bool {
30        *self == Self::default()
31    }
32}
33
34/// One attribute clause of a routine statement.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36pub enum RoutineAttributeClause {
37    As,
38    Language,
39    Transform,
40    Window,
41    Volatility,
42    Strict,
43    Security,
44    Leakproof,
45    Set,
46    Cost,
47    Rows,
48    Support,
49    Parallel,
50}
51
52impl RoutineAttributeClause {
53    /// Whether a procedure cannot have the attribute (`invalid attribute in procedure definition`).
54    pub const fn rejected_by_procedures(self) -> bool {
55        matches!(
56            self,
57            Self::Window
58                | Self::Volatility
59                | Self::Strict
60                | Self::Leakproof
61                | Self::Cost
62                | Self::Rows
63                | Self::Support
64                | Self::Parallel
65        )
66    }
67
68    /// Whether the clause may be written more than once; every SET clause applies.
69    pub const fn repeatable(self) -> bool {
70        matches!(self, Self::Set)
71    }
72}
73
74/// A type that a `TRANSFORM FOR TYPE` clause names.
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub struct RoutineTransformType {
77    /// The name the catalog resolves.
78    pub type_name: String,
79    /// The name as written, as `TypeNameToString` spells it.
80    pub written: String,
81}
82
83/// A body that `interpret_AS_clause` rejects.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85pub enum RoutineBodyError {
86    /// Neither an AS body nor a SQL-standard body.
87    Missing,
88    /// Both an AS body and a SQL-standard body.
89    Duplicate,
90    /// An AS clause with more than the one item the routine's language takes.
91    ExtraAsItems,
92}
93
94/// Serde for a COST or ROWS estimate, a `float4` that overflows to infinity and that the JSON catalog cannot hold as a number: a finite estimate is a number, and an infinite one the text `PostgreSQL` prints for it.
95pub mod routine_estimate {
96    use serde::{Deserialize, Deserializer, Serializer};
97
98    #[derive(Deserialize)]
99    #[serde(untagged)]
100    enum Estimate {
101        Number(f32),
102        Text(String),
103    }
104
105    pub fn serialize<S: Serializer>(value: &Option<f32>, serializer: S) -> Result<S::Ok, S::Error> {
106        match value {
107            None => serializer.serialize_none(),
108            Some(value) if value.is_finite() => serializer.serialize_some(value),
109            Some(value) if value.is_nan() => serializer.serialize_some("NaN"),
110            Some(value) if value.is_sign_positive() => serializer.serialize_some("Infinity"),
111            Some(_) => serializer.serialize_some("-Infinity"),
112        }
113    }
114
115    pub fn deserialize<'de, D: Deserializer<'de>>(
116        deserializer: D,
117    ) -> Result<Option<f32>, D::Error> {
118        Ok(match Option::<Estimate>::deserialize(deserializer)? {
119            None => None,
120            Some(Estimate::Number(value)) => Some(value),
121            Some(Estimate::Text(text)) => Some(match text.as_str() {
122                "Infinity" => f32::INFINITY,
123                "-Infinity" => f32::NEG_INFINITY,
124                "NaN" => f32::NAN,
125                other => {
126                    return Err(serde::de::Error::custom(format!(
127                        "invalid routine estimate `{other}`"
128                    )))
129                }
130            }),
131        })
132    }
133}