Skip to main content

uqa_sql/schema/domains/
dependencies.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Declared column types affected by a domain constraint change.
8
9use crate::catalog::domain::DomainCatalog;
10use crate::expr::composites::{descriptor, CompositeTypeCatalog};
11use crate::{ColumnType, SQLError};
12use std::collections::BTreeSet;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum DomainColumnDependency {
16    None,
17    /// The stored value has the domain's type or a domain derived from it.
18    Direct,
19    /// An array or composite lies between the stored column and the domain. `PostgreSQL` rejects scanning these containers when it validates a changed domain constraint.
20    Container,
21}
22
23/// Classify a resolved column type using the statement's live domain and composite definitions. Embedded domain bases can predate catalog changes and are not a substitute for those definitions.
24pub fn column_domain_dependency(
25    ty: &ColumnType,
26    target: u32,
27    domains: &dyn DomainCatalog,
28    composites: &dyn CompositeTypeCatalog,
29) -> Result<DomainColumnDependency, SQLError> {
30    Dependencies {
31        target,
32        domains,
33        composites,
34        visited: BTreeSet::new(),
35    }
36    .classify(ty)
37}
38
39struct Dependencies<'a> {
40    target: u32,
41    domains: &'a dyn DomainCatalog,
42    composites: &'a dyn CompositeTypeCatalog,
43    visited: BTreeSet<u32>,
44}
45
46impl Dependencies<'_> {
47    fn classify(&mut self, ty: &ColumnType) -> Result<DomainColumnDependency, SQLError> {
48        match ty {
49            ColumnType::Domain { oid, .. } => {
50                if *oid == self.target {
51                    return Ok(DomainColumnDependency::Direct);
52                }
53                if !self.visited.insert(*oid) {
54                    return Ok(DomainColumnDependency::None);
55                }
56                let domain = self.domains.domain_by_oid(*oid).ok_or_else(|| {
57                    SQLError::Internal(format!(
58                        "domain type OID {oid} is not available in the statement catalog"
59                    ))
60                })?;
61                self.classify(&domain.definition.base)
62            }
63            ColumnType::Array(element) => self.container(element),
64            ColumnType::Composite(reference) => {
65                if !self.visited.insert(reference.oid) {
66                    return Ok(DomainColumnDependency::None);
67                }
68                let descriptor = descriptor(Some(self.composites), reference.oid)?;
69                for attribute in &descriptor.attributes {
70                    if self.container(&attribute.ty)? == DomainColumnDependency::Container {
71                        return Ok(DomainColumnDependency::Container);
72                    }
73                }
74                Ok(DomainColumnDependency::None)
75            }
76            _ => Ok(DomainColumnDependency::None),
77        }
78    }
79
80    fn container(&mut self, ty: &ColumnType) -> Result<DomainColumnDependency, SQLError> {
81        self.classify(ty).map(|dependency| match dependency {
82            DomainColumnDependency::None => DomainColumnDependency::None,
83            DomainColumnDependency::Direct | DomainColumnDependency::Container => {
84                DomainColumnDependency::Container
85            }
86        })
87    }
88}
89
90#[cfg(test)]
91mod tests;