1use crate::ast::ForeignKey;
10use crate::ast::{ColumnDef, Expr, GeneratedColumnKind};
11use crate::{semantics::aggregates, ColumnType, SQLError};
12
13pub(crate) mod eligibility;
14mod virtual_security;
15
16#[cfg(test)]
17mod tests;
18
19pub fn prepare_generated_columns(
20 context: &super::SchemaBindingContext<'_, '_>,
21 qualifier: &str,
22 columns: &mut [ColumnDef],
23 foreign_keys: &[ForeignKey],
24) -> Result<(), SQLError> {
25 let snapshot = columns.to_vec();
26 for index in 0..columns.len() {
27 prepare_generated_column(context, qualifier, &snapshot, columns, index, foreign_keys)?;
28 }
29 Ok(())
30}
31
32pub fn prepare_generated_column(
34 context: &super::SchemaBindingContext<'_, '_>,
35 qualifier: &str,
36 snapshot: &[ColumnDef],
37 columns: &mut [ColumnDef],
38 index: usize,
39 foreign_keys: &[ForeignKey],
40) -> Result<(), SQLError> {
41 let engine = context.catalog;
42 let column = &snapshot[index];
43 let Some(generated) = column.generated.as_ref() else {
44 return Ok(());
45 };
46 if column.default.is_some() {
47 return Err(SQLError::TypeMismatch(format!(
48 "both default and generation expression specified for column `{}`",
49 column.name
50 )));
51 }
52 if column.auto_increment.is_some() {
53 return Err(SQLError::TypeMismatch(format!(
54 "both identity and generation expression specified for column `{}`",
55 column.name
56 )));
57 }
58 if generated.kind == GeneratedColumnKind::Virtual {
59 validate_virtual_column_envelope(column, foreign_keys)?;
60 }
61 check_generation_shape(engine, snapshot, &generated.expression)?;
62 validate_generation_expression(qualifier, snapshot, &generated.expression)?;
63 if generated.kind == GeneratedColumnKind::Virtual {
64 virtual_security::check_virtual_host_functions(engine, &generated.expression)?;
65 }
66 let prepared = columns[index]
67 .generated
68 .as_mut()
69 .ok_or_else(|| SQLError::Internal("generated column disappeared".into()))?;
70 eligibility::check_host_return_types(&mut prepared.expression, |name| {
71 engine
72 .registered_runtime_function_volatility(name)
73 .is_some()
74 })?;
75 bind_schema_column_references(&mut prepared.expression, qualifier);
76 let planned = engine.plan_schema_expression(&prepared.expression, snapshot)?;
77 if !planned.immutable {
78 return Err(eligibility::non_immutable_function());
79 }
80 *prepared.expression = planned.expression;
81 if generated.kind == GeneratedColumnKind::Virtual {
82 virtual_security::check_virtual_generated_security(engine, snapshot, &prepared.expression)?;
83 }
84 crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
85 engine,
86 &prepared.expression,
87 Some(&column.ty),
88 )?;
89 if let Some(source) = &planned.ty {
90 super::defaults::check_assignable(source, &column.ty, &column.name, "default expression")?;
91 } else if matches!(
92 prepared.expression.as_ref(),
93 Expr::Literal(uqa_core::Value::Str(_) | uqa_core::Value::Null)
94 ) {
95 crate::catalog::stored_ast::read_unknown_stored_literal(
96 crate::FunctionTypeResolver::enum_labels(engine),
97 crate::FunctionTypeResolver::catalog_input_functions(engine),
98 &mut prepared.expression,
99 &column.ty,
100 false,
101 )?;
102 }
103 let mut dependencies = Vec::new();
104 crate::catalog::stored_ast::visit_stored_expression(&mut prepared.expression, &mut |node| {
105 if let Expr::Func {
106 binding: Some(binding),
107 ..
108 } = node
109 {
110 if !binding.builtin && !dependencies.contains(binding) {
111 dependencies.push(binding.clone());
112 }
113 }
114 Ok(())
115 })?;
116 prepared.function_dependencies = dependencies;
117 super::dependencies::oid_alias::read_oid_alias_constants(engine, &mut prepared.expression)?;
119 crate::catalog::stored_ast::fold_assigned_stored_literal(
121 &mut prepared.expression,
122 &column.ty,
123 crate::FunctionTypeResolver::enum_labels(engine),
124 )?;
125 let typed = (*prepared.expression).clone();
126 super::defaults::bind_stored_schema_expression_with_schema(
127 context,
128 &mut prepared.expression,
129 typed,
130 &crate::RowSchema::with_column_definitions(Some(qualifier), snapshot),
131 )?;
132 Ok(())
133}
134
135fn check_generation_shape(
137 engine: &dyn super::SchemaExpressionCatalog,
138 columns: &[ColumnDef],
139 expression: &Expr,
140) -> Result<(), SQLError> {
141 let plan = crate::plan::ExpressionPlan::lower(expression.clone());
142 let schema = crate::RowSchema::with_types(
143 columns.iter().map(|column| column.name.clone()).collect(),
144 columns
145 .iter()
146 .map(|column| Some(column.ty.clone()))
147 .collect(),
148 );
149 if !plan.subqueries.is_empty() {
150 return Err(generation_error(
151 "0A000",
152 "cannot use subquery in column generation expression",
153 ));
154 }
155 if aggregates::contains_aggregate(engine, &plan.scalar) {
156 return Err(generation_error(
157 "42803",
158 "aggregate functions are not allowed in column generation expressions",
159 ));
160 }
161 if crate::semantics::windows::expr_has_window(&plan.scalar) {
162 return Err(generation_error(
163 "42P20",
164 "window functions are not allowed in column generation expressions",
165 ));
166 }
167 if crate::semantics::sets::validation::expression_may_return_set(
168 engine,
169 engine,
170 &plan.scalar,
171 &schema,
172 &[],
173 )? {
174 return Err(generation_error(
175 "0A000",
176 "set-returning functions are not allowed in column generation expressions",
177 ));
178 }
179 Ok(())
180}
181
182fn validate_virtual_column_envelope(
184 column: &ColumnDef,
185 foreign_keys: &[ForeignKey],
186) -> Result<(), SQLError> {
187 if virtual_security::is_user_defined_type(&column.ty) {
188 return Err(SQLError::Diagnostic {
189 sqlstate: "0A000".into(),
190 message: format!(
191 "virtual generated column \"{}\" cannot have a user-defined type",
192 column.name
193 ),
194 detail: Some(virtual_security::USER_DEFINED_TYPE_DETAIL.into()),
195 hint: None,
196 });
197 }
198 if contains_engine_defined_type(&column.ty) {
199 return Err(SQLError::TypeMismatch(format!(
200 "virtual generated column `{}` cannot use a user-defined type",
201 column.name
202 )));
203 }
204 if column.references.is_some()
205 || foreign_keys.iter().any(|foreign_key| {
206 foreign_key
207 .local_columns
208 .iter()
209 .any(|name| name == &column.name)
210 })
211 {
212 return Err(SQLError::Unsupported(
213 "foreign key constraints on virtual generated columns are not supported".into(),
214 ));
215 }
216 Ok(())
217}
218
219fn contains_engine_defined_type(ty: &ColumnType) -> bool {
220 match ty {
221 ColumnType::Vector(_) | ColumnType::Tensor(_) => true,
222 ColumnType::Array(element) => contains_engine_defined_type(element),
223 _ => false,
224 }
225}
226
227fn validate_generation_expression(
228 qualifier: &str,
229 columns: &[ColumnDef],
230 expression: &Expr,
231) -> Result<(), SQLError> {
232 match expression {
233 Expr::Column(name) => validate_generation_column_reference(columns, name),
234 Expr::QualifiedColumn {
235 qualifier: expression_qualifier,
236 column,
237 ..
238 } => {
239 if expression_qualifier != qualifier {
240 return Err(SQLError::UnknownTable(expression_qualifier.clone()));
241 }
242 validate_generation_column_reference(columns, column)
243 }
244 Expr::Func {
245 args,
246 distinct,
247 order_by,
248 filter,
249 ..
250 } => {
251 if *distinct || !order_by.is_empty() || filter.is_some() {
252 return Err(SQLError::TypeMismatch(
253 "aggregate syntax is not allowed in column generation expressions".into(),
254 ));
255 }
256 for argument in args {
257 validate_generation_expression(qualifier, columns, argument)?;
258 }
259 Ok(())
260 }
261 Expr::Array(items)
262 | Expr::Row(items)
263 | Expr::CompositeRow { items, .. }
264 | Expr::And(items)
265 | Expr::Or(items) => {
266 for item in items {
267 validate_generation_expression(qualifier, columns, item)?;
268 }
269 Ok(())
270 }
271 Expr::Binary { lhs, rhs, .. } => {
272 validate_generation_expression(qualifier, columns, lhs)?;
273 validate_generation_expression(qualifier, columns, rhs)
274 }
275 Expr::Not(inner)
276 | Expr::UnaryMinus(inner)
277 | Expr::IsNull { expr: inner, .. }
278 | Expr::Cast { expr: inner, .. } => {
279 validate_generation_expression(qualifier, columns, inner)
280 }
281 Expr::Between { expr, low, high } => {
282 validate_generation_expression(qualifier, columns, expr)?;
283 validate_generation_expression(qualifier, columns, low)?;
284 validate_generation_expression(qualifier, columns, high)
285 }
286 Expr::InList { expr, list, .. } => {
287 validate_generation_expression(qualifier, columns, expr)?;
288 for item in list {
289 validate_generation_expression(qualifier, columns, item)?;
290 }
291 Ok(())
292 }
293 Expr::Case {
294 base,
295 when,
296 else_branch,
297 } => {
298 if let Some(base) = base {
299 validate_generation_expression(qualifier, columns, base)?;
300 }
301 for (condition, result) in when {
302 validate_generation_expression(qualifier, columns, condition)?;
303 validate_generation_expression(qualifier, columns, result)?;
304 }
305 if let Some(else_branch) = else_branch {
306 validate_generation_expression(qualifier, columns, else_branch)?;
307 }
308 Ok(())
309 }
310 Expr::Default | Expr::Param(_) => Err(SQLError::TypeMismatch(
311 "parameters and DEFAULT are not allowed in column generation expressions".into(),
312 )),
313 Expr::Star | Expr::QualifiedStar(_) => Err(SQLError::TypeMismatch(
314 "whole-row references are not allowed in column generation expressions".into(),
315 )),
316 Expr::InternalColumn(_) => Err(SQLError::Internal(
317 "executor-only column reached generation expression validation".into(),
318 )),
319 Expr::WindowCall { .. } => Err(generation_error(
320 "42P20",
321 "window functions are not allowed in column generation expressions",
322 )),
323 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
324 Err(generation_error(
325 "0A000",
326 "cannot use subquery in column generation expression",
327 ))
328 }
329 Expr::Literal(_) | Expr::TypedLiteral { .. } => Ok(()),
330 }
331}
332
333pub fn bind_schema_column_references(expression: &mut Expr, qualifier: &str) {
334 if let Expr::QualifiedColumn {
335 qualifier: expression_qualifier,
336 column,
337 } = expression
338 {
339 if expression_qualifier == qualifier {
340 *expression = Expr::Column(column.clone());
341 }
342 return;
343 }
344 match expression {
345 Expr::Func {
346 args,
347 order_by,
348 filter,
349 ..
350 } => {
351 for argument in args {
352 bind_schema_column_references(argument, qualifier);
353 }
354 for order in order_by {
355 bind_schema_column_references(&mut order.expr, qualifier);
356 }
357 if let Some(filter) = filter {
358 bind_schema_column_references(filter, qualifier);
359 }
360 }
361 Expr::Array(items)
362 | Expr::Row(items)
363 | Expr::CompositeRow { items, .. }
364 | Expr::And(items)
365 | Expr::Or(items) => {
366 for item in items {
367 bind_schema_column_references(item, qualifier);
368 }
369 }
370 Expr::Binary { lhs, rhs, .. } => {
371 bind_schema_column_references(lhs, qualifier);
372 bind_schema_column_references(rhs, qualifier);
373 }
374 Expr::Not(inner)
375 | Expr::UnaryMinus(inner)
376 | Expr::IsNull { expr: inner, .. }
377 | Expr::Cast { expr: inner, .. } => {
378 bind_schema_column_references(inner, qualifier);
379 }
380 Expr::Between { expr, low, high } => {
381 bind_schema_column_references(expr, qualifier);
382 bind_schema_column_references(low, qualifier);
383 bind_schema_column_references(high, qualifier);
384 }
385 Expr::InList { expr, list, .. } => {
386 bind_schema_column_references(expr, qualifier);
387 for item in list {
388 bind_schema_column_references(item, qualifier);
389 }
390 }
391 Expr::Case {
392 base,
393 when,
394 else_branch,
395 } => {
396 if let Some(base) = base {
397 bind_schema_column_references(base, qualifier);
398 }
399 for (condition, result) in when {
400 bind_schema_column_references(condition, qualifier);
401 bind_schema_column_references(result, qualifier);
402 }
403 if let Some(else_branch) = else_branch {
404 bind_schema_column_references(else_branch, qualifier);
405 }
406 }
407 Expr::Star
408 | Expr::QualifiedStar(_)
409 | Expr::Default
410 | Expr::Column(_)
411 | Expr::QualifiedColumn { .. }
412 | Expr::InternalColumn(_)
413 | Expr::Literal(_)
414 | Expr::TypedLiteral { .. }
415 | Expr::Param(_)
416 | Expr::WindowCall { .. }
417 | Expr::ScalarSubquery(_)
418 | Expr::Exists { .. }
419 | Expr::InSubquery { .. } => {}
420 }
421}
422
423fn validate_generation_column_reference(columns: &[ColumnDef], name: &str) -> Result<(), SQLError> {
424 let Some(column) = columns.iter().find(|column| column.name == name) else {
425 return Err(SQLError::UnknownColumn(name.to_string()));
426 };
427 if column.generated.is_some() {
428 return Err(SQLError::Diagnostic {
429 sqlstate: "42P17".into(),
430 message: format!(
431 "cannot use generated column \"{name}\" in column generation expression"
432 ),
433 detail: Some("A generated column cannot reference another generated column.".into()),
434 hint: None,
435 });
436 }
437 Ok(())
438}
439
440fn generation_error(sqlstate: &str, message: &str) -> SQLError {
441 SQLError::Routine {
442 sqlstate: sqlstate.into(),
443 message: message.into(),
444 }
445}