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