1use crate::ast::{
9 AutoIncrement, BinaryOp, ColumnDef, DetachedPartitionConstraint, Expr, PartitionBound,
10 PartitionIdentityOverride, PartitionRangeDatum, PartitionSpec, RelationPersistence, TableCheck,
11 TableHierarchy, TableKeyConstraint,
12};
13use crate::SQLError;
14use uqa_core::Value;
15pub fn validate_row_type(
16 parent_columns: &[ColumnDef],
17 child_columns: &[ColumnDef],
18 parent: &str,
19 child: &str,
20 exact_columns: bool,
21 reject_child_identity: bool,
22) -> Result<(), SQLError> {
23 validate_attached_columns(
24 parent_columns,
25 child_columns,
26 parent,
27 child,
28 exact_columns,
29 reject_child_identity,
30 )?;
31 for parent_column in parent_columns {
32 let Some(child_column) = child_columns
33 .iter()
34 .find(|column| column.name == parent_column.name)
35 else {
36 return Err(routine(
37 "42804",
38 format!("child table is missing column \"{}\"", parent_column.name),
39 ));
40 };
41 validate_inherited_column(parent_column, child_column, child)?;
42 }
43 Ok(())
44}
45
46fn validate_attached_columns(
48 parent_columns: &[ColumnDef],
49 child_columns: &[ColumnDef],
50 parent: &str,
51 child: &str,
52 exact_columns: bool,
53 reject_child_identity: bool,
54) -> Result<(), SQLError> {
55 for column in child_columns {
56 if reject_child_identity
57 && column
58 .auto_increment
59 .as_ref()
60 .is_some_and(AutoIncrement::is_identity)
61 {
62 return Err(SQLError::Diagnostic {
63 sqlstate: "55000".into(),
64 message: format!(
65 "table \"{}\" being attached contains an identity column \"{}\"",
66 local_relation_name(child),
67 column.name
68 ),
69 detail: Some("The new partition may not contain an identity column.".into()),
70 hint: None,
71 });
72 }
73 if exact_columns
74 && !parent_columns
75 .iter()
76 .any(|parent_column| parent_column.name == column.name)
77 {
78 return Err(SQLError::Diagnostic {
79 sqlstate: "42804".into(),
80 message: format!(
81 "table \"{}\" contains column \"{}\" not found in parent \"{}\"",
82 local_relation_name(child),
83 column.name,
84 local_relation_name(parent)
85 ),
86 detail: Some(
87 "The new partition may contain only the columns present in parent.".into(),
88 ),
89 hint: None,
90 });
91 }
92 }
93 Ok(())
94}
95
96fn validate_inherited_column(
98 parent_column: &ColumnDef,
99 child_column: &ColumnDef,
100 child: &str,
101) -> Result<(), SQLError> {
102 if parent_column.ty != child_column.ty {
103 return Err(routine(
104 "42804",
105 format!(
106 "child table \"{}\" has different type for column \"{}\"",
107 local_relation_name(child),
108 parent_column.name
109 ),
110 ));
111 }
112 if crate::catalog::collations::declarations::column_oid(parent_column)
113 != crate::catalog::collations::declarations::column_oid(child_column)
114 {
115 return Err(routine(
116 "42P21",
117 format!(
118 "child table \"{}\" has different collation for column \"{}\"",
119 local_relation_name(child),
120 parent_column.name
121 ),
122 ));
123 }
124 if parent_column.not_null && !child_column.not_null {
125 return Err(routine(
126 "42804",
127 format!(
128 "column \"{}\" in child table \"{}\" must be marked NOT NULL",
129 parent_column.name,
130 local_relation_name(child)
131 ),
132 ));
133 }
134 match (&parent_column.generated, &child_column.generated) {
135 (None, None) => Ok(()),
136 (Some(_), None) => Err(routine(
137 "42804",
138 format!(
139 "column \"{}\" in child table must be a generated column",
140 parent_column.name
141 ),
142 )),
143 (None, Some(_)) => Err(routine(
144 "42804",
145 format!(
146 "column \"{}\" in child table must not be a generated column",
147 parent_column.name
148 ),
149 )),
150 (Some(parent_generated), Some(child_generated)) => {
151 if parent_generated.kind == child_generated.kind {
152 Ok(())
153 } else {
154 Err(routine(
155 "42804",
156 format!(
157 "column \"{}\" inherits from generated column of different kind",
158 parent_column.name
159 ),
160 ))
161 }
162 }
163 }
164}
165
166pub fn validate_inherited_checks(
167 child: &str,
168 child_columns: &[ColumnDef],
169 parent_checks: &[TableCheck],
170 child_checks: &[TableCheck],
171) -> Result<(), SQLError> {
172 for parent_check in parent_checks
173 .iter()
174 .filter(|constraint| !constraint.no_inherit)
175 {
176 let Some(name) = parent_check.name.as_deref() else {
177 return Err(SQLError::Internal(
178 "persisted parent CHECK constraint has no name".into(),
179 ));
180 };
181 let Some(child_check) = child_checks
182 .iter()
183 .find(|constraint| constraint.name.as_deref() == Some(name))
184 else {
185 return Err(routine(
186 "42804",
187 format!("child table is missing constraint \"{name}\""),
188 ));
189 };
190 if !crate::schema::check_inheritance::same_check_expression(
191 &child_check.expr,
192 &parent_check.expr,
193 child_columns,
194 )? {
195 return Err(routine(
196 "42804",
197 format!(
198 "child table \"{}\" has different definition for check constraint \"{name}\"",
199 local_relation_name(child)
200 ),
201 ));
202 }
203 let conflict = if child_check.no_inherit {
204 Some("non-inherited")
205 } else if parent_check.validated && child_check.enforced && !child_check.validated {
206 Some("NOT VALID")
207 } else if parent_check.enforced && !child_check.enforced {
208 Some("NOT ENFORCED")
209 } else {
210 None
211 };
212 if let Some(conflict) = conflict {
213 return Err(routine("42P17", format!("constraint \"{name}\" conflicts with {conflict} constraint on child table \"{}\"", local_relation_name(child))));
214 }
215 }
216 Ok(())
217}
218
219pub fn install_inherited_identity(
220 columns: &mut [ColumnDef],
221 inherited: &[(String, AutoIncrement)],
222) -> Result<Vec<PartitionIdentityOverride>, SQLError> {
223 let mut overrides = Vec::with_capacity(inherited.len());
224 for (name, increment) in inherited {
225 let column = columns
226 .iter_mut()
227 .find(|column| column.name == *name)
228 .ok_or_else(|| SQLError::Internal(format!("partition lost column `{name}`")))?;
229 overrides.push(PartitionIdentityOverride {
230 column: name.clone(),
231 original: column.auto_increment.clone(),
232 });
233 column.auto_increment = Some(increment.clone());
234 }
235 Ok(overrides)
236}
237
238pub fn restore_identity_overrides(
239 columns: &mut [ColumnDef],
240 inherited: &[(String, AutoIncrement)],
241 overrides: &[PartitionIdentityOverride],
242) {
243 for (name, _) in inherited {
244 let Some(column) = columns.iter_mut().find(|column| column.name == *name) else {
245 continue;
246 };
247 column.auto_increment = overrides
248 .iter()
249 .find(|identity_override| identity_override.column == *name)
250 .and_then(|identity_override| identity_override.original.clone());
251 }
252}
253
254pub fn append_inherited_keys(
255 target: &mut Vec<TableKeyConstraint>,
256 inherited: &[TableKeyConstraint],
257) -> Vec<TableKeyConstraint> {
258 append_inherited_keys_matching(target, inherited, |_, _| true)
259}
260
261pub fn append_inherited_keys_matching(
263 target: &mut Vec<TableKeyConstraint>,
264 inherited: &[TableKeyConstraint],
265 can_attach: impl Fn(&TableKeyConstraint, &TableKeyConstraint) -> bool,
266) -> Vec<TableKeyConstraint> {
267 let mut appended = Vec::new();
268 let mut used = std::collections::BTreeSet::new();
269 for constraint in inherited {
270 if let Some((position, _)) = target.iter().enumerate().find(|(position, candidate)| {
271 !used.contains(position)
272 && key_equivalent(candidate, constraint)
273 && can_attach(candidate, constraint)
274 }) {
275 used.insert(position);
276 continue;
277 }
278 let mut constraint = constraint.clone();
279 constraint.name = None;
280 constraint.catalog_identity = None;
281 used.insert(target.len());
282 target.push(constraint.clone());
283 appended.push(constraint);
284 }
285 appended
286}
287
288pub fn key_equivalent(left: &TableKeyConstraint, right: &TableKeyConstraint) -> bool {
289 left.kind == right.kind
290 && left.columns == right.columns
291 && left.included_columns == right.included_columns
292 && left.nulls_not_distinct == right.nulls_not_distinct
293 && left.without_overlaps == right.without_overlaps
294}
295
296pub fn clear_partition_constraint_provenance(constraints: &mut crate::ast::TableConstraintSet) {
297 constraints
298 .hierarchy
299 .partition_inherited_key_constraints
300 .clear();
301 constraints
302 .hierarchy
303 .partition_inherited_foreign_keys
304 .clear();
305}
306
307pub fn detached_bound_check(
308 table: &str,
309 spec: &PartitionSpec,
310 bound: &PartitionBound,
311 existing: &[TableCheck],
312) -> TableCheck {
313 let expr = renderable_bound_expression(spec, bound);
314 let relation = local_relation_name(table);
315 let key = spec.keys.first().and_then(|key| match key {
316 Expr::Column(column) => Some(column.as_str()),
317 _ => None,
318 });
319 let base = key.map_or_else(
320 || format!("{relation}_check"),
321 |column| format!("{relation}_{column}_check"),
322 );
323 let name = unique_constraint_name(&base, existing);
324 TableCheck {
325 catalog_oid: None,
326 name: Some(name),
327 expr,
328 enforced: true,
329 validated: true,
330 no_inherit: false,
331 object_id: None,
332 is_local: true,
333 partition_constraint: Some(DetachedPartitionConstraint {
334 spec: spec.clone(),
335 bound: bound.clone(),
336 }),
337 }
338}
339
340fn unique_constraint_name(base: &str, existing: &[TableCheck]) -> String {
341 if !existing
342 .iter()
343 .any(|constraint| constraint.name.as_deref() == Some(base))
344 {
345 return base.to_string();
346 }
347 for suffix in 1_u64.. {
348 let candidate = format!("{base}{suffix}");
349 if !existing
350 .iter()
351 .any(|constraint| constraint.name.as_deref() == Some(candidate.as_str()))
352 {
353 return candidate;
354 }
355 }
356 unreachable!("u64 constraint suffix space is exhaustive")
357}
358
359fn renderable_bound_expression(spec: &PartitionSpec, bound: &PartitionBound) -> Expr {
360 let Some(key) = spec.keys.first().cloned().filter(|_| spec.keys.len() == 1) else {
361 return Expr::Literal(Value::Bool(true));
362 };
363 match bound {
364 PartitionBound::List(values) => {
365 let mut terms = Vec::new();
366 let mut non_null = Vec::new();
367 for value in values {
368 if matches!(value, Expr::Literal(Value::Null)) {
369 terms.push(Expr::IsNull {
370 expr: Box::new(key.clone()),
371 negated: false,
372 });
373 } else {
374 non_null.push(value.clone());
375 }
376 }
377 if !non_null.is_empty() {
378 terms.push(Expr::InList {
379 expr: Box::new(key),
380 list: non_null,
381 negated: false,
382 });
383 }
384 if terms.len() == 1 {
385 terms.pop().unwrap_or(Expr::Literal(Value::Bool(true)))
386 } else {
387 Expr::Or(terms)
388 }
389 }
390 PartitionBound::Range { lower, upper } if lower.len() == 1 && upper.len() == 1 => {
391 let mut terms = vec![Expr::IsNull {
392 expr: Box::new(key.clone()),
393 negated: true,
394 }];
395 if let PartitionRangeDatum::Value(lower) = &lower[0] {
396 terms.push(Expr::Binary {
397 op: BinaryOp::GreaterEqual,
398 lhs: Box::new(key.clone()),
399 rhs: Box::new(lower.clone()),
400 });
401 }
402 if let PartitionRangeDatum::Value(upper) = &upper[0] {
403 terms.push(Expr::Binary {
404 op: BinaryOp::Less,
405 lhs: Box::new(key),
406 rhs: Box::new(upper.clone()),
407 });
408 }
409 Expr::And(terms)
410 }
411 PartitionBound::Hash { .. } | PartitionBound::Range { .. } | PartitionBound::Default => {
412 Expr::Literal(Value::Bool(true))
413 }
414 }
415}
416
417pub fn validate_matching_persistence(
418 child: &str,
419 parent: &str,
420 operation: &str,
421 child_persistence: RelationPersistence,
422 parent_persistence: RelationPersistence,
423) -> Result<(), SQLError> {
424 if (child_persistence == RelationPersistence::Temporary)
425 != (parent_persistence == RelationPersistence::Temporary)
426 {
427 return Err(wrong_object(format!(
428 "cannot {operation} {} relation \"{}\" from {} relation \"{}\"",
429 persistence_label(child_persistence),
430 local_relation_name(child),
431 persistence_label(parent_persistence),
432 local_relation_name(parent)
433 )));
434 }
435 Ok(())
436}
437
438fn persistence_label(persistence: RelationPersistence) -> &'static str {
439 match persistence {
440 RelationPersistence::Temporary => "temporary",
441 RelationPersistence::Unlogged => "unlogged",
442 RelationPersistence::Permanent => "permanent",
443 }
444}
445
446pub fn normalize_parent_sequence_numbers(hierarchy: &mut TableHierarchy) {
447 if hierarchy.parent_sequence_numbers.len() == hierarchy.parents.len() {
448 return;
449 }
450 hierarchy.parent_sequence_numbers = hierarchy
451 .parents
452 .iter()
453 .enumerate()
454 .map(|(index, _)| i32::try_from(index + 1).unwrap_or(i32::MAX))
455 .collect();
456}
457
458fn local_relation_name(name: &str) -> &str {
459 name.rsplit('.').next().unwrap_or(name)
460}
461fn wrong_object(message: impl Into<String>) -> SQLError {
462 routine("42809", message)
463}
464fn routine(sqlstate: &str, message: impl Into<String>) -> SQLError {
465 SQLError::Routine {
466 sqlstate: sqlstate.into(),
467 message: message.into(),
468 }
469}