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 {
18 no_inherit: bool,
19 },
20 NotNull {
21 column: &'a str,
22 no_inherit: bool,
23 },
24 ForeignKey {
25 referenced_table: &'a str,
26 },
27 ReferencedPartition {
29 partition: [u8; 16],
30 },
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct ConstraintValidation<'a> {
35 pub kind: ConstraintValidationKind<'a>,
36 pub validated: bool,
37}
38
39impl ConstraintValidation<'_> {
40 pub fn requires_descendants(self) -> bool {
41 !self.validated
42 && matches!(
43 self.kind,
44 ConstraintValidationKind::Check { no_inherit: false }
45 | ConstraintValidationKind::NotNull {
46 no_inherit: false,
47 ..
48 }
49 )
50 }
51
52 pub fn child_constraint_name<'a>(
53 self,
54 original: &'a str,
55 child: &str,
56 columns: &'a [ColumnDef],
57 ) -> Result<&'a str, SQLError> {
58 let ConstraintValidationKind::NotNull { column, .. } = self.kind else {
59 return Ok(original);
60 };
61 super::inheritance::not_null_constraint(columns, column)
62 .and_then(|candidate| candidate.not_null_name.as_deref())
63 .ok_or_else(|| constraint_error("XX000", format!("cache lookup failed for not-null constraint on column \"{column}\" of relation \"{child}\"")))
64 }
65}
66
67pub fn constraint_validation<'a>(
68 table: &str,
69 name: &str,
70 columns: &'a [ColumnDef],
71 constraints: &'a TableConstraintSet,
72) -> Result<ConstraintValidation<'a>, SQLError> {
73 let location = find_constraint(columns, constraints, name).ok_or_else(|| {
74 constraint_error(
75 "42704",
76 format!("constraint \"{name}\" of relation \"{table}\" does not exist"),
77 )
78 })?;
79 let (kind, validated, enforced) = match location {
80 ConstraintLocation::NotNull(index) => {
81 let column = &columns[index];
82 (ConstraintValidationKind::NotNull { column: &column.name, no_inherit: column.not_null_no_inherit }, column.not_null_validated, true)
83 }
84 ConstraintLocation::ColumnCheck(index) => {
85 let column = &columns[index];
86 (ConstraintValidationKind::Check { no_inherit: column.check_no_inherit }, column.check_validated, column.check_enforced)
87 }
88 ConstraintLocation::TableCheck(index) => {
89 let check = &constraints.checks[index];
90 (ConstraintValidationKind::Check { no_inherit: check.no_inherit }, check.validated, check.enforced)
91 }
92 ConstraintLocation::ColumnForeignKey(index) => {
93 let reference = columns[index].references.as_ref()
94 .ok_or_else(|| SQLError::Internal("column FOREIGN KEY disappeared".into()))?;
95 (ConstraintValidationKind::ForeignKey { referenced_table: &reference.table }, reference.validated, reference.enforced)
96 }
97 ConstraintLocation::TableForeignKey(index) => {
98 let reference = &constraints.foreign_keys[index];
99 (ConstraintValidationKind::ForeignKey { referenced_table: &reference.ref_table }, reference.validated, reference.enforced)
100 }
101 ConstraintLocation::Key(_) => return Err(constraint_error("42809", format!("constraint \"{name}\" of relation \"{table}\" is not a foreign key, check, or not-null constraint"))),
102 ConstraintLocation::ReferencedPartition(foreign_key, index) => {
103 let derived = foreign_key
104 .derived(columns, constraints)
105 .and_then(|(_, derived)| derived.get(index))
106 .ok_or_else(|| SQLError::Internal("derived constraint disappeared".into()))?;
107 let enforced = match foreign_key {
108 super::ForeignKeyLocation::Column(position) => columns[position]
109 .references
110 .as_ref()
111 .is_some_and(|reference| reference.enforced),
112 super::ForeignKeyLocation::Table(position) => constraints.foreign_keys[position].enforced,
113 };
114 (ConstraintValidationKind::ReferencedPartition { partition: derived.partition }, derived.validated, enforced)
115 }
116 };
117 if !enforced {
118 return Err(constraint_error(
119 "55000",
120 "cannot validate NOT ENFORCED constraint",
121 ));
122 }
123 Ok(ConstraintValidation { kind, validated })
124}
125
126pub fn ensure_validation_recurses(recurse: bool, has_children: bool) -> Result<(), SQLError> {
127 if !recurse && has_children {
128 return Err(constraint_error(
129 "42P16",
130 "constraint must be validated on child tables too",
131 ));
132 }
133 Ok(())
134}
135
136#[cfg(test)]
137mod tests;