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}
29
30pub fn constraint_error(sqlstate: &str, message: impl Into<String>) -> SQLError {
31 SQLError::Routine {
32 sqlstate: sqlstate.into(),
33 message: message.into(),
34 }
35}
36
37pub fn find_constraint(
38 columns: &[crate::ast::ColumnDef],
39 constraints: &crate::ast::TableConstraintSet,
40 name: &str,
41) -> Option<ConstraintLocation> {
42 names::ConstraintNames::from_definition(columns, constraints)
43 .entries()
44 .find(|constraint| constraint.name == name)
45 .map(|constraint| constraint.location)
46}
47
48pub fn ensure_constraint_name_available(
49 columns: &[crate::ast::ColumnDef],
50 constraints: &crate::ast::TableConstraintSet,
51 name: Option<&str>,
52 table: &str,
53) -> Result<(), SQLError> {
54 if let Some(name) = name.filter(|name| find_constraint(columns, constraints, name).is_some()) {
55 return Err(constraint_error(
56 "42710",
57 format!("constraint \"{name}\" for relation \"{table}\" already exists"),
58 ));
59 }
60 Ok(())
61}
62
63pub fn ensure_not_null_inheritable(
64 table: &str,
65 column: &crate::ast::ColumnDef,
66 sqlstate: &str,
67) -> Result<(), SQLError> {
68 if column.not_null_no_inherit {
69 let relation = uqa_core::RelationIdentity::from_legacy_name(table)
70 .map_err(|error| SQLError::Internal(format!("resolve NOT NULL relation: {error}")))?;
71 let name = column.not_null_name.as_deref().unwrap_or("<unnamed>");
72 return Err(constraint_error(
73 sqlstate,
74 format!(
75 "cannot change NO INHERIT status of NOT NULL constraint \"{name}\" on relation \"{}\"",
76 relation.name,
77 ),
78 ));
79 }
80 Ok(())
81}
82
83pub fn take_column_check(column: &mut crate::ast::ColumnDef) -> Option<TableCheck> {
84 let check = TableCheck {
85 expr: column.check.take()?,
86 catalog_oid: column.check_catalog_oid.take(),
87 name: column.check_name.take(),
88 object_id: column.check_object_id.take(),
89 is_local: column.check_is_local,
90 enforced: column.check_enforced,
91 validated: column.check_validated,
92 no_inherit: column.check_no_inherit,
93 partition_constraint: None,
94 };
95 column.check_is_local = true;
96 column.check_enforced = true;
97 column.check_validated = true;
98 column.check_no_inherit = false;
99 Some(check)
100}
101
102pub fn foreign_key_object_id(
103 columns: &[crate::ast::ColumnDef],
104 constraints: &crate::ast::TableConstraintSet,
105 location: ConstraintLocation,
106) -> Option<[u8; 16]> {
107 match location {
108 ConstraintLocation::ColumnForeignKey(index) => columns[index]
109 .references
110 .as_ref()
111 .and_then(|reference| reference.object_id),
112 ConstraintLocation::TableForeignKey(index) => constraints.foreign_keys[index].object_id,
113 _ => None,
114 }
115}
116
117pub trait ConstraintTypeReferrers {
118 fn try_referrers_to(
119 &self,
120 table: &str,
121 ) -> Result<Vec<(String, ForeignKey)>, crate::assignment::columns::ColumnCatalogError>;
122}
123pub struct ConstraintTypeContext<'a> {
124 pub foreign_keys: crate::schema::foreign_keys::ForeignKeyDefinitionContext<'a>,
125 pub referrers: &'a dyn ConstraintTypeReferrers,
126}
127fn ddl_storage_error(
128 action: &str,
129 error: crate::assignment::columns::ColumnCatalogError,
130) -> SQLError {
131 crate::catalog::errors::storage_error(action, error.as_ref())
132}
133pub fn validate_altered_constraint_column_types(
134 context: &ConstraintTypeContext<'_>,
135 table: &str,
136 candidate_columns: &[crate::ast::ColumnDef],
137 key_constraints: &[crate::ast::TableKeyConstraint],
138 foreign_keys: &[ForeignKey],
139) -> Result<(), SQLError> {
140 for constraint in key_constraints
141 .iter()
142 .filter(|constraint| constraint.without_overlaps)
143 {
144 let Some(period_column) = constraint.columns.last() else {
145 return Err(SQLError::Internal(
146 "WITHOUT OVERLAPS constraint has no period column".into(),
147 ));
148 };
149 let period_type = candidate_columns
150 .iter()
151 .find(|column| column.name == *period_column)
152 .map(|column| &column.ty)
153 .ok_or_else(|| SQLError::UnknownColumn(format!("{table}.{period_column}")))?;
154 if !matches!(
155 period_type,
156 ColumnType::Range(_) | ColumnType::Multirange(_)
157 ) {
158 return Err(SQLError::Routine {
159 sqlstate: "42804".into(),
160 message: format!(
161 "column \"{period_column}\" in WITHOUT OVERLAPS is not a range or multirange type"
162 ),
163 });
164 }
165 }
166
167 for foreign_key in foreign_keys.iter().filter(|foreign_key| foreign_key.period) {
168 let (parent_name, parent_columns, parent_keys) =
169 crate::schema::foreign_keys::resolve_foreign_key_parent(
170 &context.foreign_keys,
171 &foreign_key.ref_table,
172 )?;
173 let parent_columns = if parent_name == table {
174 candidate_columns
175 } else {
176 parent_columns.as_slice()
177 };
178 crate::schema::constraints::validate_foreign_key_definition(
179 table,
180 candidate_columns,
181 &parent_name,
182 parent_columns,
183 &parent_keys,
184 foreign_key,
185 )?;
186 }
187
188 for (child_table, foreign_key) in context
189 .referrers
190 .try_referrers_to(table)
191 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?
192 .into_iter()
193 .filter(|(_, foreign_key)| foreign_key.period)
194 {
195 let child_columns = if child_table == table {
196 candidate_columns.to_vec()
197 } else {
198 context
199 .foreign_keys
200 .columns
201 .try_describe_table(&child_table)
202 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?
203 .ok_or_else(|| SQLError::UnknownTable(child_table.clone()))?
204 };
205 crate::schema::constraints::validate_foreign_key_definition(
206 &child_table,
207 &child_columns,
208 table,
209 candidate_columns,
210 key_constraints,
211 &foreign_key,
212 )?;
213 }
214 Ok(())
215}
216
217pub struct ConstraintAlterOptions {
218 pub enforceability: Option<bool>,
219 pub deferrability: Option<(bool, bool)>,
220 pub no_inherit: Option<bool>,
221}
222pub struct ConstraintAlterEffects {
223 pub recreated_foreign_key: Option<ForeignKey>,
224 pub validate_after_publish: bool,
225}
226#[expect(
227 clippy::too_many_lines,
228 reason = "preserves ordered constraint alteration rules"
229)]
230pub fn apply_constraint_alteration(
231 table: &str,
232 name: &str,
233 columns: &mut [crate::ast::ColumnDef],
234 constraints: &mut crate::ast::TableConstraintSet,
235 options: ConstraintAlterOptions,
236) -> Result<ConstraintAlterEffects, SQLError> {
237 let ConstraintAlterOptions {
238 enforceability,
239 deferrability,
240 no_inherit,
241 } = options;
242 let location = find_constraint(columns, constraints, name).ok_or_else(|| {
243 constraint_error(
244 "42704",
245 format!("constraint \"{name}\" of relation \"{table}\" does not exist"),
246 )
247 })?;
248 let is_foreign_key = matches!(
249 location,
250 ConstraintLocation::ColumnForeignKey(_) | ConstraintLocation::TableForeignKey(_)
251 );
252 let is_not_null = matches!(location, ConstraintLocation::NotNull(_));
253 if enforceability.is_some() && !is_foreign_key {
254 return Err(constraint_error(
255 "42809",
256 format!("cannot alter enforceability of constraint \"{name}\" of relation \"{table}\""),
257 ));
258 }
259 if deferrability.is_some() && !is_foreign_key {
260 return Err(constraint_error(
261 "42809",
262 format!(
263 "constraint \"{name}\" of relation \"{table}\" is not a foreign key constraint"
264 ),
265 ));
266 }
267 if no_inherit.is_some() && !is_not_null {
268 return Err(constraint_error(
269 "42809",
270 format!("constraint \"{name}\" of relation \"{table}\" is not a not-null constraint"),
271 ));
272 }
273 let recreated_foreign_key = if enforceability == Some(true) {
274 match location {
275 ConstraintLocation::ColumnForeignKey(index) => columns[index]
276 .references
277 .as_ref()
278 .filter(|foreign_key| !foreign_key.enforced)
279 .map(|foreign_key| column_foreign_key(&columns[index], foreign_key)),
280 ConstraintLocation::TableForeignKey(index) => constraints
281 .foreign_keys
282 .get(index)
283 .filter(|foreign_key| !foreign_key.enforced)
284 .cloned(),
285 ConstraintLocation::NotNull(_)
286 | ConstraintLocation::ColumnCheck(_)
287 | ConstraintLocation::TableCheck(_)
288 | ConstraintLocation::Key(_) => None,
289 }
290 } else {
291 None
292 };
293 let mut validate_after_publish = false;
294 match location {
295 ConstraintLocation::NotNull(index) => {
296 if let Some(no_inherit) = no_inherit {
297 columns[index].not_null_no_inherit = no_inherit;
298 }
299 }
300 ConstraintLocation::ColumnForeignKey(index) => {
301 let foreign_key = columns[index]
302 .references
303 .as_mut()
304 .ok_or_else(|| SQLError::Internal("column FOREIGN KEY disappeared".into()))?;
305 if let Some(enforced) = enforceability {
306 if !enforced {
307 foreign_key.enforced = false;
308 foreign_key.validated = false;
309 } else if !foreign_key.enforced {
310 foreign_key.enforced = true;
311 foreign_key.validated = false;
312 validate_after_publish = true;
313 }
314 }
315 if let Some((deferrable, initially_deferred)) = deferrability {
316 foreign_key.deferrable = deferrable;
317 foreign_key.initially_deferred = initially_deferred;
318 }
319 }
320 ConstraintLocation::TableForeignKey(index) => {
321 let foreign_key = &mut constraints.foreign_keys[index];
322 if let Some(enforced) = enforceability {
323 if !enforced {
324 foreign_key.enforced = false;
325 foreign_key.validated = false;
326 } else if !foreign_key.enforced {
327 foreign_key.enforced = true;
328 foreign_key.validated = false;
329 validate_after_publish = true;
330 }
331 }
332 if let Some((deferrable, initially_deferred)) = deferrability {
333 foreign_key.deferrable = deferrable;
334 foreign_key.initially_deferred = initially_deferred;
335 }
336 }
337 ConstraintLocation::ColumnCheck(_)
338 | ConstraintLocation::TableCheck(_)
339 | ConstraintLocation::Key(_) => {}
340 }
341 Ok(ConstraintAlterEffects {
342 recreated_foreign_key,
343 validate_after_publish,
344 })
345}