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 undefined_relation_column(table: &str, column: &str) -> SQLError {
25 match uqa_core::RelationIdentity::from_legacy_name(table) {
26 Ok(relation) => SQLError::Routine {
27 sqlstate: "42703".into(),
28 message: format!(
29 "column \"{column}\" of relation \"{}\" does not exist",
30 relation.name
31 ),
32 },
33 Err(error) => SQLError::Internal(format!("resolve ALTER TABLE target `{table}`: {error}")),
34 }
35}
36
37pub fn validate_postgres_relation_column_type(name: &str, ty: &ColumnType) -> Result<(), SQLError> {
38 let pseudo_type = match ty {
39 ColumnType::Void | ColumnType::AnyArray | ColumnType::Record => Some(ty.sql_name()),
40 ColumnType::Array(element)
41 if matches!(
42 element.as_ref(),
43 ColumnType::Void | ColumnType::AnyArray | ColumnType::Record
44 ) =>
45 {
46 Some(ty.sql_name())
47 }
48 _ => None,
49 };
50 if let Some(pseudo_type) = pseudo_type {
51 return Err(SQLError::Routine {
52 sqlstate: "42P16".into(),
53 message: format!("column \"{name}\" has pseudo-type {pseudo_type}"),
54 });
55 }
56 Ok(())
57}
58
59pub fn append_registered_column(
61 table: &str,
62 columns: &mut Vec<crate::ast::ColumnDef>,
63 column: crate::ast::ColumnDef,
64) -> Result<(), String> {
65 if columns.iter().any(|existing| existing.name == column.name) {
66 return Err(format!(
67 "column `{}` already exists on table `{table}`",
68 column.name
69 ));
70 }
71 columns.push(column);
72 Ok(())
73}
74
75pub fn validate_drop_column(
77 columns: &[crate::ast::ColumnDef],
78 table: &str,
79 column: &str,
80 if_exists: bool,
81) -> Result<bool, SQLError> {
82 if POSTGRES_SYSTEM_COLUMNS.contains(&column) {
83 return Err(SQLError::Routine {
84 sqlstate: "0A000".into(),
85 message: format!("cannot drop system column \"{column}\""),
86 });
87 }
88 if columns.iter().any(|definition| definition.name == column) {
89 return Ok(true);
90 }
91 if if_exists {
92 Ok(false)
93 } else {
94 Err(undefined_relation_column(table, column))
95 }
96}
97
98pub fn missing_drop_column_notice(table: &str, column: &str) -> crate::SQLNotice {
100 crate::SQLNotice::notice(format!(
101 "column \"{column}\" of relation \"{table}\" does not exist, skipping"
102 ))
103}
104
105pub fn altered_column<'a>(
107 table: &str,
108 columns: &'a [crate::ast::ColumnDef],
109 column: &str,
110) -> Result<&'a crate::ast::ColumnDef, SQLError> {
111 columns
112 .iter()
113 .find(|definition| definition.name == column)
114 .ok_or_else(|| missing_altered_column(table, column))
115}
116
117pub fn missing_altered_column(table: &str, column: &str) -> SQLError {
119 if POSTGRES_SYSTEM_COLUMNS.contains(&column) {
120 return SQLError::Routine {
121 sqlstate: "0A000".into(),
122 message: format!("cannot alter system column \"{column}\""),
123 };
124 }
125 undefined_relation_column(table, column)
126}
127
128pub fn validate_default_change(
130 table: &str,
131 column: &crate::ast::ColumnDef,
132 setting: bool,
133) -> Result<(), SQLError> {
134 let relation = uqa_core::RelationIdentity::from_legacy_name(table).map_err(|error| {
135 SQLError::Internal(format!("resolve ALTER TABLE target `{table}`: {error}"))
136 })?;
137 if column
138 .auto_increment
139 .as_ref()
140 .is_some_and(crate::ast::AutoIncrement::is_identity)
141 {
142 return Err(SQLError::Diagnostic {
143 sqlstate: "42601".into(),
144 message: format!(
145 "column \"{}\" of relation \"{}\" is an identity column",
146 column.name, relation.name
147 ),
148 detail: None,
149 hint: (!setting)
150 .then(|| "Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead.".into()),
151 });
152 }
153 if let Some(generated) = &column.generated {
154 let hint = if setting {
155 Some("Use ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION instead.")
156 } else if generated.kind == crate::ast::GeneratedColumnKind::Stored {
157 Some("Use ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION instead.")
158 } else {
159 None
160 };
161 return Err(SQLError::Diagnostic {
162 sqlstate: "42601".into(),
163 message: format!(
164 "column \"{}\" of relation \"{}\" is a generated column",
165 column.name, relation.name
166 ),
167 detail: None,
168 hint: hint.map(Into::into),
169 });
170 }
171 Ok(())
172}
173
174pub mod addition;
175
176pub mod publication;
177
178pub mod alteration;
179
180pub mod removal_metadata;
181
182pub mod type_target;
183
184pub mod type_transform;