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