Coverage Report

Created: 2026-11-11 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/duckdb/src/optimizer/aggregate_rewrite_helper.cpp
Line
Count
Source
1
#include "duckdb/optimizer/aggregate_rewrite_helper.hpp"
2
3
#include "duckdb/common/string_util.hpp"
4
#include "duckdb/optimizer/optimizer.hpp"
5
#include "duckdb/planner/binder.hpp"
6
#include "duckdb/planner/expression/bound_aggregate_expression.hpp"
7
#include "duckdb/planner/expression/bound_columnref_expression.hpp"
8
#include "duckdb/planner/expression/bound_reference_expression.hpp"
9
#include "duckdb/planner/expression_iterator.hpp"
10
#include "duckdb/planner/operator/logical_aggregate.hpp"
11
#include "duckdb/planner/operator/logical_cteref.hpp"
12
#include "duckdb/planner/operator/logical_projection.hpp"
13
14
namespace duckdb {
15
16
0
optional_idx AggregateRewriteHelper::GetDirectReferenceIndex(const Expression &expression, LogicalOperator &input) {
17
0
  auto bindings = input.GetColumnBindings();
18
0
  if (expression.GetExpressionClass() == ExpressionClass::BOUND_REF) {
19
0
    auto index = expression.Cast<BoundReferenceExpression>().Index();
20
0
    return index < bindings.size() ? optional_idx(index) : optional_idx();
21
0
  }
22
0
  if (expression.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF) {
23
0
    return optional_idx();
24
0
  }
25
0
  auto binding = expression.Cast<BoundColumnRefExpression>().Binding();
26
0
  for (idx_t index = 0; index < bindings.size(); index++) {
27
0
    if (bindings[index] == binding) {
28
0
      return optional_idx(index);
29
0
    }
30
0
  }
31
0
  return optional_idx();
32
0
}
33
34
0
vector<Identifier> AggregateRewriteHelper::GenerateColumnNames(const string &prefix, idx_t column_count) {
35
0
  vector<Identifier> result;
36
0
  result.reserve(column_count);
37
0
  for (idx_t col_idx = 0; col_idx < column_count; col_idx++) {
38
0
    result.emplace_back(StringUtil::Format("%s_%llu", prefix, col_idx));
39
0
  }
40
0
  return result;
41
0
}
42
43
0
static void RebindExpression(unique_ptr<Expression> &expr, const column_binding_map_t<ColumnBinding> &replacement_map) {
44
0
  if (!expr || replacement_map.empty()) {
45
0
    return;
46
0
  }
47
0
  ExpressionIterator::VisitExpressionMutable<BoundColumnRefExpression>(
48
0
      expr, [&](BoundColumnRefExpression &colref, unique_ptr<Expression> &) {
49
0
        auto entry = replacement_map.find(colref.Binding());
50
0
        if (entry != replacement_map.end()) {
51
0
          colref.BindingMutable() = entry->second;
52
0
        }
53
0
      });
54
0
}
55
56
unique_ptr<Expression>
57
AggregateRewriteHelper::CopyAndRebind(const Expression &expr,
58
0
                                      const column_binding_map_t<ColumnBinding> &replacement_map) {
59
0
  auto result = expr.Copy();
60
0
  RebindExpression(result, replacement_map);
61
0
  return result;
62
0
}
63
64
static void StageVolatileExpression(unique_ptr<Expression> &expr, TableIndex projection_index,
65
0
                                    vector<unique_ptr<Expression>> &projection_expressions) {
66
0
  if (!expr || !expr->IsVolatile()) {
67
0
    return;
68
0
  }
69
0
  auto return_type = expr->GetReturnType();
70
0
  const auto column_index = projection_expressions.size();
71
0
  projection_expressions.push_back(std::move(expr));
72
0
  expr = make_uniq<BoundColumnRefExpression>(return_type,
73
0
                                             ColumnBinding(projection_index, ProjectionIndex(column_index)));
74
0
}
75
76
void AggregateRewriteHelper::StageVolatileAggregateInputs(Optimizer &optimizer, LogicalAggregate &aggr,
77
0
                                                          unique_ptr<LogicalOperator> &child) {
78
  // Branching rewrites must not multiply the evaluation of volatile aggregate inputs.
79
0
  bool has_volatile_input = false;
80
0
  for (auto &group : aggr.groups) {
81
0
    has_volatile_input |= group->IsVolatile();
82
0
  }
83
0
  for (auto &expr : aggr.expressions) {
84
0
    has_volatile_input |= expr->IsVolatile();
85
0
  }
86
0
  if (!has_volatile_input) {
87
0
    return;
88
0
  }
89
90
0
  auto child_bindings = child->GetColumnBindings();
91
0
  auto projection_index = optimizer.binder.GenerateTableIndex();
92
0
  auto projection = LogicalProjection::CreateIdentity(projection_index, std::move(child));
93
94
0
  column_binding_map_t<ColumnBinding> projection_replacements;
95
0
  for (idx_t col_idx = 0; col_idx < child_bindings.size(); col_idx++) {
96
0
    projection_replacements[child_bindings[col_idx]] = ColumnBinding(projection_index, ProjectionIndex(col_idx));
97
0
  }
98
99
0
  for (auto &group : aggr.groups) {
100
0
    StageVolatileExpression(group, projection_index, projection->expressions);
101
0
  }
102
0
  for (auto &expr : aggr.expressions) {
103
0
    auto &aggregate = expr->Cast<BoundAggregateExpression>();
104
0
    for (auto &child_expr : aggregate.GetChildrenMutable()) {
105
0
      StageVolatileExpression(child_expr, projection_index, projection->expressions);
106
0
    }
107
0
    if (aggregate.GetOrderBys()) {
108
0
      for (auto &order : aggregate.GetOrderBysMutable()->orders) {
109
0
        StageVolatileExpression(order.expression, projection_index, projection->expressions);
110
0
      }
111
0
    }
112
0
    StageVolatileExpression(aggregate.GetFilterMutable(), projection_index, projection->expressions);
113
0
  }
114
115
0
  for (auto &group : aggr.groups) {
116
0
    RebindExpression(group, projection_replacements);
117
0
  }
118
0
  for (auto &expr : aggr.expressions) {
119
0
    RebindExpression(expr, projection_replacements);
120
0
  }
121
122
0
  child = std::move(projection);
123
0
}
124
125
unique_ptr<LogicalOperator> AggregateRewriteHelper::CreateCTERef(Optimizer &optimizer, TableIndex cte_index,
126
                                                                 const vector<LogicalType> &input_types,
127
                                                                 const vector<Identifier> &input_names,
128
                                                                 const vector<ColumnBinding> &input_bindings,
129
0
                                                                 column_binding_map_t<ColumnBinding> &replacement_map) {
130
0
  auto cte_ref_index = optimizer.binder.GenerateTableIndex();
131
0
  for (idx_t col_idx = 0; col_idx < input_bindings.size(); col_idx++) {
132
0
    replacement_map[input_bindings[col_idx]] = ColumnBinding(cte_ref_index, ProjectionIndex(col_idx));
133
0
  }
134
0
  return make_uniq<LogicalCTERef>(cte_ref_index, cte_index, input_types, input_names);
135
0
}
136
137
} // namespace duckdb