Skip to main content

uqa_sql/catalog/
roles.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Role definitions and membership semantics over explicit catalog values.
8
9use crate::ast::{CreateRoleStmt, RoleAttribute};
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeSet;
12
13pub mod memberships;
14pub use memberships::{role_can_set, role_inherits};
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct RoleDefinition {
18    pub oid: i64,
19    pub name: String,
20    pub attributes: BTreeSet<RoleAttribute>,
21    pub connection_limit: i32,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
25pub struct RoleMembershipKey {
26    pub role: String,
27    pub member: String,
28    pub grantor: String,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct RoleMembership {
33    pub oid: i64,
34    pub role: String,
35    pub member: String,
36    pub grantor: String,
37    pub admin_option: bool,
38    pub inherit_option: bool,
39    pub set_option: bool,
40}
41
42impl RoleMembership {
43    pub fn key(&self) -> RoleMembershipKey {
44        RoleMembershipKey {
45            role: self.role.clone(),
46            member: self.member.clone(),
47            grantor: self.grantor.clone(),
48        }
49    }
50}
51
52impl RoleDefinition {
53    pub fn bootstrap() -> Self {
54        Self {
55            oid: 10,
56            name: "uqa".into(),
57            attributes: BTreeSet::from([
58                RoleAttribute::Superuser,
59                RoleAttribute::Inherit,
60                RoleAttribute::CreateRole,
61                RoleAttribute::CreateDb,
62                RoleAttribute::Login,
63                RoleAttribute::BypassRls,
64            ]),
65            connection_limit: -1,
66        }
67    }
68
69    pub fn from_create(statement: &CreateRoleStmt) -> Self {
70        Self {
71            oid: role_oid(&statement.name),
72            name: statement.name.clone(),
73            attributes: statement.attributes.clone(),
74            connection_limit: statement.connection_limit,
75        }
76    }
77
78    pub fn has(&self, attribute: RoleAttribute) -> bool {
79        self.attributes.contains(&attribute)
80    }
81}
82
83pub fn role_oid(name: &str) -> i64 {
84    if name == "uqa" {
85        return 10;
86    }
87    let mut hash = 14_695_981_039_346_656_037_u64;
88    for byte in name.as_bytes() {
89        hash ^= u64::from(*byte);
90        hash = hash.wrapping_mul(1_099_511_628_211);
91    }
92    20_000 + i64::try_from(hash % 2_000_000_000).unwrap_or(0)
93}
94
95/// Session names used by `CURRENT_USER` and `SESSION_USER` role references.
96pub trait RoleReferenceNames {
97    fn current_user_name(&self) -> String;
98    fn session_user_name(&self) -> String;
99}
100pub fn resolve_role_reference(names: &dyn RoleReferenceNames, name: &str) -> String {
101    match name {
102        "CURRENT_USER" => names.current_user_name(),
103        "SESSION_USER" => names.session_user_name(),
104        other => other.to_string(),
105    }
106}
107pub fn require_role_exists(
108    roles: &std::collections::BTreeMap<String, RoleDefinition>,
109    name: &str,
110) -> Result<(), crate::SQLError> {
111    if roles.contains_key(name) {
112        return Ok(());
113    }
114    Err(crate::SQLError::Routine {
115        sqlstate: "42704".into(),
116        message: format!("role \"{name}\" does not exist"),
117    })
118}
119pub fn require_set_role(
120    roles: &std::collections::BTreeMap<String, RoleDefinition>,
121    memberships: &std::collections::BTreeMap<RoleMembershipKey, RoleMembership>,
122    current: &str,
123    target: &str,
124) -> Result<(), crate::SQLError> {
125    if role_can_set(roles, memberships, current, target) {
126        return Ok(());
127    }
128    Err(crate::SQLError::Routine {
129        sqlstate: "42501".into(),
130        message: format!("must be able to SET ROLE \"{target}\""),
131    })
132}
133
134pub mod definition;
135pub mod dependencies;
136pub mod guards;
137pub mod inquiry;
138pub mod restoration;