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 base: Box::new(self.definition.base.clone()),
45 }
46 }
47
48 pub fn array_type_name(&self) -> String {
50 self.array_name
51 .clone()
52 .unwrap_or_else(|| super::array_type_names::array_type_name(&self.identity.name, 0))
53 }
54}
55
56impl StoredDomain<String> {
57 pub fn bind_owner(
59 self,
60 roles: &BTreeMap<String, RoleDefinition>,
61 ) -> Result<StoredDomain, String> {
62 let owner = RoleReference::Named(self.owner)
63 .bind(roles)
64 .map_err(|error| error.to_string())?
65 .identity();
66 Ok(StoredDomain {
67 object_id: self.object_id,
68 oid: self.oid,
69 array_oid: self.array_oid,
70 identity: self.identity,
71 owner,
72 definition: self.definition,
73 array_name: self.array_name,
74 usage_acl: self.usage_acl,
75 })
76 }
77}
78
79pub fn validate_domain_registry(
81 registry: &BTreeMap<String, StoredDomain>,
82 roles: &BTreeMap<String, RoleDefinition>,
83) -> Result<(), String> {
84 validate_domain_definitions(registry, roles)?;
85 for domain in registry.values() {
86 crate::schema::domains::constraints::validate(&domain.definition, false)
87 .map_err(|error| error.to_string())?;
88 }
89 Ok(())
90}
91
92pub fn validate_domain_definitions(
94 registry: &BTreeMap<String, StoredDomain>,
95 roles: &BTreeMap<String, RoleDefinition>,
96) -> Result<(), String> {
97 let mut identities = BTreeSet::new();
98 let mut oids = BTreeSet::new();
99 let mut constraint_objects = BTreeSet::new();
100 let mut constraint_oids = BTreeSet::new();
101 for (name, domain) in registry {
102 let oids_valid = match domain.array_oid {
104 Some(array_oid) => {
105 domain.oid >= super::oids::FIRST_NORMAL_OBJECT_ID
106 && array_oid >= super::oids::FIRST_NORMAL_OBJECT_ID
107 && oids.insert(array_oid)
108 }
109 None => domain.oid == domain_object_oid(&domain.object_id),
110 };
111 if domain.object_id == [0; 16]
112 || !identities.insert(domain.object_id)
113 || !oids_valid
114 || !oids.insert(domain.oid)
115 {
116 return Err(format!("invalid or duplicate domain identity for `{name}`"));
117 }
118 if domain.identity.schema.is_empty()
119 || domain.identity.name.is_empty()
120 || domain.identity.qualified_name() != *name
121 || RelationIdentity::from_legacy_name(&domain.definition.name).as_ref()
122 != Ok(&domain.identity)
123 {
124 return Err(format!("inconsistent domain name for `{name}`"));
125 }
126 if !domain.owner.is_valid() || domain.owner.role_definition(roles).is_none() {
127 return Err(format!(
128 "domain `{name}` references missing role incarnation {}",
129 domain.owner.oid
130 ));
131 }
132 crate::schema::domains::constraints::validate(&domain.definition, true)
133 .map_err(|error| error.to_string())?;
134 for identity in crate::schema::domains::constraints::identities(&domain.definition) {
135 if !constraint_objects.insert(identity.object_id)
136 || !constraint_oids.insert(identity.oid)
137 {
138 return Err(format!(
139 "duplicate domain constraint catalog identity for `{name}`"
140 ));
141 }
142 }
143 }
144 Ok(())
145}
146
147pub trait DomainCatalog {
149 fn domain_by_oid(&self, oid: u32) -> Option<StoredDomain>;
150}
151
152pub fn domain_default_expression(
153 catalog: &dyn DomainCatalog,
154 ty: &crate::ColumnType,
155) -> Option<crate::ast::Expr> {
156 let crate::ColumnType::Domain { oid, base, .. } = ty else {
157 return None;
158 };
159 catalog
160 .domain_by_oid(*oid)
161 .and_then(|domain| domain.definition.default)
162 .or_else(|| domain_default_expression(catalog, base))
163}
164
165#[cfg(test)]
166mod tests;