1pub mod foreign_key_target;
9pub mod inheritance;
10pub mod names;
11pub mod not_null_removal;
12pub mod renaming;
13pub mod validation;
14
15#[cfg(test)]
16mod tests;
17
18use crate::schema::foreign_keys::column_foreign_key;
19use crate::{
20 ast::{ColumnType, ForeignKey, TableCheck},
21 SQLError,
22};
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum ConstraintLocation {
25 NotNull(usize),
26 ColumnCheck(usize),
27 ColumnForeignKey(usize),
28 TableCheck(usize),
29 TableForeignKey(usize),
30 Key(usize),
31 ReferencedPartition(ForeignKeyLocation, usize),
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum ForeignKeyLocation {
38 Column(usize),
39 Table(usize),
40}
41
42impl ForeignKeyLocation {
43 pub const fn constraint(self) -> ConstraintLocation {
44 match self {
45 Self::Column(index) => ConstraintLocation::ColumnForeignKey(index),
46 Self::Table(index) => ConstraintLocation::TableForeignKey(index),
47 }
48 }
49
50 pub fn derived<'a>(
52 self,
53 columns: &'a [crate::ast::ColumnDef],
54 constraints: &'a crate::ast::TableConstraintSet,
55 ) -> Option<(&'a str, &'a [crate::ast::ReferencedPartitionConstraint])> {
56 match self {
57 Self::Column(index) => columns
58 .get(index)?
59 .references
60 .as_ref()
61 .and_then(|reference| {
62 Some((
63 reference.name.as_deref()?,
64 reference.referenced_partitions.as_slice(),
65 ))
66 }),
67 Self::Table(index) => constraints.foreign_keys.get(index).and_then(|foreign_key| {
68 Some((
69 foreign_key.name.as_deref()?,
70 foreign_key.referenced_partitions.as_slice(),
71 ))
72 }),
73 }
74 }
75
76 pub fn derived_mut<'a>(
78 self,
79 columns: &'a mut [crate::ast::ColumnDef],
80 constraints: &'a mut crate::ast::TableConstraintSet,
81 ) -> Option<&'a mut Vec<crate::ast::ReferencedPartitionConstraint>> {
82 match self {
83 Self::Column(index) => columns
84 .get_mut(index)?
85 .references
86 .as_mut()
87 .map(|reference| &mut reference.referenced_partitions),
88 Self::Table(index) => constraints
89 .foreign_keys
90 .get_mut(index)
91 .map(|foreign_key| &mut foreign_key.referenced_partitions),
92 }
93 }
94}
95
96pub fn constraint_error(sqlstate: &str, message: impl Into<String>) -> SQLError {
97 SQLError::Routine {
98 sqlstate: sqlstate.into(),
99 message: message.into(),
100 }
101}
102
103pub fn find_constraint(
104 columns: &[crate::ast::ColumnDef],
105 constraints: &crate::ast::TableConstraintSet,
106 name: &str,
107) -> Option<ConstraintLocation> {
108 names::ConstraintNames::from_definition(columns, constraints)
109 .entries()
110 .find(|constraint| constraint.name == name)
111 .map(|constraint| constraint.location)
112}
113
114pub fn ensure_constraint_name_available(
115 columns: &[crate::ast::ColumnDef],
116 constraints: &crate::ast::TableConstraintSet,
117 name: Option<&str>,
118 table: &str,
119) -> Result<(), SQLError> {
120 if let Some(name) = name.filter(|name| find_constraint(columns, constraints, name).is_some()) {
121 return Err(constraint_error(
122 "42710",
123 format!("constraint \"{name}\" for relation \"{table}\" already exists"),
124 ));
125 }
126 Ok(())
127}
128
129pub fn ensure_not_null_inheritable(
130 table: &str,
131 column: &crate::ast::ColumnDef,
132 sqlstate: &str,
133) -> Result<(), SQLError> {
134 if column.not_null_no_inherit {
135 let relation = uqa_core::RelationIdentity::from_legacy_name(table)
136 .map_err(|error| SQLError::Internal(format!("resolve NOT NULL relation: {error}")))?;
137 let name = column.not_null_name.as_deref().unwrap_or("<unnamed>");
138 return Err(constraint_error(
139 sqlstate,
140 format!(
141 "cannot change NO INHERIT status of NOT NULL constraint \"{name}\" on relation \"{}\"",
142 relation.name,
143 ),
144 ));
145 }
146 Ok(())
147}
148
149pub fn take_column_check(column: &mut crate::ast::ColumnDef) -> Option<TableCheck> {
150 let check = TableCheck {
151 expr: column.check.take()?,
152 catalog_oid: column.check_catalog_oid.take(),
153 name: column.check_name.take(),
154 object_id: column.check_object_id.take(),
155 is_local: column.check_is_local,
156 enforced: column.check_enforced,
157 validated: column.check_validated,
158 no_inherit: column.check_no_inherit,
159 partition_constraint: None,
160 };
161 column.check_is_local = true;
162 column.check_enforced = true;
163 column.check_validated = true;
164 column.check_no_inherit = false;
165 Some(check)
166}
167
168pub fn restore_column_check(column: &mut crate::ast::ColumnDef, check: TableCheck) {
170 column.check = Some(check.expr);
171 column.check_catalog_oid = check.catalog_oid;
172 column.check_name = check.name;
173 column.check_object_id = check.object_id;
174 column.check_is_local = check.is_local;
175 column.check_enforced = check.enforced;
176 column.check_validated = check.validated;
177 column.check_no_inherit = check.no_inherit;
178}
179
180pub fn foreign_key_object_id(
181 columns: &[crate::ast::ColumnDef],
182 constraints: &crate::ast::TableConstraintSet,
183 location: ConstraintLocation,
184) -> Option<[u8; 16]> {
185 match location {
186 ConstraintLocation::ColumnForeignKey(index) => columns[index]
187 .references
188 .as_ref()
189 .and_then(|reference| reference.object_id),
190 ConstraintLocation::TableForeignKey(index) => constraints.foreign_keys[index].object_id,
191 _ => None,
192 }
193}
194
195pub trait ConstraintTypeReferrers {
196 fn try_referrers_to(
197 &self,
198 table: &str,
199 ) -> Result<Vec<(String, ForeignKey)>, crate::assignment::columns::ColumnCatalogError>;
200}
201pub struct ConstraintTypeContext<'a> {
202 pub foreign_keys: crate::schema::foreign_keys::ForeignKeyDefinitionContext<'a>,
203 pub referrers: &'a dyn ConstraintTypeReferrers,
204}
205fn ddl_storage_error(
206 action: &str,
207 error: crate::assignment::columns::ColumnCatalogError,
208) -> SQLError {
209 crate::catalog::errors::storage_error(action, error.as_ref())
210}
211pub fn validate_altered_constraint_column_types(
212 context: &ConstraintTypeContext<'_>,
213 table: &str,
214 candidate_columns: &[crate::ast::ColumnDef],
215 key_constraints: &[crate::ast::TableKeyConstraint],
216 foreign_keys: &[ForeignKey],
217) -> Result<(), SQLError> {
218 for constraint in key_constraints
219 .iter()
220 .filter(|constraint| constraint.without_overlaps)
221 {
222 let Some(period_column) = constraint.columns.last() else {
223 return Err(SQLError::Internal(
224 "WITHOUT OVERLAPS constraint has no period column".into(),
225 ));
226 };
227 let period_type = candidate_columns
228 .iter()
229 .find(|column| column.name == *period_column)
230 .map(|column| &column.ty)
231 .ok_or_else(|| SQLError::UnknownColumn(format!("{table}.{period_column}")))?;
232 if !matches!(
233 period_type,
234 ColumnType::Range(_) | ColumnType::Multirange(_)
235 ) {
236 return Err(SQLError::Routine {
237 sqlstate: "42804".into(),
238 message: format!(
239 "column \"{period_column}\" in WITHOUT OVERLAPS is not a range or multirange type"
240 ),
241 });
242 }
243 }
244
245 for foreign_key in foreign_keys.iter().filter(|foreign_key| foreign_key.period) {
246 let (parent_name, parent_columns, parent_keys) =
247 crate::schema::foreign_keys::resolve_foreign_key_parent(
248 &context.foreign_keys,
249 &foreign_key.ref_table,
250 )?;
251 let parent_columns = if parent_name == table {
252 candidate_columns
253 } else {
254 parent_columns.as_slice()
255 };
256 crate::schema::constraints::validate_foreign_key_definition(
257 table,
258 candidate_columns,
259 &parent_name,
260 parent_columns,
261 &parent_keys,
262 foreign_key,
263 )?;
264 }
265
266 for (child_table, foreign_key) in context
267 .referrers
268 .try_referrers_to(table)
269 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?
270 .into_iter()
271 .filter(|(_, foreign_key)| foreign_key.period)
272 {
273 let child_columns = if child_table == table {
274 candidate_columns.to_vec()
275 } else {
276 context
277 .foreign_keys
278 .columns
279 .try_describe_table(&child_table)
280 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?
281 .ok_or_else(|| SQLError::UnknownTable(child_table.clone()))?
282 };
283 crate::schema::constraints::validate_foreign_key_definition(
284 &child_table,
285 &child_columns,
286 table,
287 candidate_columns,
288 key_constraints,
289 &foreign_key,
290 )?;
291 }
292 Ok(())
293}
294
295pub struct ConstraintAlterOptions {
296 pub enforceability: Option<bool>,
297 pub deferrability: Option<(bool, bool)>,
298 pub no_inherit: Option<bool>,
299 pub ancestor: Option<ConstraintAncestor>,
301}
302
303#[derive(Debug, Clone, PartialEq, Eq)]
305pub struct ConstraintAncestor {
306 pub name: String,
307 pub table: String,
308}
309
310pub fn derived_constraint_alteration(
312 table: &str,
313 name: &str,
314 ancestor: &ConstraintAncestor,
315) -> SQLError {
316 let local = |table: &str| match uqa_core::RelationIdentity::from_legacy_name(table) {
317 Ok(relation) => Ok(relation.name),
318 Err(error) => Err(SQLError::Internal(error)),
319 };
320 let (relation, ancestor_relation) = match (local(table), local(&ancestor.table)) {
321 (Ok(relation), Ok(ancestor_relation)) => (relation, ancestor_relation),
322 (Err(error), _) | (_, Err(error)) => return error,
323 };
324 SQLError::Diagnostic {
325 sqlstate: "55000".into(),
326 message: format!("cannot alter constraint \"{name}\" on relation \"{relation}\""),
327 detail: Some(format!(
328 "Constraint \"{name}\" is derived from constraint \"{}\" of relation \"{ancestor_relation}\".",
329 ancestor.name
330 )),
331 hint: Some("You may alter the constraint it derives from instead.".into()),
332 }
333}
334pub struct ConstraintAlterEffects {
335 pub recreated_foreign_key: Option<ForeignKey>,
336 pub validate_after_publish: bool,
337 pub changed: bool,
339}
340fn ensure_alteration_applies(
342 location: ConstraintLocation,
343 relation: &str,
344 name: &str,
345 enforceability: bool,
346 deferrability: bool,
347 no_inherit: bool,
348) -> Result<(), SQLError> {
349 let is_foreign_key = matches!(
350 location,
351 ConstraintLocation::ColumnForeignKey(_)
352 | ConstraintLocation::TableForeignKey(_)
353 | ConstraintLocation::ReferencedPartition(..)
354 );
355 if enforceability && !is_foreign_key {
356 return Err(constraint_error(
357 "42809",
358 format!(
359 "cannot alter enforceability of constraint \"{name}\" of relation \"{relation}\""
360 ),
361 ));
362 }
363 if deferrability && !is_foreign_key {
364 return Err(constraint_error(
365 "42809",
366 format!(
367 "constraint \"{name}\" of relation \"{relation}\" is not a foreign key constraint"
368 ),
369 ));
370 }
371 if no_inherit && !matches!(location, ConstraintLocation::NotNull(_)) {
372 return Err(constraint_error(
373 "42809",
374 format!(
375 "constraint \"{name}\" of relation \"{relation}\" is not a not-null constraint"
376 ),
377 ));
378 }
379 Ok(())
380}
381
382pub fn apply_constraint_alteration(
383 table: &str,
384 name: &str,
385 columns: &mut [crate::ast::ColumnDef],
386 constraints: &mut crate::ast::TableConstraintSet,
387 options: ConstraintAlterOptions,
388) -> Result<ConstraintAlterEffects, SQLError> {
389 let ConstraintAlterOptions {
390 enforceability,
391 deferrability,
392 no_inherit,
393 ancestor,
394 } = options;
395 let relation = uqa_core::RelationIdentity::from_legacy_name(table)
397 .map_err(SQLError::Internal)?
398 .name;
399 let location = find_constraint(columns, constraints, name).ok_or_else(|| {
400 constraint_error(
401 "42704",
402 format!("constraint \"{name}\" of relation \"{relation}\" does not exist"),
403 )
404 })?;
405 ensure_alteration_applies(
406 location,
407 &relation,
408 name,
409 enforceability.is_some(),
410 deferrability.is_some(),
411 no_inherit.is_some(),
412 )?;
413 let ancestor = match location {
415 ConstraintLocation::ReferencedPartition(foreign_key, _) => foreign_key
416 .derived(columns, constraints)
417 .map(|(foreign_key, _)| ConstraintAncestor {
418 name: foreign_key.to_string(),
419 table: table.to_string(),
420 }),
421 _ => ancestor,
422 };
423 if let Some(ancestor) = &ancestor {
424 return Err(derived_constraint_alteration(table, name, ancestor));
425 }
426 let recreated_foreign_key = if enforceability == Some(true) {
427 match location {
428 ConstraintLocation::ColumnForeignKey(index) => columns[index]
429 .references
430 .as_ref()
431 .filter(|foreign_key| !foreign_key.enforced)
432 .map(|foreign_key| column_foreign_key(&columns[index], foreign_key)),
433 ConstraintLocation::TableForeignKey(index) => constraints
434 .foreign_keys
435 .get(index)
436 .filter(|foreign_key| !foreign_key.enforced)
437 .cloned(),
438 ConstraintLocation::NotNull(_)
439 | ConstraintLocation::ColumnCheck(_)
440 | ConstraintLocation::TableCheck(_)
441 | ConstraintLocation::Key(_)
442 | ConstraintLocation::ReferencedPartition(..) => None,
443 }
444 } else {
445 None
446 };
447 let mut validate_after_publish = false;
448 let mut changed = false;
449 match location {
450 ConstraintLocation::NotNull(index) => {
451 if let Some(no_inherit) = no_inherit {
452 changed = columns[index].not_null_no_inherit != no_inherit;
453 columns[index].not_null_no_inherit = no_inherit;
454 }
455 }
456 ConstraintLocation::ColumnForeignKey(index) => {
457 let effects =
458 crate::schema::inheritance::foreign_keys::DeclaredForeignKey::Column(index)
459 .alter_with_effects(columns, constraints, enforceability, deferrability);
460 changed = effects.changed;
461 validate_after_publish = effects.became_enforced;
462 }
463 ConstraintLocation::TableForeignKey(index) => {
464 let effects =
465 crate::schema::inheritance::foreign_keys::DeclaredForeignKey::Table(index)
466 .alter_with_effects(columns, constraints, enforceability, deferrability);
467 changed = effects.changed;
468 validate_after_publish = effects.became_enforced;
469 }
470 ConstraintLocation::ColumnCheck(_)
471 | ConstraintLocation::TableCheck(_)
472 | ConstraintLocation::Key(_)
473 | ConstraintLocation::ReferencedPartition(..) => {}
474 }
475 Ok(ConstraintAlterEffects {
476 recreated_foreign_key,
477 validate_after_publish,
478 changed,
479 })
480}