1#include "duckdb/optimizer/filter_pushdown.hpp"
2#include "duckdb/planner/expression/bound_operator_expression.hpp"
3#include "duckdb/planner/operator/logical_comparison_join.hpp"
4
5namespace duckdb {
6
7using Filter = FilterPushdown::Filter;
8
9unique_ptr<LogicalOperator> FilterPushdown::PushdownMarkJoin(unique_ptr<LogicalOperator> op,
10 unordered_set<idx_t> &left_bindings,
11 unordered_set<idx_t> &right_bindings) {
12 auto &join = op->Cast<LogicalJoin>();
13 auto &comp_join = op->Cast<LogicalComparisonJoin>();
14 D_ASSERT(join.join_type == JoinType::MARK);
15 D_ASSERT(op->type == LogicalOperatorType::LOGICAL_COMPARISON_JOIN ||
16 op->type == LogicalOperatorType::LOGICAL_DELIM_JOIN || op->type == LogicalOperatorType::LOGICAL_ASOF_JOIN);
17
18 right_bindings.insert(x: comp_join.mark_index);
19 FilterPushdown left_pushdown(optimizer), right_pushdown(optimizer);
20#ifdef DEBUG
21 bool simplified_mark_join = false;
22#endif
23 // now check the set of filters
24 for (idx_t i = 0; i < filters.size(); i++) {
25 auto side = JoinSide::GetJoinSide(bindings: filters[i]->bindings, left_bindings, right_bindings);
26 if (side == JoinSide::LEFT) {
27 // bindings match left side: push into left
28 left_pushdown.filters.push_back(x: std::move(filters[i]));
29 // erase the filter from the list of filters
30 filters.erase(position: filters.begin() + i);
31 i--;
32 } else if (side == JoinSide::RIGHT) {
33#ifdef DEBUG
34 D_ASSERT(!simplified_mark_join);
35#endif
36 // this filter references the marker
37 // we can turn this into a SEMI join if the filter is on only the marker
38 if (filters[i]->filter->type == ExpressionType::BOUND_COLUMN_REF) {
39 // filter just references the marker: turn into semi join
40#ifdef DEBUG
41 simplified_mark_join = true;
42#endif
43 join.join_type = JoinType::SEMI;
44 filters.erase(position: filters.begin() + i);
45 i--;
46 continue;
47 }
48 // if the filter is on NOT(marker) AND the join conditions are all set to "null_values_are_equal" we can
49 // turn this into an ANTI join if all join conditions have null_values_are_equal=true, then the result of
50 // the MARK join is always TRUE or FALSE, and never NULL this happens in the case of a correlated EXISTS
51 // clause
52 if (filters[i]->filter->type == ExpressionType::OPERATOR_NOT) {
53 auto &op_expr = filters[i]->filter->Cast<BoundOperatorExpression>();
54 if (op_expr.children[0]->type == ExpressionType::BOUND_COLUMN_REF) {
55 // the filter is NOT(marker), check the join conditions
56 bool all_null_values_are_equal = true;
57 for (auto &cond : comp_join.conditions) {
58 if (cond.comparison != ExpressionType::COMPARE_DISTINCT_FROM &&
59 cond.comparison != ExpressionType::COMPARE_NOT_DISTINCT_FROM) {
60 all_null_values_are_equal = false;
61 break;
62 }
63 }
64 if (all_null_values_are_equal) {
65#ifdef DEBUG
66 simplified_mark_join = true;
67#endif
68 // all null values are equal, convert to ANTI join
69 join.join_type = JoinType::ANTI;
70 filters.erase(position: filters.begin() + i);
71 i--;
72 continue;
73 }
74 }
75 }
76 }
77 }
78 op->children[0] = left_pushdown.Rewrite(op: std::move(op->children[0]));
79 op->children[1] = right_pushdown.Rewrite(op: std::move(op->children[1]));
80 return PushFinalFilters(op: std::move(op));
81}
82
83} // namespace duckdb
84