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                name: column
18                    .check_name
19                    .clone()
20                    .or_else(|| Some(format!("{}_check", column.name))),
21                expr,
22                enforced: column.check_enforced,
23                validated: column.check_validated,
24                no_inherit: column.check_no_inherit,
25                object_id: column.check_object_id,
26                is_local: column.check_is_local,
27                partition_constraint: None,
28            });
29        }
30    }
31    out
32}
33pub fn append_column_foreign_keys(columns: &[ColumnDef], foreign_keys: &mut Vec<ForeignKey>) {
34    for column in columns {
35        if let Some(reference) = &column.references {
36            let mut foreign_key = super::foreign_keys::column_foreign_key(column, reference);
37            foreign_key.name = foreign_key
38                .name
39                .or_else(|| Some(format!("{}_fkey", column.name)));
40            foreign_keys.push(foreign_key);
41        }
42    }
43}
44pub fn append_column_keys(columns: &[ColumnDef], constraints: &mut Vec<TableKeyConstraint>) {
45    for column in columns {
46        let kind = if column.primary_key {
47            Some(TableKeyConstraintKind::PrimaryKey)
48        } else if column.unique {
49            Some(TableKeyConstraintKind::Unique)
50        } else {
51            None
52        };
53        let Some(kind) = kind else {
54            continue;
55        };
56        if constraints.iter().any(|constraint| {
57            constraint.kind == kind
58                && constraint.columns.as_slice() == std::slice::from_ref(&column.name)
59        }) {
60            continue;
61        }
62        constraints.push(TableKeyConstraint {
63            name: None,
64            kind,
65            columns: vec![column.name.clone()],
66            nulls_not_distinct: false,
67            without_overlaps: false,
68        });
69    }
70}
71pub fn auto_increment_columns(columns: &[ColumnDef]) -> BTreeSet<String> {
72    columns
73        .iter()
74        .filter(|column| column.auto_increment.is_some())
75        .map(|column| column.name.clone())
76        .collect()
77}
78pub fn unique_scalar_columns(
79    constraints: Vec<TableKeyConstraint>,
80    auto_increment: &BTreeSet<String>,
81) -> Vec<String> {
82    constraints
83        .into_iter()
84        .filter(|constraint| constraint.columns.len() == 1)
85        .map(|constraint| constraint.columns[0].clone())
86        .filter(|column| !auto_increment.contains(column))
87        .collect()
88}
89/// Loaded table identities only; persisted references do not consult the session search path.
90pub trait StoredTableNames {
91    fn stored_table_exists(&self, relation: &RelationIdentity) -> bool;
92    fn stored_table_names(&self) -> Vec<RelationIdentity>;
93}
94pub fn canonical_stored_foreign_key_target(
95    catalog: &dyn StoredTableNames,
96    reference: &str,
97) -> Result<String, String> {
98    let (schema, local_name) = RelationIdentity::parse_reference(reference)
99        .map_err(|error| format!("invalid persisted foreign-key target `{reference}`: {error}"))?;
100    if let Some(schema) = schema {
101        let target = RelationIdentity::new(schema, local_name);
102        if catalog.stored_table_exists(&target) {
103            return Ok(target.qualified_name());
104        }
105        return Err(format!(
106            "dangling persisted foreign-key target `{reference}`"
107        ));
108    }
109    let candidates = catalog
110        .stored_table_names()
111        .into_iter()
112        .filter(|candidate| candidate.name == local_name)
113        .map(|candidate| candidate.qualified_name())
114        .collect::<Vec<_>>();
115    match candidates.as_slice() {
116        [target] => Ok(target.clone()),
117        [] => Err(format!(
118            "dangling persisted foreign-key target `{reference}`"
119        )),
120        _ => Err(format!(
121            "ambiguous persisted foreign-key target `{reference}` matches {}",
122            candidates.join(", ")
123        )),
124    }
125}
126pub fn bind_stored_foreign_key_targets(
127    catalog: &dyn StoredTableNames,
128    foreign_keys: &mut [ForeignKey],
129) -> Result<(), String> {
130    for foreign_key in foreign_keys {
131        foreign_key.ref_table =
132            canonical_stored_foreign_key_target(catalog, &foreign_key.ref_table)?;
133    }
134    Ok(())
135}