uqa_sql/catalog/security/sequence_inquiry/
value.rs1use 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}