Skip to main content

uqa_sql/semantics/privileges/
context.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Immutable relation authorization and CTE name inputs for column lineage analysis.
8
9use crate::catalog::roles::RoleReference;
10use crate::{
11    catalog::resolution::RelationNameResolution,
12    plan::{CtePlan, QueryPlan},
13    SQLError,
14};
15use std::collections::BTreeMap;
16
17#[derive(Clone, Copy)]
18pub enum PrivilegeRelationKind {
19    System(crate::catalog::SystemRelation),
20    Table,
21    View,
22    MaterializedView,
23    ForeignTable,
24}
25impl PrivilegeRelationKind {
26    pub const fn has_system_columns(self) -> bool {
27        match self {
28            Self::System(relation) => matches!(relation.kind().as_bytes(), b"table"),
29            _ => matches!(self, Self::Table | Self::ForeignTable),
30        }
31    }
32    pub const fn description(self) -> &'static str {
33        match self {
34            Self::System(relation) => relation.kind(),
35            Self::Table => "table",
36            Self::View => "view",
37            Self::MaterializedView => "materialized view",
38            Self::ForeignTable => "foreign table",
39        }
40    }
41}
42pub struct PrivilegeRelation {
43    pub canonical: String,
44    pub columns: Vec<String>,
45    pub kind: PrivilegeRelationKind,
46}
47pub trait PrivilegeCatalog {
48    fn relation(
49        &self,
50        resolution: &RelationNameResolution,
51        name: &str,
52    ) -> Result<Option<PrivilegeRelation>, SQLError>;
53    fn has_select_privilege(
54        &self,
55        resolution: &RelationNameResolution,
56        relation: &PrivilegeRelation,
57        column: Option<&str>,
58        subject: &RoleReference,
59    ) -> Result<bool, SQLError>;
60}
61pub trait PrivilegeCteCatalog {
62    fn is_visible_cte(&self, name: &str) -> bool;
63    fn materialized_columns(&self, name: &str) -> Option<Vec<String>>;
64    fn deferred_reference(&self, name: &str) -> Option<&CtePlan>;
65    fn privilege_subject(&self) -> Result<&RoleReference, SQLError>;
66}
67#[derive(Clone)]
68pub struct PrivilegeScope<'a> {
69    pub catalog: &'a dyn PrivilegeCatalog,
70    pub resolution: RelationNameResolution,
71    pub inherited: &'a dyn PrivilegeCteCatalog,
72    pub scalar_subqueries: Vec<QueryPlan>,
73    local_ctes: BTreeMap<String, CtePlan>,
74    pub(super) include_authorization_only_columns: bool,
75}
76impl<'a> PrivilegeScope<'a> {
77    pub fn new(
78        catalog: &'a dyn PrivilegeCatalog,
79        resolution: RelationNameResolution,
80        inherited: &'a dyn PrivilegeCteCatalog,
81        scalar_subqueries: Vec<QueryPlan>,
82    ) -> Self {
83        Self {
84            catalog,
85            resolution,
86            inherited,
87            scalar_subqueries,
88            local_ctes: BTreeMap::new(),
89            include_authorization_only_columns: true,
90        }
91    }
92    pub fn insert_deferred(&mut self, plan: CtePlan) {
93        self.local_ctes.insert(plan.name.clone(), plan);
94    }
95    pub fn is_visible_cte(&self, name: &str) -> bool {
96        crate::semantics::cte_reference_name(name)
97            .is_some_and(|name| self.local_ctes.contains_key(&name))
98            || self.inherited.is_visible_cte(name)
99    }
100    pub fn materialized_for_scan(&self, name: &str) -> Option<Vec<String>> {
101        let canonical = crate::semantics::cte_reference_name(name)?;
102        if self.local_ctes.contains_key(&canonical) {
103            None
104        } else {
105            self.inherited.materialized_columns(name)
106        }
107    }
108    pub fn deferred_reference(&self, name: &str) -> Option<&CtePlan> {
109        self.local_ctes
110            .get(&crate::semantics::cte_reference_name(name)?)
111            .or_else(|| self.inherited.deferred_reference(name))
112    }
113
114    pub fn privilege_subject(&self) -> Result<&RoleReference, SQLError> {
115        self.inherited.privilege_subject()
116    }
117}