uqa_sql/schema/
check_inheritance.rs1use crate::ast::{ColumnDef, ColumnType, Expr, TableCheck};
10use crate::ScalarExpr;
11use crate::{ast::CreateTable, SQLError};
12use uqa_core::Value;
13
14pub fn bind_parent_check_columns(parent: &str, expr: &mut Expr) -> Result<(), SQLError> {
15 let relation =
16 uqa_core::RelationIdentity::from_legacy_name(parent).map_err(SQLError::Internal)?;
17 crate::schema::generated::bind_schema_column_references(expr, parent);
18 crate::schema::generated::bind_schema_column_references(expr, &relation.name);
19 Ok(())
20}
21
22pub fn same_check_expression(
23 left: &Expr,
24 right: &Expr,
25 columns: &[ColumnDef],
26) -> Result<bool, SQLError> {
27 fn canonical(expression: &Expr, columns: &[ColumnDef]) -> Result<ScalarExpr, SQLError> {
28 let mut scalar = crate::plan::ExpressionPlan::lower(expression.clone()).scalar;
29 let mut failure = None;
30 crate::plan::rewrite_scalar_expression(&mut scalar, &mut |node| {
31 let ScalarExpr::Cast { expr, ty } = node else {
32 return;
33 };
34 let Ok(target) = ColumnType::from_sql_name(ty) else {
35 return;
36 };
37 if let ScalarExpr::Column(name) = expr.as_ref() {
38 if columns
39 .iter()
40 .any(|column| column.name == *name && column.ty == target)
41 {
42 *node = *expr.clone();
43 }
44 } else if let ScalarExpr::Literal(value @ Value::Str(_)) = expr.as_ref() {
45 if target == ColumnType::Integer {
47 match crate::expr::cast_value(value, ty) {
48 Ok(value) => *node = ScalarExpr::Literal(value),
49 Err(error) => failure = Some(error),
50 }
51 }
52 }
53 });
54 if let Some(error) = failure {
55 return Err(error);
56 }
57 Ok(scalar)
58 }
59 Ok(canonical(left, columns)? == canonical(right, columns)?)
60}
61
62pub fn duplicate_check(table: &str, name: &str) -> SQLError {
63 error(
64 "42710",
65 format!("constraint \"{name}\" for relation \"{table}\" already exists"),
66 )
67}
68
69fn error(sqlstate: &str, message: String) -> SQLError {
70 SQLError::Routine {
71 sqlstate: sqlstate.into(),
72 message,
73 }
74}
75
76pub fn validate_check_merge(
78 table: &str,
79 existing: &TableCheck,
80 incoming: &TableCheck,
81 columns: &[ColumnDef],
82) -> Result<(), SQLError> {
83 let name = incoming.name.as_deref().unwrap_or("<unnamed>");
84 if !same_check_expression(&existing.expr, &incoming.expr, columns)? {
85 return Err(duplicate_check(table, name));
86 }
87 let conflict = if existing.no_inherit {
88 Some("non-inherited")
89 } else if incoming.no_inherit {
90 Some("inherited")
91 } else if incoming.validated && existing.enforced && !existing.validated {
92 Some("NOT VALID")
93 } else if (!incoming.is_local && incoming.enforced && !existing.enforced)
94 || (incoming.is_local && !incoming.enforced && existing.enforced)
95 {
96 Some("NOT ENFORCED")
97 } else {
98 None
99 };
100 if let Some(conflict) = conflict {
101 return Err(error("42P17", format!("constraint \"{name}\" conflicts with {conflict} constraint on relation \"{table}\"")));
102 }
103 Ok(())
104}
105
106pub fn merge_create_checks(table: &mut CreateTable) -> Result<(), SQLError> {
108 let relation =
109 uqa_core::RelationIdentity::from_legacy_name(&table.name).map_err(SQLError::Internal)?;
110 let mut local_names = std::collections::BTreeSet::new();
111 for name in table
112 .columns
113 .iter()
114 .filter_map(|column| column.check_name.as_ref())
115 .chain(
116 table
117 .checks
118 .iter()
119 .filter(|check| check.is_local)
120 .filter_map(|check| check.name.as_ref()),
121 )
122 {
123 if !local_names.insert(name) {
124 return Err(duplicate_check(&relation.name, name));
125 }
126 }
127 if table.hierarchy.parents.is_empty() {
128 return Ok(());
129 }
130 let check_columns = table.columns.clone();
131 let mut inherited: Vec<TableCheck> = Vec::new();
132 let mut local = Vec::new();
133 for check in std::mem::take(&mut table.checks) {
134 if check.is_local {
135 local.push(check);
136 } else if let Some(existing) = inherited
137 .iter_mut()
138 .find(|existing| existing.name == check.name)
139 {
140 if !same_check_expression(&existing.expr, &check.expr, &check_columns)? {
141 return Err(error("42710", format!("check constraint name \"{}\" appears multiple times but with different expressions", check.name.as_deref().unwrap_or("<unnamed>"))));
142 }
143 existing.enforced |= check.enforced;
144 existing.validated = existing.enforced;
145 } else {
146 inherited.push(check);
147 }
148 }
149 for column in &mut table.columns {
150 let Some((expr, name)) = column.check.as_ref().zip(column.check_name.as_ref()) else {
151 continue;
152 };
153 let Some(index) = inherited
154 .iter()
155 .position(|check| check.name.as_ref() == Some(name))
156 else {
157 continue;
158 };
159 let incoming = TableCheck {
160 name: Some(name.clone()),
161 object_id: column.check_object_id,
162 is_local: true,
163 expr: expr.clone(),
164 enforced: column.check_enforced,
165 validated: column.check_validated,
166 no_inherit: column.check_no_inherit,
167 partition_constraint: None,
168 };
169 validate_check_merge(&relation.name, &inherited[index], &incoming, &check_columns)?;
170 inherited.remove(index);
171 column.check_is_local = !table.hierarchy.is_partition();
172 }
173 for mut check in local {
174 if let Some(index) = inherited
175 .iter()
176 .position(|existing| existing.name.is_some() && existing.name == check.name)
177 {
178 let existing = &inherited[index];
179 if existing.is_local {
180 return Err(duplicate_check(
181 &relation.name,
182 check.name.as_deref().unwrap_or("<unnamed>"),
183 ));
184 }
185 validate_check_merge(&relation.name, existing, &check, &check_columns)?;
186 check.is_local = !table.hierarchy.is_partition();
187 inherited[index] = check;
188 } else {
189 inherited.push(check);
190 }
191 }
192 table.checks = inherited;
193 Ok(())
194}