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