1use crate::ast::{ObjectAclEntry, TypeObjectKind};
10use crate::catalog::{
11 composite_type::StoredComposite, domain::StoredDomain, enum_type::StoredEnum,
12};
13use crate::schema::domains::removal::{RowTypeRelation, TypeObjectBinding};
14use crate::SQLError;
15use uqa_core::{catalog_role::RoleIdentity, RelationIdentity};
16
17#[derive(Debug, Clone)]
19pub enum TypeObject {
20 Enum(StoredEnum),
21 Domain(Box<StoredDomain>),
22 Composite(Box<StoredComposite>),
24}
25
26impl TypeObject {
27 #[must_use]
28 pub fn oid(&self) -> u32 {
29 match self {
30 Self::Enum(definition) => definition.oid,
31 Self::Domain(domain) => domain.oid,
32 Self::Composite(definition) => definition.oid,
33 }
34 }
35
36 #[must_use]
37 pub fn identity(&self) -> &RelationIdentity {
38 match self {
39 Self::Enum(definition) => &definition.identity,
40 Self::Domain(domain) => &domain.identity,
41 Self::Composite(definition) => &definition.identity,
42 }
43 }
44
45 #[must_use]
46 pub fn owner(&self) -> RoleIdentity {
47 match self {
48 Self::Enum(definition) => definition.owner,
49 Self::Domain(domain) => domain.owner,
50 Self::Composite(definition) => definition.owner,
51 }
52 }
53
54 #[must_use]
55 pub fn array_name(&self) -> String {
56 match self {
57 Self::Enum(definition) => definition.array_name.clone(),
58 Self::Domain(domain) => domain.array_type_name(),
59 Self::Composite(definition) => definition.array_name.clone(),
60 }
61 }
62
63 #[must_use]
64 pub fn usage_acl(&self) -> Option<&[ObjectAclEntry]> {
65 match self {
66 Self::Enum(definition) => definition.usage_acl.as_deref(),
67 Self::Domain(domain) => domain.usage_acl.as_deref(),
68 Self::Composite(definition) => definition.usage_acl.as_deref(),
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
75enum ResolvedKind {
76 User(TypeObject),
77 Array {
79 element: u32,
80 },
81 RowType(RowTypeRelation),
82 System,
84}
85
86#[derive(Debug, Clone)]
87pub struct ResolvedTypeObject {
88 oid: u32,
89 kind: ResolvedKind,
90}
91
92pub fn resolve_named_type_object(
94 context: &TypeObjectBinding<'_>,
95 name: &str,
96) -> Result<ResolvedTypeObject, SQLError> {
97 let parts = crate::parse_regobject_name(name)
98 .ok_or_else(|| SQLError::Internal("type object target has no valid name".into()))?;
99 let lookup = parts
100 .iter()
101 .map(|part| format!("\"{}\"", part.replace('"', "\"\"")))
102 .collect::<Vec<_>>()
103 .join(".");
104 resolve_type_object(context, &lookup)
105}
106
107pub fn resolve_type_object(
109 context: &TypeObjectBinding<'_>,
110 name: &str,
111) -> Result<ResolvedTypeObject, SQLError> {
112 let parsed = crate::parse_regtype_name(name)?
113 .ok_or_else(|| SQLError::Internal("ALTER TYPE has no type name".into()))?;
114 let label = format!(
115 "{}{}",
116 parsed.names.join("."),
117 "[]".repeat(parsed.array_dimensions)
118 );
119 if let [schema, _] = parsed.names.as_slice() {
120 if context.catalog.schema_security(schema).is_none() {
121 return Err(SQLError::Routine {
122 sqlstate: "3F000".into(),
123 message: format!("schema \"{schema}\" does not exist"),
124 });
125 }
126 if !context
127 .authority
128 .schema_usage(schema, &context.session.current_role())
129 {
130 return Err(SQLError::Routine {
131 sqlstate: "42501".into(),
132 message: format!("permission denied for schema {schema}"),
133 });
134 }
135 }
136 let oid = context
137 .catalog
138 .resolve_drop_type_oid(name)?
139 .ok_or_else(|| SQLError::Routine {
140 sqlstate: "42704".into(),
141 message: format!("type \"{label}\" does not exist"),
142 })?;
143 let oid = u32::try_from(oid)
144 .map_err(|_| SQLError::Internal(format!("type OID {oid} is out of range")))?;
145 Ok(ResolvedTypeObject {
146 oid,
147 kind: classify(context, oid),
148 })
149}
150
151fn classify(context: &TypeObjectBinding<'_>, oid: u32) -> ResolvedKind {
152 if let Some(domain) = context.catalog.domain_by_oid(oid) {
153 return ResolvedKind::User(TypeObject::Domain(Box::new(domain)));
154 }
155 if let Some(definition) = context.catalog.enum_by_type_oid(oid) {
156 if definition.array_oid == oid {
157 return ResolvedKind::Array {
158 element: definition.oid,
159 };
160 }
161 return ResolvedKind::User(TypeObject::Enum(definition));
162 }
163 if let Some(definition) = context.catalog.composite_by_type_oid(oid) {
164 if definition.array_oid == oid {
165 return ResolvedKind::Array {
166 element: definition.oid,
167 };
168 }
169 return ResolvedKind::User(TypeObject::Composite(Box::new(definition)));
170 }
171 if let Some(element) = context.catalog.user_array_element(oid) {
172 return ResolvedKind::Array { element };
173 }
174 if let Some(relation) = context.catalog.row_type_relation(oid) {
175 return ResolvedKind::RowType(relation);
176 }
177 ResolvedKind::System
178}
179
180impl ResolvedTypeObject {
181 #[must_use]
182 pub fn oid(&self) -> u32 {
183 self.oid
184 }
185
186 fn owner(&self, context: &TypeObjectBinding<'_>) -> RoleIdentity {
187 match &self.kind {
188 ResolvedKind::User(object) => object.owner(),
189 ResolvedKind::Array { element } => context
190 .catalog
191 .domain_by_oid(*element)
192 .map(|domain| domain.owner)
193 .or_else(|| {
194 context
195 .catalog
196 .enum_by_type_oid(*element)
197 .map(|definition| definition.owner)
198 })
199 .or_else(|| {
200 context
201 .catalog
202 .composite_by_type_oid(*element)
203 .map(|definition| definition.owner)
204 })
205 .unwrap_or(RoleIdentity::BOOTSTRAP),
206 ResolvedKind::RowType(relation) => relation.owner,
207 ResolvedKind::System => RoleIdentity::BOOTSTRAP,
208 }
209 }
210
211 pub fn require_domain_keyword(
213 &self,
214 context: &TypeObjectBinding<'_>,
215 kind: TypeObjectKind,
216 ) -> Result<(), SQLError> {
217 if kind == TypeObjectKind::Domain
218 && !matches!(self.kind, ResolvedKind::User(TypeObject::Domain(_)))
219 {
220 return Err(SQLError::Routine {
221 sqlstate: "42809".into(),
222 message: format!("{} is not a domain", format_type(context, self.oid)?),
223 });
224 }
225 Ok(())
226 }
227
228 pub fn require_owner(&self, context: &TypeObjectBinding<'_>) -> Result<(), SQLError> {
230 if context
231 .authority
232 .current_user_has_role_privileges(&self.owner(context))
233 {
234 return Ok(());
235 }
236 Err(SQLError::Routine {
237 sqlstate: "42501".into(),
238 message: format!(
239 "must be owner of type {}",
240 format_type(context, self.array_element().unwrap_or(self.oid))?
241 ),
242 })
243 }
244
245 #[must_use]
247 pub fn row_type_identity(&self) -> Option<RelationIdentity> {
248 match &self.kind {
249 ResolvedKind::RowType(relation) => Some(RelationIdentity::new(
250 &relation.schema,
251 &relation.local_name,
252 )),
253 _ => None,
254 }
255 }
256
257 fn array_element(&self) -> Option<u32> {
258 match self.kind {
259 ResolvedKind::Array { element } => Some(element),
260 _ => None,
261 }
262 }
263
264 pub fn reject_row_type(&self, context: &TypeObjectBinding<'_>) -> Result<(), SQLError> {
266 if let ResolvedKind::RowType(_) = self.kind {
267 return Err(SQLError::Diagnostic {
268 sqlstate: "42809".into(),
269 message: format!("{} is a table's row type", format_type(context, self.oid)?),
270 detail: None,
271 hint: Some("Use ALTER TABLE instead.".into()),
272 });
273 }
274 Ok(())
275 }
276
277 pub fn reject_array(&self, context: &TypeObjectBinding<'_>) -> Result<(), SQLError> {
279 if let Some(element) = self.array_element() {
280 return Err(SQLError::Diagnostic {
281 sqlstate: "42809".into(),
282 message: format!(
283 "cannot alter array type {}",
284 format_type(context, self.oid)?
285 ),
286 detail: None,
287 hint: Some(format!(
288 "You can alter type {}, which will alter the array type as well.",
289 format_type(context, element)?
290 )),
291 });
292 }
293 Ok(())
294 }
295
296 pub fn require_grant_target(
298 &self,
299 context: &TypeObjectBinding<'_>,
300 kind: TypeObjectKind,
301 ) -> Result<(), SQLError> {
302 if self.array_element().is_some() {
303 return Err(SQLError::Diagnostic {
304 sqlstate: "0LP01".into(),
305 message: "cannot set privileges of array types".into(),
306 detail: None,
307 hint: Some("Set the privileges of the element type instead.".into()),
308 });
309 }
310 if kind == TypeObjectKind::Domain
311 && !matches!(self.kind, ResolvedKind::User(TypeObject::Domain(_)))
312 {
313 return Err(SQLError::Routine {
314 sqlstate: "42809".into(),
315 message: format!("\"{}\" is not a domain", format_type(context, self.oid)?),
316 });
317 }
318 Ok(())
319 }
320
321 pub fn into_type_object(self, context: &TypeObjectBinding<'_>) -> Result<TypeObject, SQLError> {
323 match self.kind {
324 ResolvedKind::User(object) => Ok(object),
325 ResolvedKind::Array { .. } | ResolvedKind::RowType(_) | ResolvedKind::System => {
326 Err(SQLError::Routine {
327 sqlstate: "0A000".into(),
328 message: format!(
329 "type {} has a fixed system definition",
330 format_type(context, self.oid)?
331 ),
332 })
333 }
334 }
335 }
336}
337
338fn format_type(context: &TypeObjectBinding<'_>, oid: u32) -> Result<String, SQLError> {
339 context
340 .catalog
341 .format_drop_type(i64::from(oid))
342 .map_err(SQLError::Internal)?
343 .ok_or_else(|| SQLError::Internal(format!("type {oid} disappeared")))
344}
345
346#[cfg(test)]
347mod tests;