Skip to main content

uqa_sql/catalog/security/
dependencies.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Added role dependencies are computed for each independently stored ACL.
8
9use super::{database::DatabaseAclEntry, TableAclEntry, TableSecurity};
10use std::collections::BTreeSet;
11use uqa_core::{catalog_schema::SchemaAclEntry, catalog_sequence::SequenceAclEntry};
12
13pub trait AclRoleReferences {
14    fn role_references(&self) -> (Option<&str>, Option<&str>);
15}
16
17impl AclRoleReferences for TableAclEntry {
18    fn role_references(&self) -> (Option<&str>, Option<&str>) {
19        (self.role.role_name(), self.grantor.as_deref())
20    }
21}
22
23impl AclRoleReferences for SchemaAclEntry {
24    fn role_references(&self) -> (Option<&str>, Option<&str>) {
25        (self.role.role_name(), self.grantor.as_deref())
26    }
27}
28
29impl AclRoleReferences for SequenceAclEntry {
30    fn role_references(&self) -> (Option<&str>, Option<&str>) {
31        (self.role.role_name(), self.grantor.as_deref())
32    }
33}
34
35impl AclRoleReferences for DatabaseAclEntry {
36    fn role_references(&self) -> (Option<&str>, Option<&str>) {
37        (self.role.role_name(), self.grantor.as_deref())
38    }
39}
40
41fn acl_roles<'a, T: AclRoleReferences>(acl: &'a [T], owner: &'a str) -> BTreeSet<&'a str> {
42    acl.iter()
43        .flat_map(|entry| {
44            let (role, grantor) = entry.role_references();
45            [role, Some(grantor.unwrap_or(owner))].into_iter().flatten()
46        })
47        .filter(|role| *role != owner)
48        .collect()
49}
50
51pub fn added_acl_roles<T: AclRoleReferences>(
52    before: &[T],
53    before_owner: &str,
54    after: &[T],
55    after_owner: &str,
56    added: &mut BTreeSet<String>,
57) {
58    let old = acl_roles(before, before_owner);
59    let new = acl_roles(after, after_owner);
60    added.extend(new.difference(&old).map(|role| (*role).to_owned()));
61}
62
63/// An existing dependency in another column or the relation ACL does not replace the dependency of this ACL. Ownership already protects the owner; PUBLIC has no role object.
64pub fn added_table_acl_roles(
65    before: &TableSecurity,
66    after: &TableSecurity,
67    added: &mut BTreeSet<String>,
68) {
69    let mut compare = |old: &[TableAclEntry], new: &[TableAclEntry]| {
70        added_acl_roles(old, &before.role_owner, new, &after.role_owner, added);
71    };
72    compare(
73        before.acl.as_deref().unwrap_or_default(),
74        after.acl.as_deref().unwrap_or_default(),
75    );
76    for (column, acl) in &after.column_acls {
77        compare(
78            before.column_acls.get(column).map_or(&[], Vec::as_slice),
79            acl,
80        );
81    }
82}
83
84#[cfg(test)]
85mod tests;