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 persistence = catalog.foreign_table_persistence();
44        let security = catalog.foreign_tables();
45        for (relation, persistence) in persistence.iter() {
46            if persistence == RelationPersistence::Temporary {
47                let security = security.get(relation).ok_or_else(|| {
48                    SQLError::Internal(format!(
49                        "temporary foreign table `{}` has no security metadata",
50                        relation.qualified_name()
51                    ))
52                })?;
53                table_dependencies(security, roles, limit, &mut referenced)?;
54            }
55        }
56    }
57    {
58        let sequences = catalog.sequences();
59        let persistence = catalog.sequence_persistence();
60        for (name, security) in sequences.iter() {
61            if persistence.get(name) == Some(&RelationPersistence::Temporary) {
62                let security = security.resolve(roles).map_err(SQLError::Internal)?;
63                add_role(&security.role_owner, roles, limit, &mut referenced)?;
64                acl_dependencies(
65                    security.acl.as_deref().unwrap_or_default(),
66                    roles,
67                    limit,
68                    &mut referenced,
69                )?;
70            }
71        }
72    }
73    Ok(referenced)
74}
75
76fn table_dependencies(
77    security: &BoundTableSecurity,
78    roles: &BTreeMap<String, RoleDefinition>,
79    limit: usize,
80    referenced: &mut BTreeSet<u32>,
81) -> Result<(), SQLError> {
82    // Validate every captured endpoint before publishing native OID leases.
83    let named = security.resolve(roles).map_err(SQLError::Internal)?;
84    add_role(&named.role_owner, roles, limit, referenced)?;
85    acl_dependencies(
86        named.acl.as_deref().unwrap_or_default(),
87        roles,
88        limit,
89        referenced,
90    )?;
91    for acl in named.column_acls.values() {
92        acl_dependencies(acl, roles, limit, referenced)?;
93    }
94    Ok(())
95}
96
97fn acl_dependencies<T: AclRoleReferences>(
98    acl: &[T],
99    roles: &BTreeMap<String, RoleDefinition>,
100    limit: usize,
101    referenced: &mut BTreeSet<u32>,
102) -> Result<(), SQLError> {
103    for entry in acl {
104        let (grantee, grantor) = entry.role_references();
105        if let Some(grantee) = grantee {
106            add_role(grantee, roles, limit, referenced)?;
107        }
108        if let Some(grantor) = grantor {
109            add_role(grantor, roles, limit, referenced)?;
110        }
111    }
112    Ok(())
113}
114
115fn add_role(
116    name: &str,
117    roles: &BTreeMap<String, RoleDefinition>,
118    limit: usize,
119    referenced: &mut BTreeSet<u32>,
120) -> Result<(), SQLError> {
121    let role = roles.get(name).ok_or_else(|| SQLError::Routine {
122        sqlstate: "42704".into(),
123        message: format!("role \"{name}\" does not exist"),
124    })?;
125    if role.oid <= 0 {
126        return Err(SQLError::Internal("invalid role OID".into()));
127    }
128    if role.oid != 10 {
129        referenced.insert(
130            u32::try_from(role.oid).map_err(|_| SQLError::Internal("invalid role OID".into()))?,
131        );
132        if referenced.len() > limit {
133            return Err(SQLError::Routine {
134                sqlstate: "53200".into(),
135                message: "temporary role dependency capacity exhausted".into(),
136            });
137        }
138    }
139    Ok(())
140}