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