uqa_sql/catalog/
domain.rs1use crate::ast::{ColumnType, CreateDomain};
8use serde::{Deserialize, Serialize};
9use std::collections::{BTreeMap, BTreeSet};
10use uqa_core::{catalog_role::RoleIdentity, RelationIdentity};
11
12use super::roles::{identity::RoleSubject, RoleDefinition, RoleReference};
13
14pub fn domain_object_oid(object_id: &[u8; 16]) -> u32 {
15 u32::try_from(super::oids::stable_object_oid("domain", object_id))
16 .expect("catalog OIDs fit in u32")
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct StoredDomain<Owner = RoleIdentity> {
21 pub object_id: [u8; 16],
22 pub oid: u32,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub array_oid: Option<u32>,
26 pub identity: RelationIdentity,
27 pub owner: Owner,
28 pub definition: CreateDomain,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub array_name: Option<String>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub usage_acl: Option<Vec<crate::ast::ObjectAclEntry>>,
35}
36
37impl<Owner> StoredDomain<Owner> {
38 pub fn column_type(&self) -> ColumnType {
39 ColumnType::Domain {
40 schema: self.identity.schema.clone(),
41 name: self.identity.name.clone(),
42 oid: self.oid,
43 array_oid: self.array_oid,
44 collation_oid: self.definition.collation_oid,
45 base: Box::new(self.definition.base.clone()),
46 }
47 }
48
49 pub fn array_type_name(&self) -> String {
51 self.array_name
52 .clone()
53 .unwrap_or_else(|| super::array_type_names::array_type_name(&self.identity.name, 0))
54 }
55}
56
57impl StoredDomain<String> {
58 pub fn bind_owner(
60 self,
61 roles: &BTreeMap<String, RoleDefinition>,
62 ) -> Result<StoredDomain, String> {
63 let owner = RoleReference::Named(self.owner)
64 .bind(roles)
65 .map_err(|error| error.to_string())?
66 .identity();
67 Ok(StoredDomain {
68 object_id: self.object_id,
69 oid: self.oid,
70 array_oid: self.array_oid,
71 identity: self.identity,
72 owner,
73 definition: self.definition,
74 array_name: self.array_name,
75 usage_acl: self.usage_acl,
76 })
77 }
78}
79
80pub fn validate_domain_registry(
82 registry: &BTreeMap<String, StoredDomain>,
83 roles: &BTreeMap<String, RoleDefinition>,
84) -> Result<(), String> {
85 validate_domain_definitions(registry, roles)?;
86 for domain in registry.values() {
87 crate::schema::domains::constraints::validate(&domain.definition, false)
88 .map_err(|error| error.to_string())?;
89 }
90 Ok(())
91}
92
93pub fn validate_domain_definitions(
95 registry: &BTreeMap<String, StoredDomain>,
96 roles: &BTreeMap<String, RoleDefinition>,
97) -> Result<(), String> {
98 let mut identities = BTreeSet::new();
99 let mut oids = BTreeSet::new();
100 let mut constraint_objects = BTreeSet::new();
101 let mut constraint_oids = BTreeSet::new();
102 for (name, domain) in registry {
103 let oids_valid = match domain.array_oid {
105 Some(array_oid) => {
106 domain.oid >= super::oids::FIRST_NORMAL_OBJECT_ID
107 && array_oid >= super::oids::FIRST_NORMAL_OBJECT_ID
108 && oids.insert(array_oid)
109 }
110 None => domain.oid == domain_object_oid(&domain.object_id),
111 };
112 if domain.object_id == [0; 16]
113 || !identities.insert(domain.object_id)
114 || !oids_valid
115 || !oids.insert(domain.oid)
116 {
117 return Err(format!("invalid or duplicate domain identity for `{name}`"));
118 }
119 if domain.identity.schema.is_empty()
120 || domain.identity.name.is_empty()
121 || domain.identity.qualified_name() != *name
122 || RelationIdentity::from_legacy_name(&domain.definition.name).as_ref()
123 != Ok(&domain.identity)
124 {
125 return Err(format!("inconsistent domain name for `{name}`"));
126 }
127 if !domain.owner.is_valid() || domain.owner.role_definition(roles).is_none() {
128 return Err(format!(
129 "domain `{name}` references missing role incarnation {}",
130 domain.owner.oid
131 ));
132 }
133 crate::schema::domains::constraints::validate(&domain.definition, true)
134 .map_err(|error| error.to_string())?;
135 for identity in crate::schema::domains::constraints::identities(&domain.definition) {
136 if !constraint_objects.insert(identity.object_id)
137 || !constraint_oids.insert(identity.oid)
138 {
139 return Err(format!(
140 "duplicate domain constraint catalog identity for `{name}`"
141 ));
142 }
143 }
144 }
145 Ok(())
146}
147
148pub trait DomainCatalog {
150 fn domain_by_oid(&self, oid: u32) -> Option<StoredDomain>;
151}
152
153pub fn domain_default_expression(
154 catalog: &dyn DomainCatalog,
155 ty: &crate::ColumnType,
156) -> Option<crate::ast::Expr> {
157 let crate::ColumnType::Domain { oid, base, .. } = ty else {
158 return None;
159 };
160 catalog
161 .domain_by_oid(*oid)
162 .and_then(|domain| domain.definition.default)
163 .or_else(|| domain_default_expression(catalog, base))
164}
165
166#[cfg(test)]
167mod tests;