uqa_sql/schema/
columns.rs1use crate::{ColumnType, SQLError};
10
11pub const POSTGRES_SYSTEM_COLUMNS: [&str; 6] = ["tableoid", "xmin", "cmin", "xmax", "cmax", "ctid"];
12
13pub fn validate_postgres_column_name(name: &str) -> Result<(), SQLError> {
14 if POSTGRES_SYSTEM_COLUMNS.contains(&name) {
15 return Err(SQLError::Routine {
16 sqlstate: "42701".into(),
17 message: format!("column name \"{name}\" conflicts with a system column name"),
18 });
19 }
20 Ok(())
21}
22
23pub fn renamed_column_position<'a>(
25 relation: &str,
26 columns: impl IntoIterator<Item = &'a str>,
27 from: &str,
28 to: &str,
29 has_system_columns: bool,
30) -> Result<usize, SQLError> {
31 let mut position = None;
32 let mut duplicate = false;
33 for (index, column) in columns.into_iter().enumerate() {
34 if column == from {
35 position = Some(index);
36 }
37 duplicate |= column == to;
38 }
39 let position = position.ok_or_else(|| SQLError::Routine {
40 sqlstate: "42703".into(),
41 message: format!("column \"{from}\" does not exist"),
42 })?;
43 if has_system_columns {
44 validate_postgres_column_name(to)?;
45 }
46 if duplicate {
47 return Err(SQLError::Routine {
48 sqlstate: "42701".into(),
49 message: format!("column \"{to}\" of relation \"{relation}\" already exists"),
50 });
51 }
52 Ok(position)
53}
54
55pub fn undefined_relation_column(table: &str, column: &str) -> SQLError {
57 match uqa_core::RelationIdentity::from_legacy_name(table) {
58 Ok(relation) => SQLError::Routine {
59 sqlstate: "42703".into(),
60 message: format!(
61 "column \"{column}\" of relation \"{}\" does not exist",
62 relation.name
63 ),
64 },
65 Err(error) => SQLError::Internal(format!("resolve ALTER TABLE target `{table}`: {error}")),
66 }
67}
68
69pub fn validate_postgres_relation_column_type(name: &str, ty: &ColumnType) -> Result<(), SQLError> {
70 let pseudo_type = match ty {
71 ColumnType::Void | ColumnType::AnyArray | ColumnType::Record => Some(ty.sql_name()),
72 ColumnType::Array(element)
73 if matches!(
74 element.as_ref(),
75 ColumnType::Void | ColumnType::AnyArray | ColumnType::Record
76 ) =>
77 {
78 Some(ty.sql_name())
79 }
80 _ => None,
81 };
82 if let Some(pseudo_type) = pseudo_type {
83 return Err(SQLError::Routine {
84 sqlstate: "42P16".into(),
85 message: format!("column \"{name}\" has pseudo-type {pseudo_type}"),
86 });
87 }
88 Ok(())
89}
90
91pub fn append_registered_column(
93 table: &str,
94 columns: &mut Vec<crate::ast::ColumnDef>,
95 column: crate::ast::ColumnDef,
96) -> Result<(), String> {
97 if columns.iter().any(|existing| existing.name == column.name) {
98 return Err(format!(
99 "column `{}` already exists on table `{table}`",
100 column.name
101 ));
102 }
103 columns.push(column);
104 Ok(())
105}
106
107pub fn validate_drop_column(
109 columns: &[crate::ast::ColumnDef],
110 table: &str,
111 column: &str,
112 if_exists: bool,
113) -> Result<bool, SQLError> {
114 if POSTGRES_SYSTEM_COLUMNS.contains(&column) {
115 return Err(SQLError::Routine {
116 sqlstate: "0A000".into(),
117 message: format!("cannot drop system column \"{column}\""),
118 });
119 }
120 if columns.iter().any(|definition| definition.name == column) {
121 return Ok(true);
122 }
123 if if_exists {
124 Ok(false)
125 } else {
126 Err(undefined_relation_column(table, column))
127 }
128}
129
130pub fn missing_drop_column_notice(table: &str, column: &str) -> crate::SQLNotice {
132 crate::SQLNotice::notice(format!(
133 "column \"{column}\" of relation \"{table}\" does not exist, skipping"
134 ))
135}
136
137pub fn altered_column<'a>(
139 table: &str,
140 columns: &'a [crate::ast::ColumnDef],
141 column: &str,
142) -> Result<&'a crate::ast::ColumnDef, SQLError> {
143 columns
144 .iter()
145 .find(|definition| definition.name == column)
146 .ok_or_else(|| missing_altered_column(table, column))
147}
148
149pub fn missing_altered_column(table: &str, column: &str) -> SQLError {
151 if POSTGRES_SYSTEM_COLUMNS.contains(&column) {
152 return SQLError::Routine {
153 sqlstate: "0A000".into(),
154 message: format!("cannot alter system column \"{column}\""),
155 };
156 }
157 undefined_relation_column(table, column)
158}
159
160pub fn validate_default_change(
162 table: &str,
163 column: &crate::ast::ColumnDef,
164 setting: bool,
165) -> Result<(), SQLError> {
166 let relation = uqa_core::RelationIdentity::from_legacy_name(table).map_err(|error| {
167 SQLError::Internal(format!("resolve ALTER TABLE target `{table}`: {error}"))
168 })?;
169 if column
170 .auto_increment
171 .as_ref()
172 .is_some_and(crate::ast::AutoIncrement::is_identity)
173 {
174 return Err(SQLError::Diagnostic {
175 sqlstate: "42601".into(),
176 message: format!(
177 "column \"{}\" of relation \"{}\" is an identity column",
178 column.name, relation.name
179 ),
180 detail: None,
181 hint: (!setting)
182 .then(|| "Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead.".into()),
183 });
184 }
185 if let Some(generated) = &column.generated {
186 let hint = if setting {
187 Some("Use ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION instead.")
188 } else if generated.kind == crate::ast::GeneratedColumnKind::Stored {
189 Some("Use ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION instead.")
190 } else {
191 None
192 };
193 return Err(SQLError::Diagnostic {
194 sqlstate: "42601".into(),
195 message: format!(
196 "column \"{}\" of relation \"{}\" is a generated column",
197 column.name, relation.name
198 ),
199 detail: None,
200 hint: hint.map(Into::into),
201 });
202 }
203 Ok(())
204}
205
206pub mod addition;
207
208pub mod publication;
209
210pub mod alteration;
211
212pub mod removal_metadata;
213
214pub mod type_target;
215
216pub mod type_transform;