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 foreign_key_object_id(
166 columns: &[crate::ast::ColumnDef],
167 constraints: &crate::ast::TableConstraintSet,
168 location: ConstraintLocation,
169) -> Option<[u8; 16]> {
170 match location {
171 ConstraintLocation::ColumnForeignKey(index) => columns[index]
172 .references
173 .as_ref()
174 .and_then(|reference| reference.object_id),
175 ConstraintLocation::TableForeignKey(index) => constraints.foreign_keys[index].object_id,
176 _ => None,
177 }
178}
179
180pub trait ConstraintTypeReferrers {
181 fn try_referrers_to(
182 &self,
183 table: &str,
184 ) -> Result<Vec<(String, ForeignKey)>, crate::assignment::columns::ColumnCatalogError>;
185}
186pub struct ConstraintTypeContext<'a> {
187 pub foreign_keys: crate::schema::foreign_keys::ForeignKeyDefinitionContext<'a>,
188 pub referrers: &'a dyn ConstraintTypeReferrers,
189}
190fn ddl_storage_error(
191 action: &str,
192 error: crate::assignment::columns::ColumnCatalogError,
193) -> SQLError {
194 crate::catalog::errors::storage_error(action, error.as_ref())
195}
196pub fn validate_altered_constraint_column_types(
197 context: &ConstraintTypeContext<'_>,
198 table: &str,
199 candidate_columns: &[crate::ast::ColumnDef],
200 key_constraints: &[crate::ast::TableKeyConstraint],
201 foreign_keys: &[ForeignKey],
202) -> Result<(), SQLError> {
203 for constraint in key_constraints
204 .iter()
205 .filter(|constraint| constraint.without_overlaps)
206 {
207 let Some(period_column) = constraint.columns.last() else {
208 return Err(SQLError::Internal(
209 "WITHOUT OVERLAPS constraint has no period column".into(),
210 ));
211 };
212 let period_type = candidate_columns
213 .iter()
214 .find(|column| column.name == *period_column)
215 .map(|column| &column.ty)
216 .ok_or_else(|| SQLError::UnknownColumn(format!("{table}.{period_column}")))?;
217 if !matches!(
218 period_type,
219 ColumnType::Range(_) | ColumnType::Multirange(_)
220 ) {
221 return Err(SQLError::Routine {
222 sqlstate: "42804".into(),
223 message: format!(
224 "column \"{period_column}\" in WITHOUT OVERLAPS is not a range or multirange type"
225 ),
226 });
227 }
228 }
229
230 for foreign_key in foreign_keys.iter().filter(|foreign_key| foreign_key.period) {
231 let (parent_name, parent_columns, parent_keys) =
232 crate::schema::foreign_keys::resolve_foreign_key_parent(
233 &context.foreign_keys,
234 &foreign_key.ref_table,
235 )?;
236 let parent_columns = if parent_name == table {
237 candidate_columns
238 } else {
239 parent_columns.as_slice()
240 };
241 crate::schema::constraints::validate_foreign_key_definition(
242 table,
243 candidate_columns,
244 &parent_name,
245 parent_columns,
246 &parent_keys,
247 foreign_key,
248 )?;
249 }
250
251 for (child_table, foreign_key) in context
252 .referrers
253 .try_referrers_to(table)
254 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?
255 .into_iter()
256 .filter(|(_, foreign_key)| foreign_key.period)
257 {
258 let child_columns = if child_table == table {
259 candidate_columns.to_vec()
260 } else {
261 context
262 .foreign_keys
263 .columns
264 .try_describe_table(&child_table)
265 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?
266 .ok_or_else(|| SQLError::UnknownTable(child_table.clone()))?
267 };
268 crate::schema::constraints::validate_foreign_key_definition(
269 &child_table,
270 &child_columns,
271 table,
272 candidate_columns,
273 key_constraints,
274 &foreign_key,
275 )?;
276 }
277 Ok(())
278}
279
280pub struct ConstraintAlterOptions {
281 pub enforceability: Option<bool>,
282 pub deferrability: Option<(bool, bool)>,
283 pub no_inherit: Option<bool>,
284 pub ancestor: Option<ConstraintAncestor>,
286}
287
288#[derive(Debug, Clone, PartialEq, Eq)]
290pub struct ConstraintAncestor {
291 pub name: String,
292 pub table: String,
293}
294
295pub fn derived_constraint_alteration(
297 table: &str,
298 name: &str,
299 ancestor: &ConstraintAncestor,
300) -> SQLError {
301 let local = |table: &str| match uqa_core::RelationIdentity::from_legacy_name(table) {
302 Ok(relation) => Ok(relation.name),
303 Err(error) => Err(SQLError::Internal(error)),
304 };
305 let (relation, ancestor_relation) = match (local(table), local(&ancestor.table)) {
306 (Ok(relation), Ok(ancestor_relation)) => (relation, ancestor_relation),
307 (Err(error), _) | (_, Err(error)) => return error,
308 };
309 SQLError::Diagnostic {
310 sqlstate: "55000".into(),
311 message: format!("cannot alter constraint \"{name}\" on relation \"{relation}\""),
312 detail: Some(format!(
313 "Constraint \"{name}\" is derived from constraint \"{}\" of relation \"{ancestor_relation}\".",
314 ancestor.name
315 )),
316 hint: Some("You may alter the constraint it derives from instead.".into()),
317 }
318}
319pub struct ConstraintAlterEffects {
320 pub recreated_foreign_key: Option<ForeignKey>,
321 pub validate_after_publish: bool,
322}
323fn ensure_alteration_applies(
325 location: ConstraintLocation,
326 relation: &str,
327 name: &str,
328 enforceability: bool,
329 deferrability: bool,
330 no_inherit: bool,
331) -> Result<(), SQLError> {
332 let is_foreign_key = matches!(
333 location,
334 ConstraintLocation::ColumnForeignKey(_)
335 | ConstraintLocation::TableForeignKey(_)
336 | ConstraintLocation::ReferencedPartition(..)
337 );
338 if enforceability && !is_foreign_key {
339 return Err(constraint_error(
340 "42809",
341 format!(
342 "cannot alter enforceability of constraint \"{name}\" of relation \"{relation}\""
343 ),
344 ));
345 }
346 if deferrability && !is_foreign_key {
347 return Err(constraint_error(
348 "42809",
349 format!(
350 "constraint \"{name}\" of relation \"{relation}\" is not a foreign key constraint"
351 ),
352 ));
353 }
354 if no_inherit && !matches!(location, ConstraintLocation::NotNull(_)) {
355 return Err(constraint_error(
356 "42809",
357 format!(
358 "constraint \"{name}\" of relation \"{relation}\" is not a not-null constraint"
359 ),
360 ));
361 }
362 Ok(())
363}
364
365pub fn apply_constraint_alteration(
366 table: &str,
367 name: &str,
368 columns: &mut [crate::ast::ColumnDef],
369 constraints: &mut crate::ast::TableConstraintSet,
370 options: ConstraintAlterOptions,
371) -> Result<ConstraintAlterEffects, SQLError> {
372 let ConstraintAlterOptions {
373 enforceability,
374 deferrability,
375 no_inherit,
376 ancestor,
377 } = options;
378 let relation = uqa_core::RelationIdentity::from_legacy_name(table)
380 .map_err(SQLError::Internal)?
381 .name;
382 let location = find_constraint(columns, constraints, name).ok_or_else(|| {
383 constraint_error(
384 "42704",
385 format!("constraint \"{name}\" of relation \"{relation}\" does not exist"),
386 )
387 })?;
388 ensure_alteration_applies(
389 location,
390 &relation,
391 name,
392 enforceability.is_some(),
393 deferrability.is_some(),
394 no_inherit.is_some(),
395 )?;
396 let ancestor = match location {
398 ConstraintLocation::ReferencedPartition(foreign_key, _) => foreign_key
399 .derived(columns, constraints)
400 .map(|(foreign_key, _)| ConstraintAncestor {
401 name: foreign_key.to_string(),
402 table: table.to_string(),
403 }),
404 _ => ancestor,
405 };
406 if let Some(ancestor) = &ancestor {
407 return Err(derived_constraint_alteration(table, name, ancestor));
408 }
409 let recreated_foreign_key = if enforceability == Some(true) {
410 match location {
411 ConstraintLocation::ColumnForeignKey(index) => columns[index]
412 .references
413 .as_ref()
414 .filter(|foreign_key| !foreign_key.enforced)
415 .map(|foreign_key| column_foreign_key(&columns[index], foreign_key)),
416 ConstraintLocation::TableForeignKey(index) => constraints
417 .foreign_keys
418 .get(index)
419 .filter(|foreign_key| !foreign_key.enforced)
420 .cloned(),
421 ConstraintLocation::NotNull(_)
422 | ConstraintLocation::ColumnCheck(_)
423 | ConstraintLocation::TableCheck(_)
424 | ConstraintLocation::Key(_)
425 | ConstraintLocation::ReferencedPartition(..) => None,
426 }
427 } else {
428 None
429 };
430 let mut validate_after_publish = false;
431 match location {
432 ConstraintLocation::NotNull(index) => {
433 if let Some(no_inherit) = no_inherit {
434 columns[index].not_null_no_inherit = no_inherit;
435 }
436 }
437 ConstraintLocation::ColumnForeignKey(index) => {
438 validate_after_publish =
439 crate::schema::inheritance::foreign_keys::DeclaredForeignKey::Column(index).alter(
440 columns,
441 constraints,
442 enforceability,
443 deferrability,
444 );
445 }
446 ConstraintLocation::TableForeignKey(index) => {
447 validate_after_publish =
448 crate::schema::inheritance::foreign_keys::DeclaredForeignKey::Table(index).alter(
449 columns,
450 constraints,
451 enforceability,
452 deferrability,
453 );
454 }
455 ConstraintLocation::ColumnCheck(_)
456 | ConstraintLocation::TableCheck(_)
457 | ConstraintLocation::Key(_)
458 | ConstraintLocation::ReferencedPartition(..) => {}
459 }
460 Ok(ConstraintAlterEffects {
461 recreated_foreign_key,
462 validate_after_publish,
463 })
464}