Skip to main content

uqa_sql/semantics/
table_locks.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Explicit relation-lock authorization and stored-view target scope.
8
9use crate::catalog::roles::RoleReference;
10use crate::{
11    ast::{LockTableTarget, TableLockMode},
12    catalog::{
13        roles::guards::RoleCatalogGuards,
14        security::{
15            table::{role_has_table_privilege, TableAclPrivilege},
16            BoundTableSecurity,
17        },
18    },
19    plan::QueryPlan,
20    SQLError,
21};
22
23pub fn ensure_lock_privilege(
24    roles: &dyn RoleCatalogGuards,
25    security: &BoundTableSecurity,
26    subject: &RoleReference,
27    mode: TableLockMode,
28    name: &str,
29    kind: &str,
30) -> Result<(), SQLError> {
31    let definitions = roles.role_definitions();
32    let memberships = roles.role_memberships();
33    let security = security.resolve(&definitions).map_err(SQLError::Internal)?;
34    let system = crate::catalog::SystemRelation::from_qualified_name(name);
35    if TableAclPrivilege::ALL.into_iter().any(|privilege| {
36        lock_privilege_permits(privilege, mode)
37            && system.map_or_else(
38                || {
39                    role_has_table_privilege(
40                        &security,
41                        subject,
42                        privilege,
43                        &definitions,
44                        &memberships,
45                    )
46                },
47                |relation| {
48                    crate::catalog::security::system_relations::has_table_privilege(
49                        relation,
50                        &security,
51                        subject,
52                        crate::catalog::security::table::TablePrivilegeCheck {
53                            privilege,
54                            grant_option: false,
55                        },
56                        &definitions,
57                        &memberships,
58                    )
59                },
60            )
61    }) {
62        return Ok(());
63    }
64    let (_, local) =
65        uqa_core::RelationIdentity::parse_reference(name).map_err(SQLError::Internal)?;
66    Err(SQLError::Routine {
67        sqlstate: "42501".into(),
68        message: format!("permission denied for {kind} {local}"),
69    })
70}
71
72pub fn lock_privilege_permits(privilege: TableAclPrivilege, mode: TableLockMode) -> bool {
73    match privilege {
74        TableAclPrivilege::Maintain
75        | TableAclPrivilege::Update
76        | TableAclPrivilege::Delete
77        | TableAclPrivilege::Truncate => true,
78        TableAclPrivilege::Select => mode == TableLockMode::AccessShare,
79        TableAclPrivilege::Insert => matches!(
80            mode,
81            TableLockMode::AccessShare | TableLockMode::RowShare | TableLockMode::RowExclusive
82        ),
83        TableAclPrivilege::References | TableAclPrivilege::Trigger => false,
84    }
85}
86
87pub fn view_lock_targets(query: &QueryPlan) -> Result<Vec<LockTableTarget>, SQLError> {
88    let mut targets = Vec::new();
89    crate::binding::view_dependencies::bind_query_plan_relation_targets(
90        &mut query.clone(),
91        &std::collections::BTreeSet::new(),
92        &mut |name, include_descendants| {
93            targets.push(LockTableTarget {
94                name: name.into(),
95                include_descendants,
96            });
97            Ok::<_, SQLError>(name.into())
98        },
99    )?;
100    Ok(targets)
101}
102
103#[cfg(test)]
104mod tests;