1use super::SchemaExpressionCatalog;
10use crate::ast::ForeignKey;
11use crate::ast::{ColumnDef, Expr, GeneratedColumnKind, TableKeyConstraint};
12use crate::{
13 assignment::conversion::convert_value_to_column_type, semantics::aggregates, ColumnType,
14 SQLError,
15};
16
17pub(crate) mod eligibility;
18pub(super) mod typing;
19
20pub fn prepare_generated_columns(
21 engine: &dyn SchemaExpressionCatalog,
22 qualifier: &str,
23 columns: &mut [ColumnDef],
24 key_constraints: &[TableKeyConstraint],
25 foreign_keys: &[ForeignKey],
26) -> Result<(), SQLError> {
27 let snapshot = columns.to_vec();
28 for (index, column) in snapshot.iter().enumerate() {
29 let Some(generated) = column.generated.as_ref() else {
30 continue;
31 };
32 if column.default.is_some() {
33 return Err(SQLError::TypeMismatch(format!(
34 "both default and generation expression specified for column `{}`",
35 column.name
36 )));
37 }
38 if column.auto_increment.is_some() {
39 return Err(SQLError::TypeMismatch(format!(
40 "both identity and generation expression specified for column `{}`",
41 column.name
42 )));
43 }
44 if generated.kind == GeneratedColumnKind::Virtual {
45 validate_virtual_column_envelope(column, key_constraints, foreign_keys)?;
46 }
47 let plan = crate::plan::ExpressionPlan::lower((*generated.expression).clone());
48 if !plan.subqueries.is_empty() {
49 return Err(SQLError::TypeMismatch(
50 "cannot use subquery in column generation expression".into(),
51 ));
52 }
53 if aggregates::contains_aggregate(engine, &plan.scalar) {
54 return Err(SQLError::TypeMismatch(
55 "aggregate functions are not allowed in column generation expressions".into(),
56 ));
57 }
58 validate_generation_expression(
59 engine,
60 qualifier,
61 &snapshot,
62 &generated.expression,
63 generated.kind,
64 )?;
65 let prepared = columns[index]
66 .generated
67 .as_mut()
68 .ok_or_else(|| SQLError::Internal("generated column disappeared".into()))?;
69 bind_schema_column_references(&mut prepared.expression, qualifier);
70 let (expression_type, function_dependencies) =
71 typing::infer_generation_expression(engine, &snapshot, &mut prepared.expression)?;
72 crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
73 engine,
74 &prepared.expression,
75 Some(&column.ty),
76 )?;
77 if let typing::GenerationType::UnknownLiteral(value) = &expression_type {
78 convert_value_to_column_type(uqa_core::Value::Str(value.clone()), &column.ty)?;
79 } else if !typing::generation_type_assignable_to(&expression_type, &column.ty) {
80 return Err(SQLError::TypeMismatch(format!(
81 "column `{}` has type {} but generation expression has type {}",
82 column.name,
83 crate::catalog::type_metadata::column_type_name(&column.ty),
84 typing::generation_type_name(&expression_type)
85 )));
86 }
87 prepared.function_dependencies = function_dependencies;
88 }
89 Ok(())
90}
91
92fn validate_virtual_column_envelope(
93 column: &ColumnDef,
94 key_constraints: &[TableKeyConstraint],
95 foreign_keys: &[ForeignKey],
96) -> Result<(), SQLError> {
97 if contains_engine_defined_type(&column.ty) {
98 return Err(SQLError::TypeMismatch(format!(
99 "virtual generated column `{}` cannot use a user-defined type",
100 column.name
101 )));
102 }
103 if column.primary_key
104 || key_constraints.iter().any(|constraint| {
105 constraint.kind == crate::ast::TableKeyConstraintKind::PrimaryKey
106 && constraint.columns.iter().any(|name| name == &column.name)
107 })
108 {
109 return Err(SQLError::TypeMismatch(
110 "primary keys on virtual generated columns are not supported".into(),
111 ));
112 }
113 if column.unique
114 || key_constraints.iter().any(|constraint| {
115 constraint.kind == crate::ast::TableKeyConstraintKind::Unique
116 && constraint.columns.iter().any(|name| name == &column.name)
117 })
118 {
119 return Err(SQLError::TypeMismatch(
120 "unique constraints on virtual generated columns are not supported".into(),
121 ));
122 }
123 if column.references.is_some()
124 || foreign_keys.iter().any(|foreign_key| {
125 foreign_key
126 .local_columns
127 .iter()
128 .any(|name| name == &column.name)
129 })
130 {
131 return Err(SQLError::TypeMismatch(
132 "foreign key constraints on virtual generated columns are not supported".into(),
133 ));
134 }
135 Ok(())
136}
137
138fn contains_engine_defined_type(ty: &ColumnType) -> bool {
139 match ty {
140 ColumnType::Vector(_) | ColumnType::Tensor(_) => true,
141 ColumnType::Array(element) => contains_engine_defined_type(element),
142 _ => false,
143 }
144}
145
146#[expect(
147 clippy::too_many_lines,
148 reason = "preserves generated coercion diagnostics"
149)]
150fn validate_generation_expression(
151 engine: &dyn SchemaExpressionCatalog,
152 qualifier: &str,
153 columns: &[ColumnDef],
154 expression: &Expr,
155 kind: GeneratedColumnKind,
156) -> Result<(), SQLError> {
157 match expression {
158 Expr::Column(name) => validate_generation_column_reference(columns, name),
159 Expr::QualifiedColumn {
160 qualifier: expression_qualifier,
161 column,
162 ..
163 } => {
164 if expression_qualifier != qualifier {
165 return Err(SQLError::UnknownTable(expression_qualifier.clone()));
166 }
167 validate_generation_column_reference(columns, column)
168 }
169 Expr::Func {
170 name,
171 binding,
172 args,
173 distinct,
174 order_by,
175 filter,
176 ..
177 } => {
178 if *distinct || !order_by.is_empty() || filter.is_some() {
179 return Err(SQLError::TypeMismatch(
180 "aggregate syntax is not allowed in column generation expressions".into(),
181 ));
182 }
183 if kind == GeneratedColumnKind::Virtual
184 && binding
185 .as_ref()
186 .and_then(|binding| binding.dispatch)
187 .is_none()
188 && (engine
189 .registered_runtime_function_volatility(name)
190 .is_some()
191 || engine.lookup_visible_sql_functions(name)?.is_some())
192 {
193 return Err(SQLError::Diagnostic {
194 sqlstate: "0A000".into(),
195 message: "generation expression uses user-defined function".into(),
196 detail: Some("Virtual generated columns that make use of user-defined functions are not yet supported.".into()),
197 hint: None,
198 });
199 }
200 for argument in args {
201 validate_generation_expression(engine, qualifier, columns, argument, kind)?;
202 }
203 Ok(())
204 }
205 Expr::Array(items) | Expr::Row(items) | Expr::And(items) | Expr::Or(items) => {
206 for item in items {
207 validate_generation_expression(engine, qualifier, columns, item, kind)?;
208 }
209 Ok(())
210 }
211 Expr::Binary { lhs, rhs, .. } => {
212 validate_generation_expression(engine, qualifier, columns, lhs, kind)?;
213 validate_generation_expression(engine, qualifier, columns, rhs, kind)
214 }
215 Expr::Not(inner)
216 | Expr::UnaryMinus(inner)
217 | Expr::IsNull { expr: inner, .. }
218 | Expr::Cast { expr: inner, .. } => {
219 validate_generation_expression(engine, qualifier, columns, inner, kind)
220 }
221 Expr::Between { expr, low, high } => {
222 validate_generation_expression(engine, qualifier, columns, expr, kind)?;
223 validate_generation_expression(engine, qualifier, columns, low, kind)?;
224 validate_generation_expression(engine, qualifier, columns, high, kind)
225 }
226 Expr::InList { expr, list, .. } => {
227 validate_generation_expression(engine, qualifier, columns, expr, kind)?;
228 for item in list {
229 validate_generation_expression(engine, qualifier, columns, item, kind)?;
230 }
231 Ok(())
232 }
233 Expr::Case {
234 base,
235 when,
236 else_branch,
237 } => {
238 if let Some(base) = base {
239 validate_generation_expression(engine, qualifier, columns, base, kind)?;
240 }
241 for (condition, result) in when {
242 validate_generation_expression(engine, qualifier, columns, condition, kind)?;
243 validate_generation_expression(engine, qualifier, columns, result, kind)?;
244 }
245 if let Some(else_branch) = else_branch {
246 validate_generation_expression(engine, qualifier, columns, else_branch, kind)?;
247 }
248 Ok(())
249 }
250 Expr::Default | Expr::Param(_) => Err(SQLError::TypeMismatch(
251 "parameters and DEFAULT are not allowed in column generation expressions".into(),
252 )),
253 Expr::Star | Expr::QualifiedStar(_) => Err(SQLError::TypeMismatch(
254 "whole-row references are not allowed in column generation expressions".into(),
255 )),
256 Expr::InternalColumn(_) => Err(SQLError::Internal(
257 "executor-only column reached generation expression validation".into(),
258 )),
259 Expr::WindowCall { .. } => Err(SQLError::TypeMismatch(
260 "window functions are not allowed in column generation expressions".into(),
261 )),
262 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => Err(
263 SQLError::TypeMismatch("cannot use subquery in column generation expression".into()),
264 ),
265 Expr::Literal(_) | Expr::TypedLiteral { .. } => Ok(()),
266 }
267}
268
269pub fn bind_schema_column_references(expression: &mut Expr, qualifier: &str) {
270 if let Expr::QualifiedColumn {
271 qualifier: expression_qualifier,
272 column,
273 } = expression
274 {
275 if expression_qualifier == qualifier {
276 *expression = Expr::Column(column.clone());
277 }
278 return;
279 }
280 match expression {
281 Expr::Func {
282 args,
283 order_by,
284 filter,
285 ..
286 } => {
287 for argument in args {
288 bind_schema_column_references(argument, qualifier);
289 }
290 for order in order_by {
291 bind_schema_column_references(&mut order.expr, qualifier);
292 }
293 if let Some(filter) = filter {
294 bind_schema_column_references(filter, qualifier);
295 }
296 }
297 Expr::Array(items) | Expr::Row(items) | Expr::And(items) | Expr::Or(items) => {
298 for item in items {
299 bind_schema_column_references(item, qualifier);
300 }
301 }
302 Expr::Binary { lhs, rhs, .. } => {
303 bind_schema_column_references(lhs, qualifier);
304 bind_schema_column_references(rhs, qualifier);
305 }
306 Expr::Not(inner)
307 | Expr::UnaryMinus(inner)
308 | Expr::IsNull { expr: inner, .. }
309 | Expr::Cast { expr: inner, .. } => {
310 bind_schema_column_references(inner, qualifier);
311 }
312 Expr::Between { expr, low, high } => {
313 bind_schema_column_references(expr, qualifier);
314 bind_schema_column_references(low, qualifier);
315 bind_schema_column_references(high, qualifier);
316 }
317 Expr::InList { expr, list, .. } => {
318 bind_schema_column_references(expr, qualifier);
319 for item in list {
320 bind_schema_column_references(item, qualifier);
321 }
322 }
323 Expr::Case {
324 base,
325 when,
326 else_branch,
327 } => {
328 if let Some(base) = base {
329 bind_schema_column_references(base, qualifier);
330 }
331 for (condition, result) in when {
332 bind_schema_column_references(condition, qualifier);
333 bind_schema_column_references(result, qualifier);
334 }
335 if let Some(else_branch) = else_branch {
336 bind_schema_column_references(else_branch, qualifier);
337 }
338 }
339 Expr::Star
340 | Expr::QualifiedStar(_)
341 | Expr::Default
342 | Expr::Column(_)
343 | Expr::QualifiedColumn { .. }
344 | Expr::InternalColumn(_)
345 | Expr::Literal(_)
346 | Expr::TypedLiteral { .. }
347 | Expr::Param(_)
348 | Expr::WindowCall { .. }
349 | Expr::ScalarSubquery(_)
350 | Expr::Exists { .. }
351 | Expr::InSubquery { .. } => {}
352 }
353}
354
355fn validate_generation_column_reference(columns: &[ColumnDef], name: &str) -> Result<(), SQLError> {
356 let Some(column) = columns.iter().find(|column| column.name == name) else {
357 return Err(SQLError::UnknownColumn(name.to_string()));
358 };
359 if column.generated.is_some() {
360 return Err(SQLError::TypeMismatch(format!(
361 "cannot use generated column `{name}` in column generation expression"
362 )));
363 }
364 Ok(())
365}