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