uqa_sql/semantics/grouping_sets/
validation.rs1use super::{expression_identity, resolve_grouping_expression};
10use crate::plan::QueryBlockPlan;
11use crate::routines::RoutineResolution;
12use crate::semantics::aggregates::{has_aggregate, is_aggregate};
13use crate::{RowSchema, SQLError, SQLParam, ScalarExpr};
14
15pub fn validate_grouped_expressions(
16 routines: &dyn RoutineResolution,
17 statement: &QueryBlockPlan,
18 schema: &RowSchema,
19 params: &[SQLParam],
20) -> Result<(), SQLError> {
21 let aggregates = |name: &str| routines.has_registered_aggregate_function(name);
22 if statement.group_by.is_empty()
23 && statement.grouping_sets.is_empty()
24 && statement.having.is_none()
25 && !has_aggregate(&aggregates, &statement.projections)
26 {
27 return Ok(());
28 }
29 let groups = statement
30 .group_by
31 .iter()
32 .chain(statement.grouping_sets.iter().flatten())
33 .map(|expression| {
34 let expression = resolve_grouping_expression(
35 routines,
36 expression,
37 &statement.projections,
38 schema,
39 params,
40 )?;
41 expression_identity(routines, &expression, schema, params)
42 })
43 .collect::<Result<Vec<_>, _>>()?;
44 let projections =
45 crate::semantics::expand_bound_projection_stars(&statement.projections, schema)?;
46 for expression in projections
47 .iter()
48 .map(|projection| (&projection.expr, false))
49 .chain(
50 statement
51 .having
52 .iter()
53 .map(|expression| (expression, false)),
54 )
55 .chain(statement.order_by.iter().map(|order| (&order.expr, true)))
56 .chain(
57 statement
58 .distinct_on
59 .iter()
60 .map(|expression| (expression, true)),
61 )
62 {
63 let (expression, output_names) = expression;
64 if output_names
66 && matches!(expression, ScalarExpr::Column(name) if projections.iter().any(|projection| crate::semantics::projection_label_at(projection) == *name))
67 {
68 continue;
69 }
70 expression.try_visit(&mut |part| {
71 if is_aggregate(&aggregates, part)
72 || groups.contains(&expression_identity(routines, part, schema, params)?)
73 {
74 return Ok(false);
75 }
76 let position = match part {
77 ScalarExpr::Column(column) => schema.unqualified_position(column),
78 ScalarExpr::QualifiedColumn { qualifier, column } => {
79 schema.qualified_position(qualifier, column)
80 }
81 ScalarExpr::Position(position) => Some(*position),
82 _ => None,
83 };
84 if let Some(identity) = position.and_then(|position| schema.identity(position)) {
85 let name = identity.qualifier().map_or_else(
86 || identity.column().to_owned(),
87 |qualifier| format!("{qualifier}.{}", identity.column()),
88 );
89 return Err(SQLError::Routine {
90 sqlstate: "42803".into(),
91 message: format!("column \"{name}\" must appear in the GROUP BY clause or be used in an aggregate function"),
92 });
93 }
94 Ok(true)
95 })?;
96 }
97 Ok(())
98}