uqa_sql/schema/constraint_changes/
validation.rs1use 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;