Skip to main content

postrust_core/schema_cache/
routine.rs

1//! Stored function/procedure types.
2
3use crate::api_request::QualifiedIdentifier;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// A stored function or procedure.
8#[derive(Clone, Debug, Serialize, Deserialize)]
9pub struct Routine {
10    /// Schema name
11    pub schema: String,
12    /// Function name
13    pub name: String,
14    /// Description from comment
15    pub description: Option<String>,
16    /// Function parameters
17    pub params: Vec<RoutineParam>,
18    /// Return type
19    pub return_type: RetType,
20    /// Whether the return type is composite (a row type or `record`), meaning
21    /// `SELECT * FROM fn()` expands to the type's own columns rather than a
22    /// single column named after the function.
23    #[serde(default)]
24    pub returns_composite: bool,
25    /// Function volatility
26    pub volatility: FuncVolatility,
27    /// Whether the function has VARIADIC parameters
28    pub has_variadic: bool,
29    /// Isolation level (if set by function)
30    pub isolation_level: Option<String>,
31    /// Function-level GUC settings
32    pub settings: Vec<(String, String)>,
33    /// Whether this is a procedure (vs function)
34    pub is_procedure: bool,
35}
36
37impl Routine {
38    /// Get the qualified identifier for this routine.
39    pub fn qualified_identifier(&self) -> QualifiedIdentifier {
40        QualifiedIdentifier::new(&self.schema, &self.name)
41    }
42
43    /// Check if this function is safe for GET requests.
44    pub fn is_safe_for_get(&self) -> bool {
45        matches!(
46            self.volatility,
47            FuncVolatility::Immutable | FuncVolatility::Stable
48        )
49    }
50
51    /// Get required parameters (no default).
52    pub fn required_params(&self) -> impl Iterator<Item = &RoutineParam> {
53        self.params.iter().filter(|p| p.required)
54    }
55
56    /// Find a parameter by name.
57    pub fn find_param(&self, name: &str) -> Option<&RoutineParam> {
58        self.params.iter().find(|p| p.name == name)
59    }
60}
61
62/// A function parameter.
63#[derive(Clone, Debug, Serialize, Deserialize)]
64pub struct RoutineParam {
65    /// Parameter name
66    pub name: String,
67    /// PostgreSQL type
68    pub param_type: String,
69    /// Type with max length info
70    pub type_max_length: String,
71    /// Whether this parameter is required
72    pub required: bool,
73    /// Whether this is a VARIADIC parameter
74    pub variadic: bool,
75}
76
77/// Function return type.
78#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
79pub enum RetType {
80    /// Returns a single value
81    Single(String),
82    /// Returns a set of values (SETOF)
83    SetOf(String),
84    /// Returns a table (RETURNS TABLE)
85    Table(Vec<(String, String)>),
86    /// Returns void
87    Void,
88}
89
90impl RetType {
91    /// Check if this returns multiple rows.
92    pub fn is_set_returning(&self) -> bool {
93        matches!(self, Self::SetOf(_) | Self::Table(_))
94    }
95
96    /// Get the base type name.
97    pub fn type_name(&self) -> Option<&str> {
98        match self {
99            Self::Single(t) => Some(t),
100            Self::SetOf(t) => Some(t),
101            Self::Table(_) => None,
102            Self::Void => None,
103        }
104    }
105}
106
107/// Function volatility category.
108#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
109pub enum FuncVolatility {
110    /// Function cannot modify database and always returns same result for same inputs
111    Immutable,
112    /// Function cannot modify database but result may change across queries
113    Stable,
114    /// Function can modify database
115    Volatile,
116}
117
118impl FuncVolatility {
119    pub fn from_char(c: char) -> Self {
120        match c {
121            'i' => Self::Immutable,
122            's' => Self::Stable,
123            _ => Self::Volatile,
124        }
125    }
126}
127
128/// Map of qualified identifier to routines (overloaded functions share name).
129pub type RoutineMap = HashMap<QualifiedIdentifier, Vec<Routine>>;
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_routine_is_safe_for_get() {
137        let mut routine = Routine {
138            schema: "public".into(),
139            name: "get_users".into(),
140            description: None,
141            params: vec![],
142            return_type: RetType::SetOf("users".into()),
143            returns_composite: true,
144            volatility: FuncVolatility::Stable,
145            has_variadic: false,
146            isolation_level: None,
147            settings: vec![],
148            is_procedure: false,
149        };
150
151        assert!(routine.is_safe_for_get());
152
153        routine.volatility = FuncVolatility::Volatile;
154        assert!(!routine.is_safe_for_get());
155    }
156
157    #[test]
158    fn test_ret_type_is_set_returning() {
159        assert!(!RetType::Single("text".into()).is_set_returning());
160        assert!(RetType::SetOf("users".into()).is_set_returning());
161        assert!(RetType::Table(vec![("id".into(), "int".into())]).is_set_returning());
162        assert!(!RetType::Void.is_set_returning());
163    }
164
165    #[test]
166    fn test_func_volatility_from_char() {
167        assert_eq!(FuncVolatility::from_char('i'), FuncVolatility::Immutable);
168        assert_eq!(FuncVolatility::from_char('s'), FuncVolatility::Stable);
169        assert_eq!(FuncVolatility::from_char('v'), FuncVolatility::Volatile);
170        assert_eq!(FuncVolatility::from_char('x'), FuncVolatility::Volatile);
171    }
172}