/src/duckdb/src/planner/expression_binder/group_binder.cpp
Line | Count | Source |
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/planner/expression_binder/select_bind_state.hpp" |
8 | | #include "duckdb/planner/query_node/bound_select_node.hpp" |
9 | | #include "duckdb/common/to_string.hpp" |
10 | | #include "duckdb/common/string_util.hpp" |
11 | | |
12 | | namespace duckdb { |
13 | | |
14 | | GroupBinder::GroupBinder(Binder &binder, ClientContext &context, BoundSelectNode &node, SelectBindState &bind_state) |
15 | 0 | : ExpressionBinder(binder, context), node(node), bind_state(bind_state) { |
16 | 0 | } |
17 | | |
18 | 0 | BindResult GroupBinder::BindExpression(unique_ptr<ParsedExpression> &expr_ptr, idx_t depth, bool root_expression) { |
19 | 0 | auto &expr = *expr_ptr; |
20 | 0 | switch (expr.GetExpressionClass()) { |
21 | 0 | case ExpressionClass::DEFAULT: |
22 | 0 | return BindUnsupportedExpression(expr, depth, "GROUP BY clause cannot contain DEFAULT clause"); |
23 | 0 | case ExpressionClass::WINDOW: |
24 | 0 | return BindUnsupportedExpression(expr, depth, "GROUP BY clause cannot contain window functions!"); |
25 | 0 | default: |
26 | 0 | return ExpressionBinder::BindExpression(expr_ptr, depth, root_expression); |
27 | 0 | } |
28 | 0 | } |
29 | | |
30 | 0 | string GroupBinder::UnsupportedAggregateMessage() { |
31 | 0 | return "GROUP BY clause cannot contain aggregates!"; |
32 | 0 | } |
33 | | |
34 | 0 | void GroupBinder::ThrowIfUnnestInLambda(const ColumnBinding &column_binding) { |
35 | 0 | for (auto &node_pair : node.unnests.GroupBy()) { |
36 | 0 | auto &unnest_node = node_pair.second; |
37 | 0 | if (unnest_node.index == column_binding.table_index && |
38 | 0 | column_binding.column_index < unnest_node.expressions.size()) { |
39 | 0 | throw BinderException("UNNEST in lambda expressions is not supported"); |
40 | 0 | } |
41 | 0 | } |
42 | 0 | } |
43 | | |
44 | | void GroupBinder::ReplaceSelectRef(SelectNode &node, SelectBindState &bind_state, ProjectionIndex group_index, |
45 | 0 | unique_ptr<ParsedExpression> &expr_ptr) { |
46 | 0 | auto &expr = *expr_ptr; |
47 | 0 | idx_t select_list_idx; |
48 | 0 | switch (expr.GetExpressionClass()) { |
49 | 0 | case ExpressionClass::COLUMN_REF: { |
50 | | // root is a column - check if it refers to an alias in the select list |
51 | 0 | auto &colref = expr.Cast<ColumnRefExpression>(); |
52 | 0 | if (!IsPotentialAlias(colref)) { |
53 | 0 | return; |
54 | 0 | } |
55 | 0 | auto &alias_name = colref.GetColumnName(); |
56 | 0 | auto entry = bind_state.alias_map.find(alias_name); |
57 | 0 | if (entry == bind_state.alias_map.end()) { |
58 | | // no matching alias found |
59 | 0 | return; |
60 | 0 | } |
61 | 0 | select_list_idx = entry->second; |
62 | 0 | bind_state.group_alias_map[alias_name] = group_index; |
63 | 0 | break; |
64 | 0 | } |
65 | 0 | case ExpressionClass::CONSTANT: { |
66 | | // root is a constant |
67 | 0 | auto &constant = expr.Cast<ConstantExpression>(); |
68 | 0 | if (!constant.GetValue().type().IsIntegral()) { |
69 | | // non-integral expression, we just leave the constant here. |
70 | 0 | return; |
71 | 0 | } |
72 | | // INTEGER constant: we use the integer as an index into the select list (e.g. GROUP BY 1) |
73 | 0 | auto index = (idx_t)constant.GetValue().GetValue<int64_t>(); |
74 | 0 | select_list_idx = index - 1; |
75 | 0 | break; |
76 | 0 | } |
77 | 0 | case ExpressionClass::PARAMETER: |
78 | 0 | throw ParameterNotAllowedException("Parameter not supported in GROUP BY clause"); |
79 | 0 | default: |
80 | 0 | return; |
81 | 0 | } |
82 | | // this group entry directly refers to a select list expression |
83 | | // we need to switch them around - the select list expression needs to become a group |
84 | | // it will then instead refer to the group entry |
85 | | |
86 | | // check if we already have a reference to this group |
87 | 0 | auto &used_aliases = bind_state.used_group_aliases; |
88 | 0 | if (used_aliases.find(select_list_idx) != used_aliases.end()) { |
89 | | // the alias has already been bound to before! |
90 | | // this happens if we group on the same alias twice |
91 | | // e.g. GROUP BY k, k or GROUP BY 1, 1 |
92 | | // in this case, we can just replace the grouping with a constant since the second grouping has no effect |
93 | | // (the constant grouping will be optimized out later) |
94 | 0 | expr_ptr = make_uniq<ConstantExpression>(Value::INTEGER(42)); |
95 | 0 | return; |
96 | 0 | } |
97 | 0 | if (select_list_idx >= node.select_list.size()) { |
98 | 0 | throw BinderException("GROUP BY term out of range - should be between 1 and %d", (int)node.select_list.size()); |
99 | 0 | } |
100 | | // move the expression that this refers to into the group expression |
101 | 0 | expr_ptr = std::move(node.select_list[select_list_idx]); |
102 | | // now replace the original expression in the select list with a reference to this group |
103 | 0 | bind_state.group_alias_map[Identifier(to_string(select_list_idx))] = group_index; |
104 | 0 | node.select_list[select_list_idx] = make_uniq<ColumnRefExpression>(Identifier(to_string(select_list_idx))); |
105 | | // insert into the set of used aliases |
106 | 0 | used_aliases.insert(select_list_idx); |
107 | 0 | } |
108 | | |
109 | | bool GroupBinder::TryResolveAliasReference(ColumnRefExpression &colref, idx_t depth, bool root_expression, |
110 | 0 | BindResult &result, unique_ptr<ParsedExpression> &expr_ptr) { |
111 | | // try to resolve alias references in GROUP |
112 | | // failed to bind the column and the node is the root expression with depth = 0 |
113 | | // check if refers to an alias in the select clause |
114 | |
|
115 | 0 | auto &alias_name = colref.GetColumnName(); |
116 | 0 | auto entry = bind_state.alias_map.find(alias_name); |
117 | 0 | if (entry == bind_state.alias_map.end()) { |
118 | | // no matching alias found |
119 | 0 | return false; |
120 | 0 | } |
121 | 0 | result = BindResult(BinderException( |
122 | 0 | colref, "Alias with name \"%s\" exists, but aliases cannot be used as part of an expression in the GROUP BY", |
123 | 0 | alias_name)); |
124 | 0 | return true; |
125 | 0 | } |
126 | | |
127 | | } // namespace duckdb |