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