Skip to main content

uqa_sql/schema/constraint_changes/
validation.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Select constraint validation dependencies and match inherited NOT NULL constraints by column.
8
9use super::{constraint_error, find_constraint, ConstraintLocation};
10use crate::{
11    ast::{ColumnDef, TableConstraintSet},
12    SQLError,
13};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum ConstraintValidationKind<'a> {
17    Check { no_inherit: bool },
18    NotNull { column: &'a str, no_inherit: bool },
19    ForeignKey { referenced_table: &'a str },
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct ConstraintValidation<'a> {
24    pub kind: ConstraintValidationKind<'a>,
25    pub validated: bool,
26}
27
28impl ConstraintValidation<'_> {
29    pub fn requires_descendants(self) -> bool {
30        !self.validated
31            && matches!(
32                self.kind,
33                ConstraintValidationKind::Check { no_inherit: false }
34                    | ConstraintValidationKind::NotNull {
35                        no_inherit: false,
36                        ..
37                    }
38            )
39    }
40
41    pub fn child_constraint_name<'a>(
42        self,
43        original: &'a str,
44        child: &str,
45        columns: &'a [ColumnDef],
46    ) -> Result<&'a str, SQLError> {
47        let ConstraintValidationKind::NotNull { column, .. } = self.kind else {
48            return Ok(original);
49        };
50        super::inheritance::not_null_constraint(columns, column)
51            .and_then(|candidate| candidate.not_null_name.as_deref())
52            .ok_or_else(|| constraint_error("XX000", format!("cache lookup failed for not-null constraint on column \"{column}\" of relation \"{child}\"")))
53    }
54}
55
56pub fn constraint_validation<'a>(
57    table: &str,
58    name: &str,
59    columns: &'a [ColumnDef],
60    constraints: &'a TableConstraintSet,
61) -> Result<ConstraintValidation<'a>, SQLError> {
62    let location = find_constraint(columns, constraints, name).ok_or_else(|| {
63        constraint_error(
64            "42704",
65            format!("constraint \"{name}\" of relation \"{table}\" does not exist"),
66        )
67    })?;
68    let (kind, validated, enforced) = match location {
69        ConstraintLocation::NotNull(index) => {
70            let column = &columns[index];
71            (ConstraintValidationKind::NotNull { column: &column.name, no_inherit: column.not_null_no_inherit }, column.not_null_validated, true)
72        }
73        ConstraintLocation::ColumnCheck(index) => {
74            let column = &columns[index];
75            (ConstraintValidationKind::Check { no_inherit: column.check_no_inherit }, column.check_validated, column.check_enforced)
76        }
77        ConstraintLocation::TableCheck(index) => {
78            let check = &constraints.checks[index];
79            (ConstraintValidationKind::Check { no_inherit: check.no_inherit }, check.validated, check.enforced)
80        }
81        ConstraintLocation::ColumnForeignKey(index) => {
82            let reference = columns[index].references.as_ref()
83                .ok_or_else(|| SQLError::Internal("column FOREIGN KEY disappeared".into()))?;
84            (ConstraintValidationKind::ForeignKey { referenced_table: &reference.table }, reference.validated, reference.enforced)
85        }
86        ConstraintLocation::TableForeignKey(index) => {
87            let reference = &constraints.foreign_keys[index];
88            (ConstraintValidationKind::ForeignKey { referenced_table: &reference.ref_table }, reference.validated, reference.enforced)
89        }
90        ConstraintLocation::Key(_) => return Err(constraint_error("42809", format!("constraint \"{name}\" of relation \"{table}\" is not a foreign key, check, or not-null constraint"))),
91    };
92    if !enforced {
93        return Err(constraint_error(
94            "55000",
95            "cannot validate NOT ENFORCED constraint",
96        ));
97    }
98    Ok(ConstraintValidation { kind, validated })
99}
100
101pub fn ensure_validation_recurses(recurse: bool, has_children: bool) -> Result<(), SQLError> {
102    if !recurse && has_children {
103        return Err(constraint_error(
104            "42P16",
105            "constraint must be validated on child tables too",
106        ));
107    }
108    Ok(())
109}
110
111#[cfg(test)]
112mod tests;