uqa_sql/semantics/grouping_sets/
names.rs1use std::borrow::Cow;
10
11use crate::plan::{ProjectionPlan, QueryBlockPlan};
12use crate::routines::RoutineResolution;
13use crate::{RowSchema, SQLError, SQLParam, ScalarExpr};
14
15pub fn resolve_grouping_expression<'a>(
17 routines: &dyn RoutineResolution,
18 expression: &'a ScalarExpr,
19 projections: &'a [ProjectionPlan],
20 schema: &RowSchema,
21 outer: Option<&RowSchema>,
22 params: &[SQLParam],
23) -> Result<Cow<'a, ScalarExpr>, SQLError> {
24 let resolved = resolve_grouping_expression_reference(
25 routines,
26 expression,
27 projections,
28 schema,
29 outer,
30 params,
31 )?;
32 Ok(if std::ptr::eq(resolved, expression) {
33 Cow::Borrowed(expression)
34 } else {
35 Cow::Owned(resolved.clone())
36 })
37}
38
39pub(crate) fn resolve_grouping_expression_reference<'a>(
41 routines: &dyn RoutineResolution,
42 expression: &'a ScalarExpr,
43 projections: &'a [ProjectionPlan],
44 schema: &RowSchema,
45 outer: Option<&RowSchema>,
46 params: &[SQLParam],
47) -> Result<&'a ScalarExpr, SQLError> {
48 let mut resolved = expression;
49 if let ScalarExpr::Column(name) = expression {
50 if !is_input_column(schema, outer, name) {
51 let mut matches = projections
52 .iter()
53 .filter(|projection| crate::semantics::projection_label_at(projection) == *name);
54 if let Some(first) = matches.next() {
55 resolved = &first.expr;
56 let mut identity = None;
57 for candidate in matches {
58 let first_identity = match &identity {
59 Some(identity) => identity,
60 None => identity.insert(super::expression_identity(
61 routines, resolved, schema, params,
62 )?),
63 };
64 if *first_identity
65 != super::expression_identity(routines, &candidate.expr, schema, params)?
66 {
67 return Err(SQLError::Routine {
68 sqlstate: "42702".into(),
69 message: format!("GROUP BY \"{name}\" is ambiguous"),
70 });
71 }
72 }
73 }
74 }
75 }
76 if crate::semantics::aggregates::contains_aggregate(
77 &|name: &str| routines.has_registered_aggregate_function(name),
78 resolved,
79 ) {
80 return Err(SQLError::Routine {
81 sqlstate: "42803".into(),
82 message: "aggregate functions are not allowed in GROUP BY".into(),
83 });
84 }
85 if resolved.contains_window() {
86 return Err(SQLError::Routine {
87 sqlstate: "42P20".into(),
88 message: "window functions are not allowed in GROUP BY".into(),
89 });
90 }
91 Ok(resolved)
92}
93
94fn is_input_column(schema: &RowSchema, outer: Option<&RowSchema>, name: &str) -> bool {
96 if !schema.has_unqualified_column(name) {
97 return false;
98 }
99 let Some(outer) = outer else {
100 return true;
101 };
102 let own_width = schema
103 .physical_width()
104 .saturating_sub(outer.physical_width());
105 schema.column_slot(name).is_none_or(|slot| slot < own_width)
106}
107
108pub fn bind_grouping_names(
110 routines: &dyn RoutineResolution,
111 statement: &mut QueryBlockPlan,
112 schema: &RowSchema,
113 outer: Option<&RowSchema>,
114 params: &[SQLParam],
115) -> Result<bool, SQLError> {
116 let mut changed = false;
117 for expression in statement
118 .group_by
119 .iter_mut()
120 .chain(statement.grouping_sets.iter_mut().flatten())
121 {
122 if let Cow::Owned(resolved) = resolve_grouping_expression(
123 routines,
124 expression,
125 &statement.projections,
126 schema,
127 outer,
128 params,
129 )? {
130 *expression = resolved;
131 changed = true;
132 }
133 }
134 Ok(changed)
135}