Skip to main content

uqa_sql/catalog/security/sequence_inquiry/
value.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Strict arguments, retained role subjects and target diagnostics for sequence privilege inquiry.
8
9use super::{
10    acl, role_has_privilege, PrivilegeCheck, RelationIdentity, RelationResolution,
11    RoleCatalogGuards, RoleReferenceNames, RoleSubject, SQLError, SequencePrivilegeResolution,
12    SequenceSecurityCatalog, Value,
13};
14use crate::catalog::roles::RoleReference;
15use uqa_core::catalog_acl::AclGrantee;
16
17pub struct SequencePrivilegeArguments<'a> {
18    subject: Option<&'a Value>,
19    sequence: &'a Value,
20    privilege: &'a Value,
21}
22
23#[derive(Clone, Copy)]
24pub enum SequencePrivilegeTarget<'a> {
25    Name(&'a str),
26    Oid(i64),
27}
28
29pub struct SequencePrivilegeRequest<'a> {
30    pub target: SequencePrivilegeTarget<'a>,
31    subject: Option<RoleReference>,
32    checks: Vec<PrivilegeCheck>,
33}
34
35impl<'a> SequencePrivilegeArguments<'a> {
36    pub fn parse(arguments: &'a [Value]) -> Result<Option<Self>, SQLError> {
37        if arguments.iter().any(|argument| argument == &Value::Null) {
38            return Ok(None);
39        }
40        let (subject, sequence, privilege) = match arguments {
41            [sequence, privilege] => (None, sequence, privilege),
42            [subject, sequence, privilege] => (Some(subject), sequence, privilege),
43            _ => {
44                return Err(SQLError::BadArity {
45                    name: "has_sequence_privilege".into(),
46                    expected: "2 or 3".into(),
47                    actual: arguments.len(),
48                })
49            }
50        };
51        Ok(Some(Self {
52            subject,
53            sequence,
54            privilege,
55        }))
56    }
57
58    pub fn has_explicit_subject(&self) -> bool {
59        self.subject.is_some()
60    }
61
62    pub fn bind(
63        self,
64        names: &dyn RoleReferenceNames,
65        roles: &dyn RoleCatalogGuards,
66    ) -> Result<SequencePrivilegeRequest<'a>, SQLError> {
67        let subject = match self.subject {
68            Some(value) => super::super::role_bindings::bind_inquiry_subject(
69                value,
70                &roles.role_definitions(),
71                "has_sequence_privilege",
72            )?,
73            None => Some(names.current_role()),
74        };
75        let privilege = match self.privilege {
76            Value::Str(privilege) | Value::FixedChar(privilege) => privilege,
77            other => {
78                return Err(SQLError::TypeMismatch(format!(
79                    "has_sequence_privilege privilege must be text, got {other:?}"
80                )))
81            }
82        };
83        let checks = acl::parse_privilege_checks(privilege)?;
84        let target = match self.sequence {
85            Value::Str(reference) | Value::FixedChar(reference) => {
86                SequencePrivilegeTarget::Name(reference)
87            }
88            Value::Int(oid) => SequencePrivilegeTarget::Oid(*oid),
89            other => {
90                return Err(SQLError::TypeMismatch(format!(
91                    "has_sequence_privilege sequence must be text or oid, got {other:?}"
92                )))
93            }
94        };
95        Ok(SequencePrivilegeRequest {
96            target,
97            subject,
98            checks,
99        })
100    }
101}
102
103impl SequencePrivilegeRequest<'_> {
104    pub fn evaluate(
105        &self,
106        relation: &RelationIdentity,
107        roles: &dyn RoleCatalogGuards,
108        catalog: &dyn SequenceSecurityCatalog,
109    ) -> Result<Value, SQLError> {
110        let security = catalog
111            .security_read()
112            .get(relation)
113            .cloned()
114            .ok_or_else(|| {
115                SQLError::Internal(format!(
116                    "sequence `{}` has no security metadata",
117                    relation.qualified_name()
118                ))
119            })?;
120        let definitions = roles.role_definitions();
121        let security = security.resolve(&definitions).map_err(SQLError::Internal)?;
122        let memberships = roles.role_memberships();
123        let subject: &dyn RoleSubject = self
124            .subject
125            .as_ref()
126            .map_or(&AclGrantee::Public as &dyn RoleSubject, |subject| {
127                subject as &dyn RoleSubject
128            });
129        Ok(Value::Bool(self.checks.iter().any(|check| {
130            role_has_privilege(&security, subject, *check, &definitions, &memberships)
131        })))
132    }
133}
134
135impl SequencePrivilegeTarget<'_> {
136    pub fn resolve(
137        self,
138        resolution: &dyn SequencePrivilegeResolution,
139    ) -> Result<Option<(String, RelationIdentity)>, SQLError> {
140        match self {
141            Self::Oid(oid) => resolution.sequence_privilege_oid(oid),
142            Self::Name(reference) => {
143                let name = match resolution.visible_relation_kind(reference)? {
144                    RelationResolution::Found(name, "sequence") => name,
145                    RelationResolution::Found(_, _) => {
146                        return Err(SQLError::Routine {
147                            sqlstate: "42809".into(),
148                            message: format!("\"{reference}\" is not a sequence"),
149                        })
150                    }
151                    RelationResolution::MissingSchema(schema) => {
152                        return Err(SQLError::Routine {
153                            sqlstate: "3F000".into(),
154                            message: format!("schema \"{schema}\" does not exist"),
155                        })
156                    }
157                    RelationResolution::MissingRelation => return Err(missing_sequence(reference)),
158                };
159                let relation = RelationIdentity::from_legacy_name(&name).map_err(|error| {
160                    SQLError::Internal(format!("resolve sequence `{name}`: {error}"))
161                })?;
162                Ok(Some((name, relation)))
163            }
164        }
165    }
166}
167
168pub fn missing_sequence(reference: &str) -> SQLError {
169    SQLError::Routine {
170        sqlstate: "42P01".into(),
171        message: format!("relation \"{reference}\" does not exist"),
172    }
173}