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