1use std::collections::BTreeSet;
9use uqa_core::RelationIdentity;
10
11#[derive(Debug, thiserror::Error)]
12#[error("{0}")]
13pub struct ConstraintMetadataError(pub String);
14pub type ConstraintMetadataResult<T> = Result<T, ConstraintMetadataError>;
15pub type CatalogIdentityAllocator<'a> = dyn FnMut(&str) -> ConstraintMetadataResult<[u8; 16]> + 'a;
16
17pub fn materialize_constraint_metadata(
18 relation: &RelationIdentity,
19 columns: &mut [crate::ast::ColumnDef],
20 constraints: &mut crate::ast::TableConstraintSet,
21 allocate: &mut CatalogIdentityAllocator<'_>,
22) -> ConstraintMetadataResult<bool> {
23 let mut changed = promote_legacy_column_key_constraints(columns, constraints);
25 let mut used = BTreeSet::new();
26 for column in columns.iter() {
27 record_constraint_name(&mut used, column.not_null_name.as_deref())?;
28 record_constraint_name(&mut used, column.check_name.as_deref())?;
29 record_constraint_name(
30 &mut used,
31 column
32 .references
33 .as_ref()
34 .and_then(|reference| reference.name.as_deref()),
35 )?;
36 }
37 for constraint in &constraints.key_constraints {
38 record_constraint_name(&mut used, constraint.name.as_deref())?;
39 }
40 for constraint in &constraints.checks {
41 record_constraint_name(&mut used, constraint.name.as_deref())?;
42 }
43 for constraint in &constraints.foreign_keys {
44 record_constraint_name(&mut used, constraint.name.as_deref())?;
45 }
46
47 let mut column_object_ids = BTreeSet::new();
48 for column in columns.iter_mut() {
49 if column
50 .object_id
51 .is_some_and(|object_id| !column_object_ids.insert(object_id))
52 {
53 column.object_id = None;
54 }
55 changed |= assign_catalog_object_id(&mut column.object_id, "column", allocate)?;
56 if let Some(object_id) = column.object_id {
57 column_object_ids.insert(object_id);
58 }
59 if column.not_null {
60 changed |= assign_constraint_name(
61 &mut column.not_null_name,
62 format!("{}_{}_not_null", relation.name, column.name),
63 &mut used,
64 )?;
65 }
66 if column.check.is_some() {
67 changed |= assign_constraint_name(
68 &mut column.check_name,
69 format!("{}_{}_check", relation.name, column.name),
70 &mut used,
71 )?;
72 changed |= assign_catalog_object_id(
73 &mut column.check_object_id,
74 "CHECK constraint",
75 allocate,
76 )?;
77 }
78 if let Some(reference) = &mut column.references {
79 changed |= assign_constraint_name(
80 &mut reference.name,
81 format!("{}_{}_fkey", relation.name, column.name),
82 &mut used,
83 )?;
84 changed |= assign_constraint_object_id(&mut reference.object_id, allocate)?;
85 }
86 }
87 for constraint in &mut constraints.key_constraints {
88 let base = match constraint.kind {
89 crate::ast::TableKeyConstraintKind::PrimaryKey => {
90 format!("{}_pkey", relation.name)
91 }
92 crate::ast::TableKeyConstraintKind::Unique => format!(
93 "{}_{}_key",
94 relation.name,
95 constraint_column_component(&constraint.columns, relation)?
96 ),
97 };
98 changed |= assign_constraint_name(&mut constraint.name, base, &mut used)?;
99 }
100 for constraint in &mut constraints.checks {
101 let mut referenced_columns = Vec::new();
102 collect_constraint_columns(&constraint.expr, &mut referenced_columns);
103 let base = if referenced_columns.len() == 1 {
104 format!("{}_{}_check", relation.name, referenced_columns[0])
105 } else {
106 format!("{}_check", relation.name)
107 };
108 changed |= assign_constraint_name(&mut constraint.name, base, &mut used)?;
109 changed |=
110 assign_catalog_object_id(&mut constraint.object_id, "CHECK constraint", allocate)?;
111 }
112 changed |= synchronize_partition_inherited_foreign_key_ids(constraints);
113 for constraint in &mut constraints.foreign_keys {
114 let component = constraint_column_component(&constraint.local_columns, relation)?;
115 changed |= assign_constraint_name(
116 &mut constraint.name,
117 format!("{}_{}_fkey", relation.name, component),
118 &mut used,
119 )?;
120 changed |= assign_constraint_object_id(&mut constraint.object_id, allocate)?;
121 }
122 changed |= synchronize_partition_inherited_foreign_key_ids(constraints);
123 Ok(changed)
124}
125
126fn promote_legacy_column_key_constraints(
127 columns: &[crate::ast::ColumnDef],
128 constraints: &mut crate::ast::TableConstraintSet,
129) -> bool {
130 let mut changed = false;
131 for column in columns {
132 for (present, kind) in [
133 (
134 column.primary_key,
135 crate::ast::TableKeyConstraintKind::PrimaryKey,
136 ),
137 (column.unique, crate::ast::TableKeyConstraintKind::Unique),
138 ] {
139 if !present
140 || constraints.key_constraints.iter().any(|constraint| {
141 constraint.kind == kind
142 && constraint.columns.as_slice() == [column.name.as_str()]
143 })
144 {
145 continue;
146 }
147 constraints
148 .key_constraints
149 .push(crate::ast::TableKeyConstraint {
150 name: None,
151 kind,
152 columns: vec![column.name.clone()],
153 nulls_not_distinct: false,
154 without_overlaps: false,
155 });
156 changed = true;
157 }
158 }
159 changed
160}
161
162pub fn foreign_keys_match_without_object_id(
163 left: &crate::ast::ForeignKey,
164 right: &crate::ast::ForeignKey,
165) -> bool {
166 let mut left = left.clone();
167 let mut right = right.clone();
168 left.object_id = None;
169 right.object_id = None;
170 left == right
171}
172
173pub fn synchronize_partition_inherited_foreign_key_ids(
174 constraints: &mut crate::ast::TableConstraintSet,
175) -> bool {
176 let mut changed = false;
177 for inherited_index in 0..constraints.hierarchy.partition_inherited_foreign_keys.len() {
178 let inherited = &constraints.hierarchy.partition_inherited_foreign_keys[inherited_index];
179 let Some(foreign_key_index) = constraints
180 .foreign_keys
181 .iter()
182 .position(|foreign_key| foreign_keys_match_without_object_id(foreign_key, inherited))
183 else {
184 continue;
185 };
186 let object_id = constraints.foreign_keys[foreign_key_index]
187 .object_id
188 .or(inherited.object_id);
189 if constraints.foreign_keys[foreign_key_index].object_id != object_id {
190 constraints.foreign_keys[foreign_key_index].object_id = object_id;
191 changed = true;
192 }
193 if constraints.hierarchy.partition_inherited_foreign_keys[inherited_index].object_id
194 != object_id
195 {
196 constraints.hierarchy.partition_inherited_foreign_keys[inherited_index].object_id =
197 object_id;
198 changed = true;
199 }
200 }
201 changed
202}
203
204fn assign_constraint_object_id(
205 target: &mut Option<[u8; 16]>,
206 allocate: &mut CatalogIdentityAllocator<'_>,
207) -> ConstraintMetadataResult<bool> {
208 assign_catalog_object_id(target, "foreign-key constraint", allocate)
209}
210
211fn assign_catalog_object_id(
212 target: &mut Option<[u8; 16]>,
213 object_kind: &str,
214 allocate: &mut CatalogIdentityAllocator<'_>,
215) -> ConstraintMetadataResult<bool> {
216 if target.is_some() {
217 return Ok(false);
218 }
219 *target = Some(allocate(object_kind)?);
220 Ok(true)
221}
222
223fn record_constraint_name(
224 used: &mut BTreeSet<String>,
225 name: Option<&str>,
226) -> ConstraintMetadataResult<()> {
227 let Some(name) = name else {
228 return Ok(());
229 };
230 if name.is_empty() {
231 return Err(ConstraintMetadataError(
232 "constraint name must not be empty".into(),
233 ));
234 }
235 if !used.insert(name.to_string()) {
236 return Err(ConstraintMetadataError(format!(
237 "constraint `{name}` is declared more than once"
238 )));
239 }
240 Ok(())
241}
242
243fn assign_constraint_name(
244 target: &mut Option<String>,
245 base: String,
246 used: &mut BTreeSet<String>,
247) -> ConstraintMetadataResult<bool> {
248 if target.is_some() {
249 return Ok(false);
250 }
251 if used.insert(base.clone()) {
252 *target = Some(base);
253 return Ok(true);
254 }
255 for suffix in 1_u64.. {
256 let candidate = format!("{base}{suffix}");
257 if used.insert(candidate.clone()) {
258 *target = Some(candidate);
259 return Ok(true);
260 }
261 }
262 Err(ConstraintMetadataError(format!(
263 "constraint name suffix space exhausted for `{base}`"
264 )))
265}
266
267fn constraint_column_component(
268 columns: &[String],
269 relation: &RelationIdentity,
270) -> ConstraintMetadataResult<String> {
271 if columns.is_empty() {
272 return Err(ConstraintMetadataError(format!(
273 "constraint on table `{}` has no columns",
274 relation.qualified_name()
275 )));
276 }
277 Ok(columns.join("_"))
278}
279
280fn collect_constraint_columns(expression: &crate::ast::Expr, output: &mut Vec<String>) {
281 use crate::ast::{Expr, FrameBound};
282 match expression {
283 Expr::Column(name) | Expr::QualifiedColumn { column: name, .. } => {
284 if !output.contains(name) {
285 output.push(name.clone());
286 }
287 }
288 Expr::Func {
289 args,
290 order_by,
291 filter,
292 ..
293 } => {
294 for argument in args {
295 collect_constraint_columns(argument, output);
296 }
297 for order in order_by {
298 collect_constraint_columns(&order.expr, output);
299 }
300 if let Some(filter) = filter {
301 collect_constraint_columns(filter, output);
302 }
303 }
304 Expr::Array(items) | Expr::Row(items) | Expr::And(items) | Expr::Or(items) => {
305 for item in items {
306 collect_constraint_columns(item, output);
307 }
308 }
309 Expr::Binary { lhs, rhs, .. } => {
310 collect_constraint_columns(lhs, output);
311 collect_constraint_columns(rhs, output);
312 }
313 Expr::Not(inner)
314 | Expr::UnaryMinus(inner)
315 | Expr::IsNull { expr: inner, .. }
316 | Expr::Cast { expr: inner, .. } => {
317 collect_constraint_columns(inner, output);
318 }
319 Expr::Between { expr, low, high } => {
320 collect_constraint_columns(expr, output);
321 collect_constraint_columns(low, output);
322 collect_constraint_columns(high, output);
323 }
324 Expr::InList { expr, list, .. } => {
325 collect_constraint_columns(expr, output);
326 for item in list {
327 collect_constraint_columns(item, output);
328 }
329 }
330 Expr::WindowCall { args, spec, .. } => {
331 for argument in args {
332 collect_constraint_columns(argument, output);
333 }
334 for expression in &spec.partition_by {
335 collect_constraint_columns(expression, output);
336 }
337 for order in &spec.order_by {
338 collect_constraint_columns(&order.expr, output);
339 }
340 if let Some(frame) = &spec.frame {
341 for bound in [&frame.start, &frame.end] {
342 if let FrameBound::Preceding(expression) | FrameBound::Following(expression) =
343 bound
344 {
345 collect_constraint_columns(expression, output);
346 }
347 }
348 }
349 }
350 Expr::Case {
351 base,
352 when,
353 else_branch,
354 } => {
355 if let Some(base) = base {
356 collect_constraint_columns(base, output);
357 }
358 for (condition, result) in when {
359 collect_constraint_columns(condition, output);
360 collect_constraint_columns(result, output);
361 }
362 if let Some(else_branch) = else_branch {
363 collect_constraint_columns(else_branch, output);
364 }
365 }
366 Expr::InSubquery { expr, .. } => collect_constraint_columns(expr, output),
367 Expr::Default
368 | Expr::Star
369 | Expr::QualifiedStar(_)
370 | Expr::InternalColumn(_)
371 | Expr::Literal(_)
372 | Expr::TypedLiteral { .. }
373 | Expr::Param(_)
374 | Expr::ScalarSubquery(_)
375 | Expr::Exists { .. } => {}
376 }
377}