1#include "duckdb/planner/expression_binder/group_binder.hpp"
2
3#include "duckdb/parser/expression/columnref_expression.hpp"
4#include "duckdb/parser/expression/constant_expression.hpp"
5#include "duckdb/parser/query_node/select_node.hpp"
6#include "duckdb/planner/expression/bound_constant_expression.hpp"
7#include "duckdb/common/to_string.hpp"
8
9namespace duckdb {
10
11GroupBinder::GroupBinder(Binder &binder, ClientContext &context, SelectNode &node, idx_t group_index,
12 case_insensitive_map_t<idx_t> &alias_map, case_insensitive_map_t<idx_t> &group_alias_map)
13 : ExpressionBinder(binder, context), node(node), alias_map(alias_map), group_alias_map(group_alias_map),
14 group_index(group_index) {
15}
16
17BindResult GroupBinder::BindExpression(unique_ptr<ParsedExpression> &expr_ptr, idx_t depth, bool root_expression) {
18 auto &expr = *expr_ptr;
19 if (root_expression && depth == 0) {
20 switch (expr.expression_class) {
21 case ExpressionClass::COLUMN_REF:
22 return BindColumnRef(expr&: expr.Cast<ColumnRefExpression>());
23 case ExpressionClass::CONSTANT:
24 return BindConstant(expr&: expr.Cast<ConstantExpression>());
25 case ExpressionClass::PARAMETER:
26 throw ParameterNotAllowedException("Parameter not supported in GROUP BY clause");
27 default:
28 break;
29 }
30 }
31 switch (expr.expression_class) {
32 case ExpressionClass::DEFAULT:
33 return BindResult("GROUP BY clause cannot contain DEFAULT clause");
34 case ExpressionClass::WINDOW:
35 return BindResult("GROUP BY clause cannot contain window functions!");
36 default:
37 return ExpressionBinder::BindExpression(expr_ptr, depth);
38 }
39}
40
41string GroupBinder::UnsupportedAggregateMessage() {
42 return "GROUP BY clause cannot contain aggregates!";
43}
44
45BindResult GroupBinder::BindSelectRef(idx_t entry) {
46 if (used_aliases.find(x: entry) != used_aliases.end()) {
47 // the alias has already been bound to before!
48 // this happens if we group on the same alias twice
49 // e.g. GROUP BY k, k or GROUP BY 1, 1
50 // in this case, we can just replace the grouping with a constant since the second grouping has no effect
51 // (the constant grouping will be optimized out later)
52 return BindResult(make_uniq<BoundConstantExpression>(args: Value::INTEGER(value: 42)));
53 }
54 if (entry >= node.select_list.size()) {
55 throw BinderException("GROUP BY term out of range - should be between 1 and %d", (int)node.select_list.size());
56 }
57 // we replace the root expression, also replace the unbound expression
58 unbound_expression = node.select_list[entry]->Copy();
59 // move the expression that this refers to here and bind it
60 auto select_entry = std::move(node.select_list[entry]);
61 auto binding = Bind(expr&: select_entry, result_type: nullptr, root_expression: false);
62 // now replace the original expression in the select list with a reference to this group
63 group_alias_map[to_string(val: entry)] = bind_index;
64 node.select_list[entry] = make_uniq<ColumnRefExpression>(args: to_string(val: entry));
65 // insert into the set of used aliases
66 used_aliases.insert(x: entry);
67 return BindResult(std::move(binding));
68}
69
70BindResult GroupBinder::BindConstant(ConstantExpression &constant) {
71 // constant as root expression
72 if (!constant.value.type().IsIntegral()) {
73 // non-integral expression, we just leave the constant here.
74 return ExpressionBinder::BindExpression(expr&: constant, depth: 0);
75 }
76 // INTEGER constant: we use the integer as an index into the select list (e.g. GROUP BY 1)
77 auto index = (idx_t)constant.value.GetValue<int64_t>();
78 return BindSelectRef(entry: index - 1);
79}
80
81BindResult GroupBinder::BindColumnRef(ColumnRefExpression &colref) {
82 // columns in GROUP BY clauses:
83 // FIRST refer to the original tables, and
84 // THEN if no match is found refer to aliases in the SELECT list
85 // THEN if no match is found, refer to outer queries
86
87 // first try to bind to the base columns (original tables)
88 auto result = ExpressionBinder::BindExpression(expr&: colref, depth: 0);
89 if (result.HasError()) {
90 if (colref.IsQualified()) {
91 // explicit table name: not an alias reference
92 return result;
93 }
94 // failed to bind the column and the node is the root expression with depth = 0
95 // check if refers to an alias in the select clause
96 auto alias_name = colref.column_names[0];
97 auto entry = alias_map.find(x: alias_name);
98 if (entry == alias_map.end()) {
99 // no matching alias found
100 return result;
101 }
102 result = BindResult(BindSelectRef(entry: entry->second));
103 if (!result.HasError()) {
104 group_alias_map[alias_name] = bind_index;
105 }
106 }
107 return result;
108}
109
110} // namespace duckdb
111