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<(), SQLError> {
94 let columns = described_columns(context, table, "ALTER COLUMN DROP DEFAULT")?;
95 super::validate_default_change(
96 table,
97 super::altered_column(table, &columns, column)?,
98 false,
99 )
100}
101fn not_generated(table: &str, column: &str) -> Result<String, SQLError> {
102 let relation = uqa_core::RelationIdentity::from_legacy_name(table).map_err(|error| {
103 SQLError::Internal(format!("resolve ALTER TABLE target `{table}`: {error}"))
104 })?;
105 Ok(format!(
106 "column \"{column}\" of relation \"{}\" is not a generated column",
107 relation.name
108 ))
109}
110pub fn analyze_generated_expression(
111 context: &ColumnAlterAnalysisContext<'_>,
112 table: &str,
113 qualifier: &str,
114 name: &str,
115 expression: Expr,
116) -> Result<(GeneratedColumn, GeneratedColumnKind), SQLError> {
117 let mut columns = context
118 .columns
119 .try_describe_table(table)
120 .map_err(|error| ddl_storage_error("ALTER COLUMN SET EXPRESSION", error))?
121 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
122 let column = columns
123 .iter_mut()
124 .find(|column| column.name == name)
125 .ok_or_else(|| super::missing_altered_column(table, name))?;
126 let Some(current) = column.generated.as_ref() else {
127 return Err(SQLError::Routine {
128 sqlstate: "55000".into(),
129 message: not_generated(table, name)?,
130 });
131 };
132 let kind = current.kind;
133 column.generated = Some(GeneratedColumn {
134 kind,
135 expression: Box::new(expression),
136 function_dependencies: Vec::new(),
137 });
138 let foreign_keys = context
139 .keys
140 .try_foreign_keys(table)
141 .map_err(|error| ddl_storage_error("ALTER COLUMN SET EXPRESSION", error))?;
142 let binding = context.bindings.bindings.binding_scope()?;
143 crate::schema::generated::prepare_generated_columns(
144 &crate::schema::SchemaBindingContext {
145 catalog: context.bindings.schema,
146 binding: &binding.context(),
147 },
148 qualifier,
149 &mut columns,
150 &foreign_keys,
151 )?;
152 let generated = columns
153 .iter()
154 .find(|column| column.name == name)
155 .and_then(|column| column.generated.clone())
156 .ok_or_else(|| {
157 SQLError::Internal(format!(
158 "generated column `{name}` disappeared during validation"
159 ))
160 })?;
161 Ok((generated, kind))
162}
163pub fn validate_drop_expression(
165 context: &ColumnAlterAnalysisContext<'_>,
166 table: &str,
167 name: &str,
168 if_exists: bool,
169) -> Result<Option<crate::SQLNotice>, SQLError> {
170 let columns = described_columns(context, table, "ALTER COLUMN DROP EXPRESSION")?;
171 let column = super::altered_column(table, &columns, name)?;
172 let Some(generated) = column.generated.as_ref() else {
173 let message = not_generated(table, name)?;
174 if if_exists {
175 return Ok(Some(crate::SQLNotice::notice(format!(
176 "{message}, skipping"
177 ))));
178 }
179 return Err(SQLError::Routine {
180 sqlstate: "55000".into(),
181 message,
182 });
183 };
184 if generated.kind == GeneratedColumnKind::Virtual {
185 let relation = uqa_core::RelationIdentity::from_legacy_name(table).map_err(|error| {
186 SQLError::Internal(format!("resolve ALTER TABLE target `{table}`: {error}"))
187 })?;
188 return Err(SQLError::Diagnostic {
189 sqlstate: "0A000".into(),
190 message: "ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"
191 .into(),
192 detail: Some(format!(
193 "Column \"{name}\" of relation \"{}\" is a virtual generated column.",
194 relation.name
195 )),
196 hint: None,
197 });
198 }
199 Ok(None)
200}
201pub fn analyze_column_type(
202 context: &ColumnAlterAnalysisContext<'_>,
203 table: &str,
204 _qualifier: &str,
205 name: &str,
206 ty: &ColumnType,
207) -> Result<Option<GeneratedColumnKind>, SQLError> {
208 if !context
209 .state
210 .has_column(table, name)
211 .map_err(|error| ddl_storage_error("ALTER COLUMN", error))?
212 {
213 return Err(super::missing_altered_column(table, name));
214 }
215 context.bindings.schema.require_type_usage(ty)?;
217 super::validate_postgres_relation_column_type(name, ty)?;
218 let mut candidate_columns = context
219 .columns
220 .try_describe_table(table)
221 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?
222 .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
223 let candidate = candidate_columns
224 .iter_mut()
225 .find(|column| column.name == name)
226 .ok_or_else(|| SQLError::UnknownColumn(format!("{table}.{name}")))?;
227 candidate.ty.clone_from(ty);
228 let target_generated_kind = candidate.generated.as_ref().map(|generated| generated.kind);
229 if target_generated_kind.is_none() {
230 let columns = context
231 .state
232 .stored_columns(table)
233 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?;
234 let dependents = generated_columns_referencing_column(&columns, name);
235 if !dependents.is_empty() {
236 return Err(SQLError::TypeMismatch(format!("cannot alter type of column `{name}` because generated column(s) `{}` depend on it", dependents.join("`, `"))));
237 }
238 }
239 Ok(target_generated_kind)
240}
241
242pub fn validate_column_type_constraints(
244 context: &ColumnAlterAnalysisContext<'_>,
245 table: &str,
246 qualifier: &str,
247) -> Result<(), SQLError> {
248 let mut candidate_columns = described_columns(context, table, "ALTER COLUMN TYPE")?;
249 let key_constraints = context
250 .keys
251 .try_key_constraints(table)
252 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?;
253 let foreign_keys = context
254 .keys
255 .try_foreign_keys(table)
256 .map_err(|error| ddl_storage_error("ALTER COLUMN TYPE", error))?;
257 crate::schema::constraint_changes::validate_altered_constraint_column_types(
258 &context.constraint_types,
259 table,
260 &candidate_columns,
261 &key_constraints,
262 &foreign_keys,
263 )?;
264 let binding = context.bindings.bindings.binding_scope()?;
265 crate::schema::generated::prepare_generated_columns(
266 &crate::schema::SchemaBindingContext {
267 catalog: context.bindings.schema,
268 binding: &binding.context(),
269 },
270 qualifier,
271 &mut candidate_columns,
272 &foreign_keys,
273 )?;
274 Ok(())
275}