uqa_sql/schema/domains/
removal.rs1use crate::catalog::roles::RoleReference;
10use crate::{
11 catalog::{domain::DomainCatalog, roles::RoleReferenceNames, security::BoundSchemaSecurity},
12 SQLError,
13};
14
15pub trait DomainDropCatalog: DomainCatalog {
16 fn schema_security(&self, name: &str) -> Option<BoundSchemaSecurity>;
17 fn resolve_domain_drop_type(&self, name: &str) -> Result<Option<i64>, SQLError>;
18 fn format_domain_drop_type(&self, oid: i64) -> Result<Option<String>, String>;
19}
20pub trait DomainDropAuthority {
21 fn schema_usage(&self, schema: &str, role: &RoleReference) -> bool;
22 fn current_user_has_role_privileges(
23 &self,
24 role: &dyn crate::catalog::roles::identity::RoleSubject,
25 ) -> bool;
26}
27pub struct DomainDropBinding<'a> {
28 pub catalog: &'a dyn DomainDropCatalog,
29 pub authority: &'a dyn DomainDropAuthority,
30 pub session: &'a dyn RoleReferenceNames,
31}
32pub enum BoundDomainDrop {
33 Target(u32),
34 Skipped(String),
35}
36
37pub fn resolve_drop_domain(
38 context: &DomainDropBinding<'_>,
39 name: &str,
40 if_exists: bool,
41) -> Result<BoundDomainDrop, SQLError> {
42 let parsed = crate::parse_regtype_name(name)?
43 .ok_or_else(|| SQLError::Internal("DROP DOMAIN has no type name".into()))?;
44 let label = format!(
45 "{}{}",
46 parsed.names.join("."),
47 "[]".repeat(parsed.array_dimensions)
48 );
49 if let [schema, _] = parsed.names.as_slice() {
50 if context.catalog.schema_security(schema).is_none() {
51 if if_exists {
52 return Ok(BoundDomainDrop::Skipped(format!(
53 "schema \"{schema}\" does not exist, skipping"
54 )));
55 }
56 return Err(SQLError::Routine {
57 sqlstate: "3F000".into(),
58 message: format!("schema \"{schema}\" does not exist"),
59 });
60 }
61 if !context
62 .authority
63 .schema_usage(schema, &context.session.current_role())
64 {
65 return Err(SQLError::Routine {
66 sqlstate: "42501".into(),
67 message: format!("permission denied for schema {schema}"),
68 });
69 }
70 }
71 let oid = context.catalog.resolve_domain_drop_type(name)?;
72 let Some(oid) = oid else {
73 if if_exists {
74 return Ok(BoundDomainDrop::Skipped(format!(
75 "type \"{label}\" does not exist, skipping"
76 )));
77 }
78 return Err(SQLError::Routine {
79 sqlstate: "42704".into(),
80 message: format!("type \"{label}\" does not exist"),
81 });
82 };
83 let domain = u32::try_from(oid)
84 .ok()
85 .and_then(|oid| context.catalog.domain_by_oid(oid))
86 .ok_or_else(|| SQLError::Routine {
87 sqlstate: "42809".into(),
88 message: format!("\"{label}\" is not a domain"),
89 })?;
90 let owns_schema = context
91 .catalog
92 .schema_security(&domain.identity.schema)
93 .is_some_and(|security| {
94 context
95 .authority
96 .current_user_has_role_privileges(&security.role_owner)
97 });
98 if !owns_schema
99 && !context
100 .authority
101 .current_user_has_role_privileges(&domain.owner)
102 {
103 let label = context
104 .catalog
105 .format_domain_drop_type(oid)
106 .map_err(SQLError::Internal)?
107 .ok_or_else(|| SQLError::Internal("DROP DOMAIN target disappeared".into()))?;
108 return Err(SQLError::Routine {
109 sqlstate: "42501".into(),
110 message: format!("must be owner of type {label}"),
111 });
112 }
113 Ok(BoundDomainDrop::Target(domain.oid))
114}