From 2103713c2d0fcd07b286c5b6e193cdeb1692335a Mon Sep 17 00:00:00 2001 From: Richard Wesley <13156216+hawkfish@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:30:27 -0700 Subject: [PATCH] Issue #13899: AsOf Unrelated Pushdown Push down right side predicates that do not reference the inequality column. This appears to be what other systems do, but the optimiser doesn't know this, so we have to require it for testing. fixes: duckdb/duckdb#13813 fixes: duckdblabs/duckdb-internal#3048 --- .../pushdown/pushdown_cross_product.cpp | 28 +++++++++++- .../join/asof/test_asof_join_pushdown.test | 43 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/optimizer/pushdown/pushdown_cross_product.cpp b/src/optimizer/pushdown/pushdown_cross_product.cpp index a05da2c27b8..73f7b47ac8f 100644 --- a/src/optimizer/pushdown/pushdown_cross_product.cpp +++ b/src/optimizer/pushdown/pushdown_cross_product.cpp @@ -1,4 +1,6 @@ #include "duckdb/optimizer/filter_pushdown.hpp" +#include "duckdb/planner/expression/bound_comparison_expression.hpp" +#include "duckdb/planner/expression_iterator.hpp" #include "duckdb/planner/operator/logical_comparison_join.hpp" #include "duckdb/planner/operator/logical_cross_product.hpp" @@ -22,6 +24,10 @@ unique_ptr FilterPushdown::PushdownCrossProduct(unique_ptr left_bindings, right_bindings; if (!filters.empty()) { + // We can only push down right side AsOf expressions + // that do not reference the inequality column + vector> asof_filters; + optional_ptr asof_compare; // check to see into which side we should push the filters // first get the LHS and RHS bindings LogicalJoin::GetTableReferences(*op->children[0], left_bindings); @@ -37,16 +43,36 @@ unique_ptr FilterPushdown::PushdownCrossProduct(unique_ptrfilter)); + asof_filters.push_back(std::move(f)); } else { right_pushdown.filters.push_back(std::move(f)); } } else { D_ASSERT(side == JoinSide::BOTH || side == JoinSide::NONE); + // Record the inequality binding for AsOf + if (join_ref_type == JoinRefType::ASOF && ExpressionType::COMPARE_LESSTHAN <= f->filter->type && + f->filter->type <= ExpressionType::COMPARE_GREATERTHANOREQUALTO) { + asof_compare = f->filter->Cast().right.get(); + } // bindings match both: turn into join condition join_expressions.push_back(std::move(f->filter)); } } + // Now that we know what the right side AsOf inequality expression is, + // we can push down any predicates that don't refer to it + for (auto &f : asof_filters) { + bool referenced = false; + ExpressionIterator::EnumerateExpression(f->filter, [&](const Expression &child) { + if (child.Equals(*asof_compare)) { + referenced = true; + } + }); + if (referenced) { + join_expressions.push_back(std::move(f->filter)); + } else { + right_pushdown.filters.push_back(std::move(f)); + } + } } op->children[0] = left_pushdown.Rewrite(std::move(op->children[0])); diff --git a/test/sql/join/asof/test_asof_join_pushdown.test b/test/sql/join/asof/test_asof_join_pushdown.test index ba298378c49..7bcbda159bb 100644 --- a/test/sql/join/asof/test_asof_join_pushdown.test +++ b/test/sql/join/asof/test_asof_join_pushdown.test @@ -79,3 +79,46 @@ ORDER BY ALL ---- 5 6 10 11 + +statement ok +CREATE TABLE issue13899(seq_no INT, amount DECIMAL(10,2)); + +statement ok +INSERT INTO issue13899 VALUES + (1,1.00), + (2,null), + (3,null), + (4,null), + (5,2.00), + (6,null), + (7,null), + (8,3.00), + (9,null), + (10,null), + (11,5.00); + +# Without the pushdown, the filter does not get applied properly +statement ok +PRAGMA disable_verification + +query III +SELECT + a.seq_no, + a.amount, + b.amount +FROM issue13899 AS a +ASOF JOIN issue13899 AS b + ON a.seq_no>=b.seq_no + AND b.amount IS NOT NULL; +---- +1 1.00 1.00 +2 NULL 1.00 +3 NULL 1.00 +4 NULL 1.00 +5 2.00 2.00 +6 NULL 2.00 +7 NULL 2.00 +8 3.00 3.00 +9 NULL 3.00 +10 NULL 3.00 +11 5.00 5.00