Skip to main content

uqa_sql/catalog/roles/dependencies/
temporary.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Role references of session-local relations are catalog dependencies after commit.
8
9use super::context::TemporaryRoleDependencyCatalog;
10use crate::{
11    ast::RelationPersistence,
12    catalog::{
13        roles::RoleDefinition,
14        security::{dependencies::AclRoleReferences, BoundTableSecurity},
15    },
16    SQLError,
17};
18use std::collections::{BTreeMap, BTreeSet};
19
20pub fn role_dependencies(
21    catalog: &dyn TemporaryRoleDependencyCatalog,
22    roles: &BTreeMap<String, RoleDefinition>,
23    limit: usize,
24) -> Result<BTreeSet<u32>, SQLError> {
25    let mut referenced = BTreeSet::new();
26    {
27        let tables = catalog.tables();
28        for (_, table) in tables.iter() {
29            if table.persistence() == RelationPersistence::Temporary {
30                table_dependencies(&table.security(), roles, limit, &mut referenced)?;
31            }
32        }
33    }
34    {
35        let views = catalog.views();
36        for view in views.values() {
37            if view.persistence == RelationPersistence::Temporary {
38                table_dependencies(&view.security(), roles, limit, &mut referenced)?;
39            }
40        }
41    }
42    {
43        let sequences = catalog.sequences();
44        let persistence = catalog.sequence_persistence();
45        for (name, security) in sequences.iter() {
46            if persistence.get(name) == Some(&RelationPersistence::Temporary) {
47                let security = security.resolve(roles).map_err(SQLError::Internal)?;
48                add_role(&security.role_owner, roles, limit, &mut referenced)?;
49                acl_dependencies(
50                    security.acl.as_deref().unwrap_or_default(),
51                    roles,
52                    limit,
53                    &mut referenced,
54                )?;
55            }
56        }
57    }
58    Ok(referenced)
59}
60
61fn table_dependencies(
62    security: &BoundTableSecurity,
63    roles: &BTreeMap<String, RoleDefinition>,
64    limit: usize,
65    referenced: &mut BTreeSet<u32>,
66) -> Result<(), SQLError> {
67    // Validate every captured endpoint before publishing native OID leases.
68    let named = security.resolve(roles).map_err(SQLError::Internal)?;
69    add_role(&named.role_owner, roles, limit, referenced)?;
70    acl_dependencies(
71        named.acl.as_deref().unwrap_or_default(),
72        roles,
73        limit,
74        referenced,
75    )?;
76    for acl in named.column_acls.values() {
77        acl_dependencies(acl, roles, limit, referenced)?;
78    }
79    Ok(())
80}
81
82fn acl_dependencies<T: AclRoleReferences>(
83    acl: &[T],
84    roles: &BTreeMap<String, RoleDefinition>,
85    limit: usize,
86    referenced: &mut BTreeSet<u32>,
87) -> Result<(), SQLError> {
88    for entry in acl {
89        let (grantee, grantor) = entry.role_references();
90        if let Some(grantee) = grantee {
91            add_role(grantee, roles, limit, referenced)?;
92        }
93        if let Some(grantor) = grantor {
94            add_role(grantor, roles, limit, referenced)?;
95        }
96    }
97    Ok(())
98}
99
100fn add_role(
101    name: &str,
102    roles: &BTreeMap<String, RoleDefinition>,
103    limit: usize,
104    referenced: &mut BTreeSet<u32>,
105) -> Result<(), SQLError> {
106    let role = roles.get(name).ok_or_else(|| SQLError::Routine {
107        sqlstate: "42704".into(),
108        message: format!("role \"{name}\" does not exist"),
109    })?;
110    if role.oid <= 0 {
111        return Err(SQLError::Internal("invalid role OID".into()));
112    }
113    if role.oid != 10 {
114        referenced.insert(
115            u32::try_from(role.oid).map_err(|_| SQLError::Internal("invalid role OID".into()))?,
116        );
117        if referenced.len() > limit {
118            return Err(SQLError::Routine {
119                sqlstate: "53200".into(),
120                message: "temporary role dependency capacity exhausted".into(),
121            });
122        }
123    }
124    Ok(())
125}