|
6 | 6 | #include "duckdb/planner/expression/bound_constant_expression.hpp" |
7 | 7 | #include "duckdb/planner/expression/bound_function_expression.hpp" |
8 | 8 | #include "duckdb/planner/expression/bound_operator_expression.hpp" |
9 | | -#include "duckdb/planner/expression/bound_reference_expression.hpp" |
10 | | -#include "duckdb/planner/filter/conjunction_filter.hpp" |
11 | | -#include "duckdb/planner/filter/constant_filter.hpp" |
12 | 9 | #include "duckdb/planner/filter/expression_filter.hpp" |
13 | | -#include "duckdb/planner/filter/in_filter.hpp" |
14 | | -#include "duckdb/planner/filter/optional_filter.hpp" |
15 | | -#include "duckdb/planner/filter/struct_filter.hpp" |
| 10 | +#include "duckdb/planner/filter/table_filter_functions.hpp" |
16 | 11 |
|
17 | 12 | namespace duckdb { |
18 | 13 |
|
@@ -101,6 +96,45 @@ py::object TransformExpression(const Expression &expression, const vector<string |
101 | 96 | return EmitCompare(backend, expression_type, std::move(col), constant_side->value, resolved.leaf_type, |
102 | 97 | timezone_config); |
103 | 98 | } |
| 99 | + |
| 100 | + // Internal table-filter functions. Since the table-filter -> expression-filter |
| 101 | + // migration in core, optional / dynamic / bloom / perfect-hash-join / prefix-range |
| 102 | + // filters no longer have dedicated TableFilter subtypes. They arrive as scalar |
| 103 | + // function wrappers inside the ExpressionFilter expression tree (see |
| 104 | + // table_filter_functions.hpp). |
| 105 | + const auto &func_name = bound_function_expression.function.GetName(); |
| 106 | + |
| 107 | + // OPTIONAL / SELECTIVITY_OPTIONAL wrap a child predicate that lives in `bind_info` |
| 108 | + // (their `children` hold only a placeholder column ref). An optional filter is never |
| 109 | + // required for correctness, so if its child can't be translated we push nothing for |
| 110 | + // it rather than failing the whole scan. |
| 111 | + if (func_name == OptionalFilterScalarFun::NAME || func_name == SelectivityOptionalFilterScalarFun::NAME) { |
| 112 | + optional_ptr<const Expression> child; |
| 113 | + if (bound_function_expression.bind_info) { |
| 114 | + if (func_name == OptionalFilterScalarFun::NAME) { |
| 115 | + child = |
| 116 | + bound_function_expression.bind_info->Cast<OptionalFilterFunctionData>().child_filter_expr.get(); |
| 117 | + } else { |
| 118 | + child = bound_function_expression.bind_info->Cast<SelectivityOptionalFilterFunctionData>() |
| 119 | + .child_filter_expr.get(); |
| 120 | + } |
| 121 | + } |
| 122 | + if (!child) { |
| 123 | + return py::none(); |
| 124 | + } |
| 125 | + try { |
| 126 | + return TransformExpression(*child, column_path, backend, arrow_type, timezone_config); |
| 127 | + } catch (const NotImplementedException &) { |
| 128 | + return py::none(); |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + // DYNAMIC / BLOOM / PERFECT_HASH_JOIN / PREFIX_RANGE are runtime filters with no |
| 133 | + // static pyarrow/polars equivalent. They are not required for correctness (the |
| 134 | + // engine applies them above the scan), so skip them. |
| 135 | + if (TableFilterFunctions::IsTableFilterFunction(func_name)) { |
| 136 | + return py::none(); |
| 137 | + } |
104 | 138 | } |
105 | 139 |
|
106 | 140 | if (expression_class == ExpressionClass::BOUND_OPERATOR) { |
@@ -130,19 +164,27 @@ py::object TransformExpression(const Expression &expression, const vector<string |
130 | 164 |
|
131 | 165 | if (expression_class == ExpressionClass::BOUND_CONJUNCTION) { |
132 | 166 | if (expression_type == ExpressionType::CONJUNCTION_OR || expression_type == ExpressionType::CONJUNCTION_AND) { |
| 167 | + const bool is_and = expression_type == ExpressionType::CONJUNCTION_AND; |
133 | 168 | auto &conj_expr = expression.Cast<BoundConjunctionExpression>(); |
134 | 169 | py::object result = py::none(); |
135 | 170 | for (idx_t i = 0; i < conj_expr.children.size(); i++) { |
136 | 171 | py::object child_expression = |
137 | 172 | TransformExpression(*conj_expr.children[i], column_path, backend, arrow_type, timezone_config); |
138 | 173 | if (child_expression.is(py::none())) { |
139 | | - // An OR branch that can't be translated (e.g. DYNAMIC_FILTER) means the pushed-down |
140 | | - // predicate would be stricter than the engine intends — fall back to no pushdown. |
| 174 | + if (is_and) { |
| 175 | + // A conjunct we can't push can simply be dropped: the remaining AND |
| 176 | + // terms still form a correct (if weaker) filter, and the engine |
| 177 | + // re-applies the rest above the scan. |
| 178 | + continue; |
| 179 | + } |
| 180 | + // An OR branch that can't be translated (e.g. a dynamic filter) would |
| 181 | + // make the pushed-down predicate stricter than the engine intends — |
| 182 | + // fall back to no pushdown for the whole disjunction. |
141 | 183 | return py::none(); |
142 | 184 | } |
143 | 185 | if (result.is(py::none())) { |
144 | 186 | result = std::move(child_expression); |
145 | | - } else if (expression_type == ExpressionType::CONJUNCTION_AND) { |
| 187 | + } else if (is_and) { |
146 | 188 | result = backend.And(std::move(result), std::move(child_expression)); |
147 | 189 | } else { |
148 | 190 | result = backend.Or(std::move(result), std::move(child_expression)); |
|
0 commit comments