Coverage Report

Created: 2026-07-07 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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