1use crate::{
9 assignment::columns::{AssignmentColumnCatalog, ColumnCatalogError},
10 ast::{ColumnDef, ColumnType, Expr, GeneratedColumn, GeneratedColumnKind},
11 schema::{
12 columns::addition::AddedColumnKeys, constraint_changes::ConstraintTypeContext,
13 dependencies::registration::SchemaDependencyBindingContext,
14 },
15 SQLError,
16};
17pub trait ColumnChangeCatalog {
19 fn has_column(&self, table: &str, column: &str) -> Result<bool, ColumnCatalogError>;
20 fn column_type(
21 &self,
22 table: &str,
23 column: &str,
24 ) -> Result<Option<ColumnType>, ColumnCatalogError>;
25 fn stored_columns(&self, table: &str) -> Result<Vec<ColumnDef>, ColumnCatalogError>;
26}
27pub struct ColumnAlterAnalysisContext<'a> {
28 pub columns: &'a dyn AssignmentColumnCatalog,
29 pub keys: &'a dyn AddedColumnKeys,
30 pub state: &'a dyn ColumnChangeCatalog,
31 pub bindings: SchemaDependencyBindingContext<'a>,
32 pub constraint_types: ConstraintTypeContext<'a>,
33}
34fn ddl_storage_error(action: &str, error: ColumnCatalogError) -> SQLError {
35 crate::catalog::errors::storage_error(action, error.as_ref())
36}
37pub fn generated_columns_referencing_column(columns: &[ColumnDef], column: &str) -> Vec<String> {
38 columns
39 .iter()
40 .filter(|candidate| candidate.name != column)
41 .filter(|candidate| {
42 candidate.generated.as_ref().is_some_and(|generated| {
43 crate::schema::dependencies::schema_expr_references_column(
44 &generated.expression,
45 column,
46 )
47 })
48 })
49 .map(|candidate| candidate.name.clone())
50 .collect()
51}
52fn described_columns(
53 context: &ColumnAlterAnalysisContext<'_>,
54 table: &str,
55 action: &str,
56) -> Result<Vec<ColumnDef>, SQLError> {
57 context
58 .columns
59 .try_describe_table(table)
60 .map_err(|error| ddl_storage_error(action, error))?
61 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))
62}
63pub fn validate_column_default(
65 context: &ColumnAlterAnalysisContext<'_>,
66 table: &str,
67 column: &str,
68 default: &mut Expr,
69) -> Result<bool, SQLError> {
70 let columns = described_columns(context, table, "ALTER COLUMN SET DEFAULT")?;
71 super::validate_default_change(table, super::altered_column(table, &columns, column)?, true)?;
72 let target = context
73 .state
74 .column_type(table, column)
75 .map_err(|error| ddl_storage_error("ALTER COLUMN SET DEFAULT", error))?
76 .ok_or_else(|| super::undefined_relation_column(table, column))?;
77 let binding = context.bindings.bindings.binding_scope()?;
78 crate::schema::defaults::validate_default_expression(
79 &crate::schema::SchemaBindingContext {
80 catalog: context.bindings.schema,
81 binding: &binding.context(),
82 },
83 default,
84 &target,
85 column,
86 )
87}
88pub fn validate_default_removal(
90 context: &ColumnAlterAnalysisContext<'_>,
91 table: &str,
92 column: &str,
93) -> Result<bool, SQLError> {
94 let columns = described_columns(context, table, "ALTER COLUMN DROP DEFAULT")?;
95 let column = super::altered_column(table, &columns, column)?;
96 super::validate_default_change(table, column, false)?;
97 Ok(column.default.is_some())
98}
99fn not_generated(table: &str, column: &str) -> Result<String, SQLError> {
100 let relation = uqa_core::RelationIdentity::from_legacy_name(table).map_err(|error| {
101 SQLError::Internal(format!("resolve ALTER TABLE target `{table}`: {error}"))
102 })?;
103 Ok(format!(
104 "column \"{column}\" of relation \"{}\" is not a generated column",
105 relation.name
106 ))
107}
108pub fn analyze_generated_expression(
109 context: &ColumnAlterAnalysisContext<'_>,
110 table: &str,
111 qualifier: &str,
112 name: &str,
113 expression: Expr,
114) -> Result<(GeneratedColumn, GeneratedColumnKind), SQLError> {
115 let mut columns = context
116 .columns
117 .try_describe_table(table)
118 .map_err(|error| ddl_storage_error("ALTER COLUMN SET EXPRESSION", error))?
119 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
120 let column = columns
121 .iter_mut()
122 .find(|column| column.name == name)
123 .ok_or_else(|| super::missing_altered_column(table, name))?;
124 let Some(current) = column.generated.as_ref() else {
125 return Err(SQLError::Routine {
126 sqlstate: "55000".into(),
127 message: not_generated(table, name)?,
128 });
129 };
130 let kind = current.kind;
131 column.generated = Some(GeneratedColumn {
132 kind,
133 expression: Box::new(expression),
134 function_dependencies: Vec::new(),
135 });
136 let foreign_keys = context
137 .keys
138 .try_foreign_keys(table)
139 .map_err(|error| ddl_storage_error("ALTER COLUMN SET EXPRESSION", error))?;
140 let binding = context.bindings.bindings.binding_scope()?;
141 crate::schema::generated::prepare_generated_columns(
142 &crate::schema::SchemaBindingContext {
143 catalog: context.bindings.schema,
144 binding: &binding.context(),
145 },
146 qualifier,
147 &mut columns,
148 &foreign_keys,
149 )?;
150 let generated = columns
151 .iter()
152 .find(|column| column.name == name)
153 .and_then(|column| column.generated.clone())
154 .ok_or_else(|| {
155 SQLError::Internal(format!(
156 "generated column `{name}` disappeared during validation"
157 ))
158 })?;
159 Ok((generated, kind))
160}
161pub fn validate_drop_expression(
163 context: &ColumnAlterAnalysisContext<'_>,
164 table: &str,
165 name: &str,
166 if_exists: bool,
167) -> Result<Option<crate::SQLNotice>, SQLError> {
168 let columns = described_columns(context, table, "ALTER COLUMN DROP EXPRESSION")?;
169 let column = super::altered_column(table, &columns, name)?;
170 let Some(generated) = column.generated.as_ref() else {
171 let message = not_generated(table, name)?;
172 if if_exists {
173 return Ok(Some(crate::SQLNotice::notice(format!(
174 "{message}, skipping"
175 ))));
176 }
177 return Err(SQLError::Routine {
178 sqlstate: "55000".into(),
179 message,
180 });
181 };
182 if generated.kind == GeneratedColumnKind::Virtual {
183 let relation = uqa_core::RelationIdentity::from_legacy_name(table).map_err(|error| {
184 SQLError::Internal(format!("resolve ALTER TABLE target `{table}`: {error}"))
185 })?;
186 return Err(SQLError::Diagnostic {
187 sqlstate: "0A000".into(),
188 message: "ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"
189 .into(),
190 detail: Some(format!(
191 "Column \"{name}\" of relation \"{}\" is a virtual generated column.",
192 relation.name
193 )),
194 hint: None,
195 });
196 }
197 Ok(None)
198}
199pub fn analyze_column_type(
200 context: &ColumnAlterAnalysisContext<'_>,
201 table: &str,
202 _qualifier: &str,
203 name: &str,
204 ty: &ColumnType,
205) -> Result<Option<GeneratedColumnKind>, SQLError> {
206 if !context
207 .state
208 .has_column(table, name)
209 .map_err(|error| ddl_storage_error("ALTER COLUMN", error))?
210 {
211 return Err(super::missing_altered_column(table, name));
212 }
213 context.bindings.schema.require_type_usage(ty)?;
215 super::validate_postgres_relation_column_type(name, ty)?;
216 let mut candidate_columns = context
217 .columns
218 .try_describe_table(table)
219 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?
220 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
221 let candidate = candidate_columns
222 .iter_mut()
223 .find(|column| column.name == name)
224 .ok_or_else(|| SQLError::UnknownColumn(format!("{table}.{name}")))?;
225 candidate.ty.clone_from(ty);
226 let target_generated_kind = candidate.generated.as_ref().map(|generated| generated.kind);
227 if target_generated_kind.is_none() {
228 let columns = context
229 .state
230 .stored_columns(table)
231 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?;
232 let dependents = generated_columns_referencing_column(&columns, name);
233 if !dependents.is_empty() {
234 return Err(SQLError::TypeMismatch(format!("cannot alter type of column `{name}` because generated column(s) `{}` depend on it", dependents.join("`, `"))));
235 }
236 }
237 Ok(target_generated_kind)
238}
239
240pub fn validate_column_type_constraints(
242 context: &ColumnAlterAnalysisContext<'_>,
243 table: &str,
244 qualifier: &str,
245) -> Result<(), SQLError> {
246 let mut candidate_columns = described_columns(context, table, "ALTER COLUMN TYPE")?;
247 let key_constraints = context
248 .keys
249 .try_key_constraints(table)
250 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?;
251 let foreign_keys = context
252 .keys
253 .try_foreign_keys(table)
254 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?;
255 crate::schema::constraint_changes::validate_altered_constraint_column_types(
256 &context.constraint_types,
257 table,
258 &candidate_columns,
259 &key_constraints,
260 &foreign_keys,
261 )?;
262 let binding = context.bindings.bindings.binding_scope()?;
263 crate::schema::generated::prepare_generated_columns(
264 &crate::schema::SchemaBindingContext {
265 catalog: context.bindings.schema,
266 binding: &binding.context(),
267 },
268 qualifier,
269 &mut candidate_columns,
270 &foreign_keys,
271 )?;
272 Ok(())
273}