Skip to main content

uqa_core/
catalog_acl.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Durable access-control metadata for table-shaped relations.
8
9use serde::{Deserialize, Serialize};
10
11/// Grantable privileges carried by one table-shaped relation ACL path.
12#[expect(
13    clippy::struct_excessive_bools,
14    reason = "models PostgreSQL's independently grantable table-shaped relation privileges"
15)]
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
17pub struct TablePrivileges {
18    #[serde(default)]
19    pub select: bool,
20    #[serde(default)]
21    pub insert: bool,
22    #[serde(default)]
23    pub update: bool,
24    #[serde(default)]
25    pub delete: bool,
26    #[serde(default)]
27    pub truncate: bool,
28    #[serde(default)]
29    pub references: bool,
30    #[serde(default)]
31    pub trigger: bool,
32    #[serde(default)]
33    pub maintain: bool,
34}
35
36impl TablePrivileges {
37    pub const ALL: Self = Self {
38        select: true,
39        insert: true,
40        update: true,
41        delete: true,
42        truncate: true,
43        references: true,
44        trigger: true,
45        maintain: true,
46    };
47
48    #[must_use]
49    pub const fn is_empty(self) -> bool {
50        !self.select
51            && !self.insert
52            && !self.update
53            && !self.delete
54            && !self.truncate
55            && !self.references
56            && !self.trigger
57            && !self.maintain
58    }
59
60    #[must_use]
61    pub const fn intersects(self, other: Self) -> bool {
62        self.select && other.select
63            || self.insert && other.insert
64            || self.update && other.update
65            || self.delete && other.delete
66            || self.truncate && other.truncate
67            || self.references && other.references
68            || self.trigger && other.trigger
69            || self.maintain && other.maintain
70    }
71
72    pub fn insert(&mut self, other: Self) {
73        self.select |= other.select;
74        self.insert |= other.insert;
75        self.update |= other.update;
76        self.delete |= other.delete;
77        self.truncate |= other.truncate;
78        self.references |= other.references;
79        self.trigger |= other.trigger;
80        self.maintain |= other.maintain;
81    }
82
83    pub fn remove(&mut self, other: Self) {
84        self.select &= !other.select;
85        self.insert &= !other.insert;
86        self.update &= !other.update;
87        self.delete &= !other.delete;
88        self.truncate &= !other.truncate;
89        self.references &= !other.references;
90        self.trigger &= !other.trigger;
91        self.maintain &= !other.maintain;
92    }
93}
94
95/// One explicit table-shaped relation ACL path. Legacy entries without an explicit grantor originate from the relation owner.
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct TableAclEntry {
98    pub role: String,
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub grantor: Option<String>,
101    #[serde(default)]
102    pub privileges: TablePrivileges,
103    #[serde(default)]
104    pub grant_options: TablePrivileges,
105}