Skip to main content

uqa_core/
catalog_schema.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Durable schema ownership and access-control metadata shared by catalog consumers.
8
9use serde::{Deserialize, Serialize};
10
11/// Grantable privileges carried by one schema ACL path.
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
13pub struct SchemaPrivileges {
14    #[serde(default)]
15    pub usage: bool,
16    #[serde(default)]
17    pub create: bool,
18}
19
20impl SchemaPrivileges {
21    pub const ALL: Self = Self {
22        usage: true,
23        create: true,
24    };
25
26    #[must_use]
27    pub const fn is_empty(self) -> bool {
28        !self.usage && !self.create
29    }
30
31    #[must_use]
32    pub const fn intersects(self, other: Self) -> bool {
33        self.usage && other.usage || self.create && other.create
34    }
35
36    pub fn insert(&mut self, other: Self) {
37        self.usage |= other.usage;
38        self.create |= other.create;
39    }
40
41    pub fn remove(&mut self, other: Self) {
42        self.usage &= !other.usage;
43        self.create &= !other.create;
44    }
45}
46
47/// One explicit schema ACL path. `None` on [`SchemaRow::acl`] retains the owner-only default privileges of an ordinary newly created schema.
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct SchemaAclEntry {
50    pub role: String,
51    /// Legacy persisted entries without an explicit grantor originate from the schema owner.
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub grantor: Option<String>,
54    #[serde(default)]
55    pub privileges: SchemaPrivileges,
56    #[serde(default)]
57    pub grant_options: SchemaPrivileges,
58}
59
60/// Durable schema ownership and ACL metadata.
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct SchemaRow {
63    pub name: String,
64    /// SQL role that owns the schema. Catalogs written before schema security belonged to the bootstrap role.
65    #[serde(default = "default_schema_role_owner")]
66    pub role_owner: String,
67    /// Explicit ACL paths. `None` represents the owner-only default for an ordinary schema.
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub acl: Option<Vec<SchemaAclEntry>>,
70}
71
72impl SchemaRow {
73    #[must_use]
74    pub fn legacy(name: impl Into<String>) -> Self {
75        let name = name.into();
76        let acl = (name == "public").then(|| {
77            vec![
78                SchemaAclEntry {
79                    role: "uqa".into(),
80                    grantor: Some("uqa".into()),
81                    privileges: SchemaPrivileges::ALL,
82                    grant_options: SchemaPrivileges::default(),
83                },
84                SchemaAclEntry {
85                    role: "PUBLIC".into(),
86                    grantor: Some("uqa".into()),
87                    privileges: SchemaPrivileges {
88                        usage: true,
89                        create: false,
90                    },
91                    grant_options: SchemaPrivileges::default(),
92                },
93            ]
94        });
95        Self {
96            name,
97            role_owner: default_schema_role_owner(),
98            acl,
99        }
100    }
101}
102
103fn default_schema_role_owner() -> String {
104    "uqa".into()
105}