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.
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct RoutineAclEntry {
40    /// An explicit null means PUBLIC; a missing grantee is invalid.
41    #[serde(deserialize_with = "Deserialize::deserialize")]
42    pub role: Option<uqa_core::catalog_role::RoleIdentity>,
43    pub grantor: uqa_core::catalog_role::RoleIdentity,
44    pub grant_option: bool,
45}
46
47/// Old parsed declarations used an empty owner string. This accepts only that unbound marker; stored authority is separately required to carry a valid identity.
48pub(super) fn deserialize_routine_owner<'de, D: serde::Deserializer<'de>>(
49    deserializer: D,
50) -> Result<Option<uqa_core::catalog_role::RoleIdentity>, D::Error> {
51    #[derive(Deserialize)]
52    #[serde(untagged)]
53    enum Owner {
54        Bound(Option<uqa_core::catalog_role::RoleIdentity>),
55        Unbound(String),
56    }
57    match Owner::deserialize(deserializer)? {
58        Owner::Bound(identity) => Ok(identity),
59        Owner::Unbound(name) if name.is_empty() => Ok(None),
60        Owner::Unbound(_) => Err(serde::de::Error::custom(
61            "routine owner requires a role identity",
62        )),
63    }
64}
65
66/// Routine namespace selected by `ALTER FUNCTION`, `ALTER PROCEDURE`, or `ALTER ROUTINE`.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68pub enum AlterRoutineKind {
69    Function,
70    Procedure,
71    Routine,
72}
73
74/// `ALTER FUNCTION | PROCEDURE | ROUTINE name[(input_types)] ...` with an optional exact declared input identity.
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub struct AlterRoutineStmt {
77    pub kind: AlterRoutineKind,
78    pub name: String,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub arg_types: Option<Vec<String>>,
81    #[serde(default, skip_serializing_if = "Vec::is_empty")]
82    pub arg_type_references: Vec<Option<RoutineColumnTypeReference>>,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub volatility: Option<FunctionVolatility>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub strict: Option<bool>,
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub security_definer: Option<bool>,
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub leakproof: Option<bool>,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub parallel: Option<FunctionParallel>,
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub support: Option<String>,
95    #[serde(default, skip_serializing_if = "Vec::is_empty")]
96    pub config_actions: Vec<RoutineConfigAction>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub enum RoutineConfigAction {
101    Set { name: String, value: String },
102    FromCurrent { name: String },
103    Reset { name: String },
104    ResetAll,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108pub struct AlterRoutineOwnerStmt {
109    pub kind: AlterRoutineKind,
110    pub name: String,
111    pub arg_types: Option<Vec<String>>,
112    pub arg_type_references: Vec<Option<RoutineColumnTypeReference>>,
113    pub new_owner: RoleSpecification,
114}
115
116/// `ALTER FUNCTION | PROCEDURE | ROUTINE name[(input_types)] RENAME TO new_name`.
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub struct RenameRoutineStmt {
119    pub kind: AlterRoutineKind,
120    pub name: String,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub arg_types: Option<Vec<String>>,
123    #[serde(default, skip_serializing_if = "Vec::is_empty")]
124    pub arg_type_references: Vec<Option<RoutineColumnTypeReference>>,
125    pub new_name: String,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub struct GrantRoutineItem {
130    pub name: String,
131    pub arg_types: Option<Vec<String>>,
132}
133
134#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
135pub enum RoutineRevokeBehavior {
136    #[default]
137    Restrict,
138    Cascade,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142pub struct GrantRoutineStmt {
143    pub kind: AlterRoutineKind,
144    pub is_grant: bool,
145    pub grant_option: bool,
146    pub grant_option_only: bool,
147    pub items: Vec<GrantRoutineItem>,
148    pub grantees: Vec<AclRoleSpecification>,
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub grantor: Option<RoleSpecification>,
151    #[serde(default)]
152    pub revoke_behavior: RoutineRevokeBehavior,
153}
154
155/// `PostgreSQL` 18 options stored on one role-membership grant.
156#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
157pub struct RoleMembershipOptions {
158    pub admin: Option<bool>,
159    pub inherit: Option<bool>,
160    pub set: Option<bool>,
161}
162
163/// `GRANT role TO member` or `REVOKE role FROM member`.
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165pub struct GrantRoleStmt {
166    pub granted_roles: Vec<String>,
167    pub grantee_roles: Vec<RoleSpecification>,
168    pub is_grant: bool,
169    pub options: RoleMembershipOptions,
170    pub grantor: Option<RoleSpecification>,
171    pub cascade: bool,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
175pub enum RoleAttribute {
176    Superuser,
177    Inherit,
178    CreateRole,
179    CreateDb,
180    Login,
181    Replication,
182    BypassRls,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct CreateRoleStmt {
187    pub name: String,
188    pub attributes: BTreeSet<RoleAttribute>,
189    pub connection_limit: i32,
190    #[serde(default, skip_serializing_if = "Vec::is_empty")]
191    pub in_roles: Vec<RoleSpecification>,
192    #[serde(default, skip_serializing_if = "Vec::is_empty")]
193    pub role_members: Vec<RoleSpecification>,
194    #[serde(default, skip_serializing_if = "Vec::is_empty")]
195    pub admin_members: Vec<RoleSpecification>,
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
199pub enum RoleMembershipAction {
200    Add,
201    Drop,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
205pub struct AlterRoleStmt {
206    pub name: RoleSpecification,
207    pub attributes: BTreeMap<RoleAttribute, bool>,
208    pub connection_limit: Option<i32>,
209    pub membership_action: Option<RoleMembershipAction>,
210    pub members: Vec<RoleSpecification>,
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214pub struct RenameRoleStmt {
215    pub name: String,
216    pub new_name: String,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
220pub struct DropRoleStmt {
221    pub names: Vec<RoleSpecification>,
222    pub if_exists: bool,
223}