Skip to main content

uqa_sql/catalog/security/
sequence_grants.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Sequence GRANT target binding, namespace rules and ACL candidates.
8
9use super::{
10    sequence::{grant_acl, revoke_acl, select_acl_grantor, AclPrivilege},
11    sequence_inquiry::SequencePrivilegeResolution,
12    SequenceSecurity,
13};
14use crate::{
15    ast::{GrantSequenceStmt, SequenceRevokeBehavior},
16    catalog::{
17        resolution::RelationResolution,
18        roles::{RoleDefinition, RoleMembership, RoleMembershipKey},
19    },
20    SQLError,
21};
22use std::collections::BTreeMap;
23use uqa_core::RelationIdentity;
24
25pub use super::grants::{
26    bind_grant_schemas as bind_sequence_grant_schemas, GrantNamespace as SequenceGrantNamespace,
27};
28
29pub struct ResolvedSequenceGrantTarget {
30    pub requested: String,
31    pub name: String,
32    pub relation: RelationIdentity,
33    pub kind: &'static str,
34}
35
36pub fn bind_named_sequence_grants(
37    resolution: &dyn SequencePrivilegeResolution,
38    names: &[String],
39) -> Result<Vec<ResolvedSequenceGrantTarget>, SQLError> {
40    let mut resolved = Vec::with_capacity(names.len());
41    for requested in names {
42        let (name, kind) = match resolution.visible_relation_kind(requested)? {
43            RelationResolution::Found(name, kind) => (name, kind),
44            RelationResolution::MissingSchema(schema) => {
45                return Err(SQLError::Routine {
46                    sqlstate: "3F000".into(),
47                    message: format!("schema \"{schema}\" does not exist"),
48                });
49            }
50            RelationResolution::MissingRelation => {
51                return Err(SQLError::Routine {
52                    sqlstate: "42P01".into(),
53                    message: format!("relation \"{requested}\" does not exist"),
54                });
55            }
56        };
57        let relation = RelationIdentity::from_legacy_name(&name)
58            .map_err(|error| SQLError::Internal(format!("resolve sequence `{name}`: {error}")))?;
59        resolved.push(ResolvedSequenceGrantTarget {
60            requested: requested.clone(),
61            name,
62            relation,
63            kind,
64        });
65    }
66    Ok(resolved)
67}
68
69pub fn sequence_grants_in_schemas<'a>(
70    resolved_schemas: &[String],
71    sequences: impl Iterator<Item = &'a RelationIdentity>,
72) -> Vec<ResolvedSequenceGrantTarget> {
73    let mut targets = sequences
74        .filter(|relation| resolved_schemas.contains(&relation.schema))
75        .map(|relation| ResolvedSequenceGrantTarget {
76            requested: relation.qualified_name(),
77            name: relation.qualified_name(),
78            relation: relation.clone(),
79            kind: "sequence",
80        })
81        .collect::<Vec<_>>();
82    targets.sort_by(|left, right| left.relation.cmp(&right.relation));
83    targets
84}
85
86pub fn apply_sequence_acl(
87    statement: &GrantSequenceStmt,
88    grantees: &[String],
89    privileges: &[AclPrivilege],
90    current_user: &str,
91    roles: &BTreeMap<String, RoleDefinition>,
92    memberships: &BTreeMap<RoleMembershipKey, RoleMembership>,
93    current: &SequenceSecurity,
94) -> Result<(SequenceSecurity, usize), SQLError> {
95    let grantors = privileges
96        .iter()
97        .map(|privilege| {
98            (
99                *privilege,
100                select_acl_grantor(current, *privilege, current_user, roles, memberships),
101            )
102        })
103        .collect::<Vec<_>>();
104    let grantable = grantors
105        .iter()
106        .filter(|(_, grantor)| grantor.is_some())
107        .count();
108    let mut next = current.clone();
109    for (privilege, grantor) in grantors {
110        let Some(grantor) = grantor else {
111            continue;
112        };
113        if statement.is_grant {
114            grant_acl(
115                &mut next,
116                privilege,
117                grantees,
118                &grantor,
119                statement.grant_option,
120            );
121        } else {
122            revoke_acl(
123                &mut next,
124                privilege,
125                grantees,
126                &grantor,
127                statement.grant_option_only,
128                statement.revoke_behavior == SequenceRevokeBehavior::Cascade,
129            )?;
130        }
131    }
132    Ok((next, grantable))
133}
134
135pub fn validate_sequence_acl_roles(
136    statement: &GrantSequenceStmt,
137    grantees: &[String],
138    requested_grantor: Option<&str>,
139    current_user: &str,
140    roles: &BTreeMap<String, RoleDefinition>,
141) -> Result<(), SQLError> {
142    for role in grantees {
143        if role != "PUBLIC" && !roles.contains_key(role) {
144            return Err(SQLError::Routine {
145                sqlstate: "42704".into(),
146                message: format!("role \"{role}\" does not exist"),
147            });
148        }
149    }
150    if statement.is_grant && statement.grant_option && grantees.iter().any(|role| role == "PUBLIC")
151    {
152        return Err(SQLError::Routine {
153            sqlstate: "0LP01".into(),
154            message: "grant options can only be granted to roles".into(),
155        });
156    }
157    if let Some(requested_grantor) = requested_grantor {
158        if !roles.contains_key(requested_grantor) {
159            return Err(SQLError::Routine {
160                sqlstate: "42704".into(),
161                message: format!("role \"{requested_grantor}\" does not exist"),
162            });
163        }
164        if requested_grantor != current_user {
165            return Err(SQLError::Routine {
166                sqlstate: "0A000".into(),
167                message: "grantor must be current user".into(),
168            });
169        }
170    }
171    Ok(())
172}
173
174pub fn validate_sequence_grant_target_kinds(
175    targets: &[ResolvedSequenceGrantTarget],
176) -> Result<(), SQLError> {
177    for target in targets {
178        if target.kind == "sequence" {
179            continue;
180        }
181        return Err(SQLError::Routine {
182            sqlstate: "42809".into(),
183            message: format!("\"{}\" is not a sequence", target.requested),
184        });
185    }
186    Ok(())
187}
188
189pub fn sequence_acl_warning(is_grant: bool, partial: bool, name: &str) -> (&'static str, String) {
190    let message = match (is_grant, partial) {
191        (true, true) => format!("not all privileges were granted for \"{name}\""),
192        (true, false) => format!("no privileges were granted for \"{name}\""),
193        (false, true) => format!("not all privileges could be revoked for \"{name}\""),
194        (false, false) => format!("no privileges could be revoked for \"{name}\""),
195    };
196    ("WARNING", message)
197}