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 pub identity: RelationIdentity,
24 pub owner: Owner,
25 pub definition: CreateDomain,
26}
27
28impl<Owner> StoredDomain<Owner> {
29 pub fn column_type(&self) -> ColumnType {
30 ColumnType::Domain {
31 schema: self.identity.schema.clone(),
32 name: self.identity.name.clone(),
33 oid: self.oid,
34 base: Box::new(self.definition.base.clone()),
35 }
36 }
37}
38
39impl StoredDomain<String> {
40 pub fn bind_owner(
42 self,
43 roles: &BTreeMap<String, RoleDefinition>,
44 ) -> Result<StoredDomain, String> {
45 let owner = RoleReference::Named(self.owner)
46 .bind(roles)
47 .map_err(|error| error.to_string())?
48 .identity();
49 Ok(StoredDomain {
50 object_id: self.object_id,
51 oid: self.oid,
52 identity: self.identity,
53 owner,
54 definition: self.definition,
55 })
56 }
57}
58
59pub fn validate_domain_registry(
61 registry: &BTreeMap<String, StoredDomain>,
62 roles: &BTreeMap<String, RoleDefinition>,
63) -> Result<(), String> {
64 validate_domain_definitions(registry, roles)?;
65 for domain in registry.values() {
66 crate::schema::domains::constraints::validate(&domain.definition, false)
67 .map_err(|error| error.to_string())?;
68 }
69 Ok(())
70}
71
72pub fn validate_domain_definitions(
74 registry: &BTreeMap<String, StoredDomain>,
75 roles: &BTreeMap<String, RoleDefinition>,
76) -> Result<(), String> {
77 let mut identities = BTreeSet::new();
78 let mut oids = BTreeSet::new();
79 let mut constraint_objects = BTreeSet::new();
80 let mut constraint_oids = BTreeSet::new();
81 for (name, domain) in registry {
82 if domain.object_id == [0; 16]
83 || !identities.insert(domain.object_id)
84 || domain.oid != domain_object_oid(&domain.object_id)
85 || !oids.insert(domain.oid)
86 {
87 return Err(format!("invalid or duplicate domain identity for `{name}`"));
88 }
89 if domain.identity.schema.is_empty()
90 || domain.identity.name.is_empty()
91 || domain.identity.qualified_name() != *name
92 || RelationIdentity::from_legacy_name(&domain.definition.name).as_ref()
93 != Ok(&domain.identity)
94 {
95 return Err(format!("inconsistent domain name for `{name}`"));
96 }
97 if !domain.owner.is_valid() || domain.owner.role_definition(roles).is_none() {
98 return Err(format!(
99 "domain `{name}` references missing role incarnation {}",
100 domain.owner.oid
101 ));
102 }
103 crate::schema::domains::constraints::validate(&domain.definition, true)
104 .map_err(|error| error.to_string())?;
105 for identity in crate::schema::domains::constraints::identities(&domain.definition) {
106 if !constraint_objects.insert(identity.object_id)
107 || !constraint_oids.insert(identity.oid)
108 {
109 return Err(format!(
110 "duplicate domain constraint catalog identity for `{name}`"
111 ));
112 }
113 }
114 }
115 Ok(())
116}
117
118pub trait DomainCatalog {
120 fn domain_by_oid(&self, oid: u32) -> Option<StoredDomain>;
121}
122
123pub fn domain_default_expression(
124 catalog: &dyn DomainCatalog,
125 ty: &crate::ColumnType,
126) -> Option<crate::ast::Expr> {
127 let crate::ColumnType::Domain { oid, base, .. } = ty else {
128 return None;
129 };
130 catalog
131 .domain_by_oid(*oid)
132 .and_then(|domain| domain.definition.default)
133 .or_else(|| domain_default_expression(catalog, base))
134}
135
136#[cfg(test)]
137mod tests;