Coverage Report

Created: 2026-07-07 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/duckdb/src/optimizer/rule/predicate_factoring.cpp
Line
Count
Source
1
#include "duckdb/optimizer/rule/predicate_factoring.hpp"
2
3
#include "duckdb/planner/expression/bound_conjunction_expression.hpp"
4
#include "duckdb/planner/expression/bound_columnref_expression.hpp"
5
#include "duckdb/planner/expression_iterator.hpp"
6
#include "duckdb/planner/column_binding_map.hpp"
7
8
namespace duckdb {
9
10
72.9k
PredicateFactoringRule::PredicateFactoringRule(ExpressionRewriter &rewriter) : Rule(rewriter) {
11
  // Match on a ConjunctionExpression that has at least one ConjunctionExpression as a child
12
72.9k
  auto op = make_uniq<ConjunctionExpressionMatcher>();
13
72.9k
  op->matchers.push_back(make_uniq<ConjunctionExpressionMatcher>());
14
72.9k
  op->policy = SetMatcher::Policy::SOME;
15
72.9k
  root = std::move(op);
16
72.9k
}
17
18
0
static bool ExpressionIsDisjunction(const Expression &expr) {
19
0
  return expr.GetExpressionClass() == ExpressionClass::BOUND_CONJUNCTION &&
20
0
         expr.GetExpressionType() == ExpressionType::CONJUNCTION_OR;
21
0
}
22
23
0
static void ExtractDisjunctedPredicates(Expression &expression, vector<reference<Expression>> &disjuncted_children) {
24
0
  if (ExpressionIsDisjunction(expression)) {
25
0
    auto &disjunction = expression.Cast<BoundConjunctionExpression>();
26
0
    for (auto &child : disjunction.GetChildren()) {
27
0
      ExtractDisjunctedPredicates(*child, disjuncted_children);
28
0
    }
29
0
  } else {
30
0
    disjuncted_children.push_back(expression);
31
0
  }
32
0
}
33
34
0
static bool ColumnBindingIsvalid(const ColumnBinding &column_binding) {
35
0
  return column_binding.table_index.IsValid() && column_binding.column_index.IsValid();
36
0
}
37
38
0
static bool GetSingleColumnBinding(const Expression &expr, ColumnBinding &column_binding) {
39
0
  if (expr.IsVolatile()) {
40
0
    return false;
41
0
  }
42
43
0
  column_binding = ColumnBinding();
44
45
0
  bool found_multiple = false;
46
0
  ExpressionIterator::VisitExpression<BoundColumnRefExpression>(expr, [&](const BoundColumnRefExpression &colref) {
47
0
    if (!ColumnBindingIsvalid(column_binding)) {
48
0
      column_binding = colref.Binding();
49
0
    } else if (column_binding != colref.Binding()) {
50
0
      found_multiple = true;
51
0
    }
52
0
  });
53
54
0
  return ColumnBindingIsvalid(column_binding) && !found_multiple;
55
0
}
56
57
static void ExtractDisjunctedPredicates(Expression &expression,
58
                                        column_binding_map_t<vector<reference<Expression>>> &binding_map);
59
60
static void ExtractConjunctedPredicates(BoundConjunctionExpression &conjunction,
61
0
                                        column_binding_map_t<vector<reference<Expression>>> &binding_map) {
62
0
  D_ASSERT(conjunction.GetExpressionType() == ExpressionType::CONJUNCTION_AND);
63
0
  for (auto &conjunction_child : conjunction.GetChildren()) {
64
0
    if (conjunction_child->GetExpressionClass() == ExpressionClass::BOUND_CONJUNCTION &&
65
0
        conjunction_child->GetExpressionType() == ExpressionType::CONJUNCTION_AND) {
66
0
      ExtractConjunctedPredicates(conjunction_child->Cast<BoundConjunctionExpression>(), binding_map);
67
0
    } else {
68
0
      ExtractDisjunctedPredicates(*conjunction_child, binding_map);
69
0
    }
70
0
  }
71
0
}
72
73
static void ExtractDisjunctedPredicates(Expression &expression,
74
0
                                        column_binding_map_t<vector<reference<Expression>>> &binding_map) {
75
0
  if (expression.GetExpressionClass() == ExpressionClass::BOUND_CONJUNCTION &&
76
0
      expression.GetExpressionType() == ExpressionType::CONJUNCTION_AND) {
77
0
    ExtractConjunctedPredicates(expression.Cast<BoundConjunctionExpression>(), binding_map);
78
0
  } else {
79
0
    ColumnBinding single_binding;
80
0
    if (GetSingleColumnBinding(expression, single_binding)) {
81
0
      binding_map[single_binding].push_back(expression);
82
0
    }
83
0
  }
84
0
}
85
86
0
static column_binding_map_t<vector<reference<Expression>>> GetDisjunctedPredicateMap(Expression &expression) {
87
0
  vector<reference<Expression>> disjuncted_children;
88
0
  ExtractDisjunctedPredicates(expression, disjuncted_children);
89
0
  D_ASSERT(disjuncted_children.size() > 1);
90
91
  // Extract predicates of the first child
92
0
  auto &first_child = disjuncted_children[0].get();
93
0
  D_ASSERT(!ExpressionIsDisjunction(first_child));
94
0
  column_binding_map_t<vector<reference<Expression>>> remaining_binding_map;
95
0
  ExtractDisjunctedPredicates(first_child, remaining_binding_map);
96
97
0
  for (idx_t child_idx = 1; child_idx < disjuncted_children.size(); child_idx++) {
98
0
    auto &child = disjuncted_children[child_idx].get();
99
0
    D_ASSERT(!ExpressionIsDisjunction(child));
100
0
    column_binding_map_t<vector<reference<Expression>>> child_binding_map;
101
0
    ExtractDisjunctedPredicates(child, child_binding_map);
102
103
    // Bindings must appear in both maps to be considered for predicate factoring
104
0
    for (auto it = remaining_binding_map.begin(); it != remaining_binding_map.end();) {
105
0
      auto child_it = child_binding_map.find(it->first);
106
0
      if (child_it == child_binding_map.end()) {
107
0
        it = remaining_binding_map.erase(it);
108
0
      } else {
109
0
        for (auto &new_predicate : child_it->second) {
110
0
          bool found = false;
111
0
          for (auto &existing_predicate : it->second) {
112
0
            if (new_predicate.get().Equals(existing_predicate.get())) {
113
0
              found = true;
114
0
              break;
115
0
            }
116
0
          }
117
0
          if (!found) {
118
0
            it->second.push_back(new_predicate.get());
119
0
          }
120
0
        }
121
0
        it++;
122
0
      }
123
0
    }
124
0
  }
125
126
0
  return remaining_binding_map;
127
0
}
128
129
unique_ptr<Expression> PredicateFactoringRule::Apply(LogicalOperator &op, vector<reference<Expression>> &bindings,
130
0
                                                     bool &changes_made, bool is_root) {
131
  // Only applies to top-level FILTER expressions
132
0
  if ((op.type != LogicalOperatorType::LOGICAL_FILTER && op.type != LogicalOperatorType::LOGICAL_ANY_JOIN) ||
133
0
      !is_root) {
134
0
    return nullptr;
135
0
  }
136
137
  // The expression must be an OR TODO: could also implement some common expression extraction for AND
138
0
  if (!ExpressionIsDisjunction(bindings[0])) {
139
0
    return nullptr;
140
0
  }
141
142
0
  ColumnBinding column_binding;
143
0
  if (GetSingleColumnBinding(bindings[0], column_binding)) {
144
0
    return nullptr; // If it only applies to one column binding it can be pushed down already
145
0
  }
146
147
  // Extract map from single column binding to expression it is contained in
148
0
  const auto binding_map = GetDisjunctedPredicateMap(bindings[0].get());
149
0
  if (binding_map.empty()) {
150
0
    return nullptr; // None qualify
151
0
  }
152
153
0
  unique_ptr<Expression> derived_filter;
154
0
  for (auto &entry : binding_map) {
155
0
    D_ASSERT(!entry.second.empty());
156
157
    // Create disjunction on single-column predicates
158
0
    unique_ptr<Expression> column_filter;
159
0
    for (auto &expr : entry.second) {
160
0
      if (!column_filter) {
161
0
        column_filter = expr.get().Copy();
162
0
      } else {
163
0
        auto new_disjunction = make_uniq<BoundConjunctionExpression>(ExpressionType::CONJUNCTION_OR);
164
0
        new_disjunction->GetChildrenMutable().push_back(std::move(column_filter));
165
0
        new_disjunction->GetChildrenMutable().push_back(expr.get().Copy());
166
0
        column_filter = std::move(new_disjunction);
167
0
      }
168
0
    }
169
170
    // Conjunct each single-column predicate together
171
0
    if (!derived_filter) {
172
0
      derived_filter = std::move(column_filter);
173
0
    } else {
174
0
      auto new_conjunction = make_uniq<BoundConjunctionExpression>(ExpressionType::CONJUNCTION_AND);
175
0
      new_conjunction->GetChildrenMutable().push_back(std::move(column_filter));
176
0
      new_conjunction->GetChildrenMutable().push_back(std::move(derived_filter));
177
0
      derived_filter = std::move(new_conjunction);
178
0
    }
179
0
  }
180
181
  // Now add the derived filter as an AND to the original expression
182
0
  auto result = make_uniq<BoundConjunctionExpression>(ExpressionType::CONJUNCTION_AND);
183
0
  result->GetChildrenMutable().push_back(bindings[0].get().Copy());
184
0
  result->GetChildrenMutable().push_back(std::move(derived_filter));
185
0
  return std::move(result);
186
0
}
187
188
} // namespace duckdb