Skip to main content

uqa_sql/schema/domains/
constraints.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Durable names and individual catalog identities for domain constraints.
8
9use crate::ast::{ConstraintCatalogIdentity, CreateDomain};
10use crate::schema::constraint_metadata::{
11    assign_constraint_name, CatalogIdentityAllocator, CatalogOidClass, ConstraintMetadataError,
12    ConstraintMetadataResult,
13};
14use crate::SQLError;
15use std::collections::BTreeSet;
16use uqa_core::RelationIdentity;
17
18pub fn assign_names(
19    definition: &mut CreateDomain,
20    schema_names: &BTreeSet<String>,
21) -> Result<(), SQLError> {
22    let identity =
23        RelationIdentity::from_legacy_name(&definition.name).map_err(SQLError::Internal)?;
24    let mut names = BTreeSet::new();
25    let mut automatic = schema_names.clone();
26    if let Some(not_null) = &mut definition.not_null {
27        assign_constraint_name(
28            &mut not_null.name,
29            (&identity.name, "", "not_null"),
30            &mut automatic,
31        )
32        .map_err(|error| crate::catalog::errors::storage_error("domain constraint name", &error))?;
33        let name = not_null.name.as_ref().expect("assigned NOT NULL name");
34        names.insert(name.clone());
35        automatic.insert(name.clone());
36    }
37    for check in &mut definition.checks {
38        if let Some(name) = &check.name {
39            if !names.insert(name.clone()) {
40                return Err(crate::assignment::domain::domain_error(
41                    "42710",
42                    format!(
43                        "constraint \"{name}\" for domain \"{}\" already exists",
44                        identity.name
45                    ),
46                ));
47            }
48        } else {
49            assign_constraint_name(
50                &mut check.name,
51                (&identity.name, "", "check"),
52                &mut automatic,
53            )
54            .map_err(|error| {
55                crate::catalog::errors::storage_error("domain constraint name", &error)
56            })?;
57            names.insert(check.name.clone().expect("assigned CHECK name"));
58        }
59        automatic.extend(check.name.iter().cloned());
60    }
61    Ok(())
62}
63
64pub fn identities(
65    definition: &CreateDomain,
66) -> impl Iterator<Item = ConstraintCatalogIdentity> + '_ {
67    definition
68        .not_null
69        .iter()
70        .filter_map(|constraint| constraint.catalog_identity)
71        .chain(
72            definition
73                .checks
74                .iter()
75                .filter_map(|constraint| constraint.catalog_identity),
76        )
77}
78
79/// Unmaterialized declarations and initial legacy conversion may omit metadata. Supplied corrupt metadata is never replaced.
80pub fn validate(definition: &CreateDomain, allow_missing: bool) -> ConstraintMetadataResult<()> {
81    let mut names = BTreeSet::new();
82    let mut objects = BTreeSet::new();
83    let mut oids = BTreeSet::new();
84    let rows = definition
85        .not_null
86        .iter()
87        .map(|constraint| (&constraint.name, constraint.catalog_identity))
88        .chain(
89            definition
90                .checks
91                .iter()
92                .map(|constraint| (&constraint.name, constraint.catalog_identity)),
93        );
94    for (name, identity) in rows {
95        if let Some(name) = name {
96            if name.is_empty() || !names.insert(name) {
97                return Err(invalid("invalid or duplicate domain constraint name"));
98            }
99        } else if !allow_missing {
100            return Err(invalid("domain constraint has no durable name"));
101        }
102        if let Some(identity) = identity {
103            if !identity.is_valid()
104                || !objects.insert(identity.object_id)
105                || !oids.insert(identity.oid)
106            {
107                return Err(invalid(
108                    "invalid or duplicate domain constraint catalog identity",
109                ));
110            }
111        } else if !allow_missing {
112            return Err(invalid("domain constraint has no catalog identity"));
113        }
114    }
115    Ok(())
116}
117
118pub fn materialize(
119    definition: &mut CreateDomain,
120    allocate: &mut CatalogIdentityAllocator<'_>,
121) -> ConstraintMetadataResult<bool> {
122    validate(definition, true)?;
123    let relation = RelationIdentity::from_legacy_name(&definition.name).map_err(invalid)?;
124    for identity in identities(definition) {
125        allocate.include_catalog_identity(&relation, CatalogOidClass::Constraint, identity)?;
126    }
127    let mut changed = false;
128    let rows = definition
129        .not_null
130        .iter_mut()
131        .map(|constraint| &mut constraint.catalog_identity)
132        .chain(
133            definition
134                .checks
135                .iter_mut()
136                .map(|constraint| &mut constraint.catalog_identity),
137        );
138    for target in rows {
139        if target.is_some() {
140            continue;
141        }
142        let object_id = allocate.allocate_object_id("domain constraint")?;
143        *target = Some(ConstraintCatalogIdentity {
144            object_id,
145            oid: allocate.allocate_catalog_oid(CatalogOidClass::Constraint, &object_id)?,
146        });
147        changed = true;
148    }
149    validate(definition, false)?;
150    Ok(changed)
151}
152
153fn invalid(message: impl Into<String>) -> ConstraintMetadataError {
154    ConstraintMetadataError::Invalid(message.into())
155}
156
157#[cfg(test)]
158mod tests;