Skip to main content

uqa_sql/schema/
constraint_views.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Effective constraint views over stored column and table declarations.
8use crate::ast::{ColumnDef, ForeignKey, TableCheck, TableKeyConstraint, TableKeyConstraintKind};
9use std::collections::BTreeSet;
10use uqa_core::RelationIdentity;
11
12pub fn column_checks(columns: &[ColumnDef]) -> Vec<TableCheck> {
13    let mut out = Vec::new();
14    for column in columns {
15        if let Some(expr) = column.check.clone() {
16            out.push(TableCheck {
17                catalog_oid: column.check_catalog_oid,
18                name: column
19                    .check_name
20                    .clone()
21                    .or_else(|| Some(format!("{}_check", column.name))),
22                expr,
23                enforced: column.check_enforced,
24                validated: column.check_validated,
25                no_inherit: column.check_no_inherit,
26                object_id: column.check_object_id,
27                is_local: column.check_is_local,
28                partition_constraint: None,
29            });
30        }
31    }
32    out
33}
34pub fn append_column_foreign_keys(columns: &[ColumnDef], foreign_keys: &mut Vec<ForeignKey>) {
35    for column in columns {
36        if let Some(reference) = &column.references {
37            let mut foreign_key = super::foreign_keys::column_foreign_key(column, reference);
38            foreign_key.name = foreign_key
39                .name
40                .or_else(|| Some(format!("{}_fkey", column.name)));
41            foreign_keys.push(foreign_key);
42        }
43    }
44}
45pub fn append_column_keys(columns: &[ColumnDef], constraints: &mut Vec<TableKeyConstraint>) {
46    for column in columns {
47        let kind = if column.primary_key {
48            Some(TableKeyConstraintKind::PrimaryKey)
49        } else if column.unique {
50            Some(TableKeyConstraintKind::Unique)
51        } else {
52            None
53        };
54        let Some(kind) = kind else {
55            continue;
56        };
57        if constraints.iter().any(|constraint| {
58            constraint.kind == kind
59                && constraint.columns.as_slice() == std::slice::from_ref(&column.name)
60        }) {
61            continue;
62        }
63        constraints.push(TableKeyConstraint {
64            catalog_identity: None,
65            index_identity: None,
66            name: None,
67            kind,
68            columns: vec![column.name.clone()],
69            included_columns: Vec::new(),
70            nulls_not_distinct: false,
71            without_overlaps: false,
72        });
73    }
74}
75pub fn auto_increment_columns(columns: &[ColumnDef]) -> BTreeSet<String> {
76    columns
77        .iter()
78        .filter(|column| column.auto_increment.is_some())
79        .map(|column| column.name.clone())
80        .collect()
81}
82pub fn unique_scalar_columns(
83    constraints: Vec<TableKeyConstraint>,
84    auto_increment: &BTreeSet<String>,
85) -> Vec<String> {
86    constraints
87        .into_iter()
88        .filter(|constraint| constraint.columns.len() == 1)
89        .map(|constraint| constraint.columns[0].clone())
90        .filter(|column| !auto_increment.contains(column))
91        .collect()
92}
93/// Loaded table identities only; persisted references do not consult the session search path.
94pub trait StoredTableNames {
95    fn stored_table_exists(&self, relation: &RelationIdentity) -> bool;
96    fn stored_table_names(&self) -> Vec<RelationIdentity>;
97}
98pub fn canonical_stored_foreign_key_target(
99    catalog: &dyn StoredTableNames,
100    reference: &str,
101) -> Result<String, String> {
102    let (schema, local_name) = RelationIdentity::parse_reference(reference)
103        .map_err(|error| format!("invalid persisted foreign-key target `{reference}`: {error}"))?;
104    if let Some(schema) = schema {
105        let target = RelationIdentity::new(schema, local_name);
106        if catalog.stored_table_exists(&target) {
107            return Ok(target.qualified_name());
108        }
109        return Err(format!(
110            "dangling persisted foreign-key target `{reference}`"
111        ));
112    }
113    let candidates = catalog
114        .stored_table_names()
115        .into_iter()
116        .filter(|candidate| candidate.name == local_name)
117        .map(|candidate| candidate.qualified_name())
118        .collect::<Vec<_>>();
119    match candidates.as_slice() {
120        [target] => Ok(target.clone()),
121        [] => Err(format!(
122            "dangling persisted foreign-key target `{reference}`"
123        )),
124        _ => Err(format!(
125            "ambiguous persisted foreign-key target `{reference}` matches {}",
126            candidates.join(", ")
127        )),
128    }
129}
130pub fn bind_stored_foreign_key_targets(
131    catalog: &dyn StoredTableNames,
132    foreign_keys: &mut [ForeignKey],
133) -> Result<(), String> {
134    for foreign_key in foreign_keys {
135        foreign_key.ref_table =
136            canonical_stored_foreign_key_target(catalog, &foreign_key.ref_table)?;
137    }
138    Ok(())
139}
140
141/// Canonicalize legacy declarations before attaching durable index identities. Resolve against all stored relations, never a caller's search path.
142pub fn bind_stored_foreign_key_declarations(
143    catalog: &dyn StoredTableNames,
144    columns: &mut [ColumnDef],
145    constraints: &mut crate::ast::TableConstraintSet,
146) -> Result<bool, String> {
147    let mut changed = false;
148    let mut bind = |target: &mut String| -> Result<(), String> {
149        let canonical = canonical_stored_foreign_key_target(catalog, target)?;
150        changed |= *target != canonical;
151        *target = canonical;
152        Ok(())
153    };
154    for column in columns {
155        if let Some(reference) = &mut column.references {
156            bind(&mut reference.table)?;
157        }
158    }
159    for key in constraints
160        .foreign_keys
161        .iter_mut()
162        .chain(&mut constraints.hierarchy.partition_inherited_foreign_keys)
163    {
164        bind(&mut key.ref_table)?;
165    }
166    Ok(changed)
167}
168
169#[cfg(test)]
170mod tests;