Skip to main content

uqa_sql/ast/
routine_security.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Routine security, ownership, configuration, privileges, and role statements.
8
9use std::collections::{BTreeMap, BTreeSet};
10
11use serde::{Deserialize, Serialize};
12
13use super::{
14    AclRoleSpecification, FunctionVolatility, RoleSpecification, RoutineColumnTypeReference,
15};
16
17/// `PARALLEL UNSAFE`, `PARALLEL RESTRICTED`, or `PARALLEL SAFE` routine metadata.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
19pub enum FunctionParallel {
20    #[default]
21    Unsafe,
22    Restricted,
23    Safe,
24}
25
26/// Execution identity and planner leakproofness attached to a routine definition.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
28pub struct RoutineSecurityAttributes {
29    /// `SECURITY DEFINER` when true, otherwise `SECURITY INVOKER`.
30    #[serde(default)]
31    pub security_definer: bool,
32    /// `LEAKPROOF` planner metadata.
33    #[serde(default)]
34    pub leakproof: bool,
35}
36
37/// One explicit `EXECUTE` ACL entry. `None` on `CreateFunction::execute_acl` retains `PostgreSQL`'s default public execution privilege.
38pub type RoutineAclEntry = super::ObjectAclEntry;
39
40/// Old parsed declarations used an empty owner string. This accepts only that unbound marker; stored authority is separately required to carry a valid identity.
41pub(super) fn deserialize_routine_owner<'de, D: serde::Deserializer<'de>>(
42    deserializer: D,
43) -> Result<Option<uqa_core::catalog_role::RoleIdentity>, D::Error> {
44    #[derive(Deserialize)]
45    #[serde(untagged)]
46    enum Owner {
47        Bound(Option<uqa_core::catalog_role::RoleIdentity>),
48        Unbound(String),
49    }
50    match Owner::deserialize(deserializer)? {
51        Owner::Bound(identity) => Ok(identity),
52        Owner::Unbound(name) if name.is_empty() => Ok(None),
53        Owner::Unbound(_) => Err(serde::de::Error::custom(
54            "routine owner requires a role identity",
55        )),
56    }
57}
58
59/// Routine namespace selected by `ALTER FUNCTION`, `ALTER PROCEDURE`, or `ALTER ROUTINE`.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61pub enum AlterRoutineKind {
62    Function,
63    Procedure,
64    Routine,
65}
66
67/// `ALTER FUNCTION | PROCEDURE | ROUTINE name[(input_types)] ...` with an optional exact declared input identity.
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct AlterRoutineStmt {
70    pub kind: AlterRoutineKind,
71    pub name: String,
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub arg_types: Option<Vec<String>>,
74    #[serde(default, skip_serializing_if = "Vec::is_empty")]
75    pub arg_type_references: Vec<Option<RoutineColumnTypeReference>>,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub volatility: Option<FunctionVolatility>,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub strict: Option<bool>,
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub security_definer: Option<bool>,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub leakproof: Option<bool>,
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub parallel: Option<FunctionParallel>,
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub support: Option<String>,
88    #[serde(
89        default,
90        skip_serializing_if = "Option::is_none",
91        with = "super::routine_estimate"
92    )]
93    pub cost: Option<f32>,
94    #[serde(
95        default,
96        skip_serializing_if = "Option::is_none",
97        with = "super::routine_estimate"
98    )]
99    pub rows: Option<f32>,
100    #[serde(default, skip_serializing_if = "Vec::is_empty")]
101    pub config_actions: Vec<RoutineConfigAction>,
102    /// The actions in written order, which `AlterFunction` checks once it has found the routine.
103    #[serde(
104        default,
105        skip_serializing_if = "super::RoutineAttributeClauses::is_empty"
106    )]
107    pub attribute_clauses: super::RoutineAttributeClauses,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
111pub enum RoutineConfigAction {
112    Set { name: String, value: String },
113    FromCurrent { name: String },
114    Reset { name: String },
115    ResetAll,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119pub struct AlterRoutineOwnerStmt {
120    pub kind: AlterRoutineKind,
121    pub name: String,
122    pub arg_types: Option<Vec<String>>,
123    pub arg_type_references: Vec<Option<RoutineColumnTypeReference>>,
124    pub new_owner: RoleSpecification,
125}
126
127/// `ALTER FUNCTION | PROCEDURE | ROUTINE name[(input_types)] RENAME TO new_name`.
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub struct RenameRoutineStmt {
130    pub kind: AlterRoutineKind,
131    pub name: String,
132    #[serde(default, skip_serializing_if = "Option::is_none")]
133    pub arg_types: Option<Vec<String>>,
134    #[serde(default, skip_serializing_if = "Vec::is_empty")]
135    pub arg_type_references: Vec<Option<RoutineColumnTypeReference>>,
136    pub new_name: String,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub struct GrantRoutineItem {
141    pub name: String,
142    pub arg_types: Option<Vec<String>>,
143}
144
145#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
146pub enum RoutineRevokeBehavior {
147    #[default]
148    Restrict,
149    Cascade,
150}
151
152/// A routine privilege clause is retained until target and recipient lookup finish.
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub enum RoutinePrivilege {
155    Execute,
156    Unsupported(String),
157    ColumnsUnsupported,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct GrantRoutineStmt {
162    pub kind: AlterRoutineKind,
163    pub is_grant: bool,
164    pub grant_option: bool,
165    pub grant_option_only: bool,
166    pub items: Vec<GrantRoutineItem>,
167    /// `ALL ... IN SCHEMA`, in written order. Legacy statements omit this and keep explicit `items`.
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub schemas: Option<Vec<String>>,
170    /// Empty means `ALL PRIVILEGES`, also the legacy representation of `EXECUTE`.
171    #[serde(default, skip_serializing_if = "Vec::is_empty")]
172    pub privileges: Vec<RoutinePrivilege>,
173    pub grantees: Vec<AclRoleSpecification>,
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub grantor: Option<RoleSpecification>,
176    #[serde(default)]
177    pub revoke_behavior: RoutineRevokeBehavior,
178}
179
180/// `PostgreSQL` 18 options stored on one role-membership grant.
181#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
182pub struct RoleMembershipOptions {
183    pub admin: Option<bool>,
184    pub inherit: Option<bool>,
185    pub set: Option<bool>,
186}
187
188/// `GRANT role TO member` or `REVOKE role FROM member`.
189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
190pub struct GrantRoleStmt {
191    pub granted_roles: Vec<String>,
192    pub grantee_roles: Vec<RoleSpecification>,
193    pub is_grant: bool,
194    pub options: RoleMembershipOptions,
195    pub grantor: Option<RoleSpecification>,
196    pub cascade: bool,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
200pub enum RoleAttribute {
201    Superuser,
202    Inherit,
203    CreateRole,
204    CreateDb,
205    Login,
206    Replication,
207    BypassRls,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211pub struct CreateRoleStmt {
212    pub name: String,
213    pub attributes: BTreeSet<RoleAttribute>,
214    pub connection_limit: i32,
215    #[serde(default, skip_serializing_if = "Vec::is_empty")]
216    pub in_roles: Vec<RoleSpecification>,
217    #[serde(default, skip_serializing_if = "Vec::is_empty")]
218    pub role_members: Vec<RoleSpecification>,
219    #[serde(default, skip_serializing_if = "Vec::is_empty")]
220    pub admin_members: Vec<RoleSpecification>,
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
224pub enum RoleMembershipAction {
225    Add,
226    Drop,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230pub struct AlterRoleStmt {
231    pub name: RoleSpecification,
232    pub attributes: BTreeMap<RoleAttribute, bool>,
233    pub connection_limit: Option<i32>,
234    pub membership_action: Option<RoleMembershipAction>,
235    pub members: Vec<RoleSpecification>,
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
239pub struct RenameRoleStmt {
240    pub name: String,
241    pub new_name: String,
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
245pub struct DropRoleStmt {
246    pub names: Vec<RoleSpecification>,
247    pub if_exists: bool,
248}