uqa_sql/schema/constraint_changes/
not_null_removal.rs1use super::constraint_error;
10use crate::{
11 ast::{ColumnDef, TableConstraintSet, TableKeyConstraintKind},
12 SQLError,
13};
14
15pub fn validate_constraint_removal(
16 table: &str,
17 column: &ColumnDef,
18 constraints: &TableConstraintSet,
19) -> Result<(), SQLError> {
20 if column.primary_key
21 || constraints.key_constraints.iter().any(|key| {
22 key.kind == TableKeyConstraintKind::PrimaryKey && key.columns.contains(&column.name)
23 })
24 {
25 return Err(constraint_error(
26 "42P16",
27 format!("column \"{}\" is in a primary key", column.name),
28 ));
29 }
30 reject_identity(table, column, "55000")
31}
32
33pub fn validate_column_removal(
35 table: &str,
36 column: &ColumnDef,
37 parent_not_null: bool,
38) -> Result<(), SQLError> {
39 if !column.not_null {
40 return Ok(());
41 }
42 reject_identity(table, column, "42601")?;
43 if parent_not_null {
44 return Err(constraint_error(
45 "42P16",
46 format!(
47 "column \"{}\" is marked NOT NULL in parent table",
48 column.name
49 ),
50 ));
51 }
52 Ok(())
53}
54
55fn reject_identity(table: &str, column: &ColumnDef, sqlstate: &str) -> Result<(), SQLError> {
56 if !column
57 .auto_increment
58 .as_ref()
59 .is_some_and(|definition| definition.is_identity())
60 {
61 return Ok(());
62 }
63 let relation =
64 uqa_core::RelationIdentity::from_legacy_name(table).map_err(SQLError::Internal)?;
65 Err(constraint_error(
66 sqlstate,
67 format!(
68 "column \"{}\" of relation \"{}\" is an identity column",
69 column.name, relation.name
70 ),
71 ))
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn identity_not_null_removal_distinguishes_column_and_constraint_syntax() {
80 let crate::Statement::CreateTable(table) = crate::compile(
81 "CREATE TABLE t(v integer GENERATED ALWAYS AS IDENTITY CONSTRAINT nn NOT NULL)",
82 )
83 .unwrap()
84 .remove(0) else {
85 panic!("table");
86 };
87 let column = &table.columns[0];
88 let constraints = TableConstraintSet::default();
89 assert_eq!(
90 validate_constraint_removal("public.t", column, &constraints)
91 .unwrap_err()
92 .sqlstate(),
93 Some("55000")
94 );
95 assert_eq!(
96 validate_column_removal("public.t", column, false)
97 .unwrap_err()
98 .sqlstate(),
99 Some("42601")
100 );
101 let mut keyed = column.clone();
102 keyed.primary_key = true;
103 assert_eq!(
104 validate_constraint_removal("public.t", &keyed, &constraints)
105 .unwrap_err()
106 .sqlstate(),
107 Some("42P16")
108 );
109 }
110
111 #[test]
112 fn serial_and_nullable_columns_do_not_inherit_identity_protection() {
113 let crate::Statement::CreateTable(table) =
114 crate::compile("CREATE TABLE t(v serial CONSTRAINT nn NOT NULL, nullable integer)")
115 .unwrap()
116 .remove(0)
117 else {
118 panic!("table");
119 };
120 for column in &table.columns {
121 validate_constraint_removal("public.t", column, &TableConstraintSet::default())
122 .unwrap();
123 validate_column_removal("public.t", column, false).unwrap();
124 }
125 }
126}