uqa-sql 0.3.5

PostgreSQL-compatible SQL compiler built on libpg_query
Documentation
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

//! Role definitions and membership semantics over explicit catalog values.

use crate::ast::{CreateRoleStmt, RoleAttribute};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;

pub mod memberships;
pub use memberships::{role_can_set, role_inherits};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoleDefinition {
    pub oid: i64,
    pub name: String,
    pub attributes: BTreeSet<RoleAttribute>,
    pub connection_limit: i32,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct RoleMembershipKey {
    pub role: String,
    pub member: String,
    pub grantor: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoleMembership {
    pub oid: i64,
    pub role: String,
    pub member: String,
    pub grantor: String,
    pub admin_option: bool,
    pub inherit_option: bool,
    pub set_option: bool,
}

impl RoleMembership {
    pub fn key(&self) -> RoleMembershipKey {
        RoleMembershipKey {
            role: self.role.clone(),
            member: self.member.clone(),
            grantor: self.grantor.clone(),
        }
    }
}

impl RoleDefinition {
    pub fn bootstrap() -> Self {
        Self {
            oid: 10,
            name: "uqa".into(),
            attributes: BTreeSet::from([
                RoleAttribute::Superuser,
                RoleAttribute::Inherit,
                RoleAttribute::CreateRole,
                RoleAttribute::CreateDb,
                RoleAttribute::Login,
                RoleAttribute::BypassRls,
            ]),
            connection_limit: -1,
        }
    }

    pub fn from_create(statement: &CreateRoleStmt) -> Self {
        Self {
            oid: role_oid(&statement.name),
            name: statement.name.clone(),
            attributes: statement.attributes.clone(),
            connection_limit: statement.connection_limit,
        }
    }

    pub fn has(&self, attribute: RoleAttribute) -> bool {
        self.attributes.contains(&attribute)
    }
}

pub fn role_oid(name: &str) -> i64 {
    if name == "uqa" {
        return 10;
    }
    let mut hash = 14_695_981_039_346_656_037_u64;
    for byte in name.as_bytes() {
        hash ^= u64::from(*byte);
        hash = hash.wrapping_mul(1_099_511_628_211);
    }
    20_000 + i64::try_from(hash % 2_000_000_000).unwrap_or(0)
}

/// Session names used by `CURRENT_USER` and `SESSION_USER` role references.
pub trait RoleReferenceNames {
    fn current_user_name(&self) -> String;
    fn session_user_name(&self) -> String;
}
pub fn resolve_role_reference(names: &dyn RoleReferenceNames, name: &str) -> String {
    match name {
        "CURRENT_USER" => names.current_user_name(),
        "SESSION_USER" => names.session_user_name(),
        other => other.to_string(),
    }
}
pub fn require_role_exists(
    roles: &std::collections::BTreeMap<String, RoleDefinition>,
    name: &str,
) -> Result<(), crate::SQLError> {
    if roles.contains_key(name) {
        return Ok(());
    }
    Err(crate::SQLError::Routine {
        sqlstate: "42704".into(),
        message: format!("role \"{name}\" does not exist"),
    })
}
pub fn require_set_role(
    roles: &std::collections::BTreeMap<String, RoleDefinition>,
    memberships: &std::collections::BTreeMap<RoleMembershipKey, RoleMembership>,
    current: &str,
    target: &str,
) -> Result<(), crate::SQLError> {
    if role_can_set(roles, memberships, current, target) {
        return Ok(());
    }
    Err(crate::SQLError::Routine {
        sqlstate: "42501".into(),
        message: format!("must be able to SET ROLE \"{target}\""),
    })
}

pub mod definition;
pub mod dependencies;
pub mod guards;
pub mod inquiry;
pub mod restoration;