Skip to content

Commit

Permalink
Issue duckdb#13899: AsOf Unrelated Pushdown
Browse files Browse the repository at this point in the history
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#13813
fixes: duckdblabs/duckdb-internal#3048
  • Loading branch information
hawkfish committed Sep 16, 2024
1 parent e5e1595 commit 2103713
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 1 deletion.
28 changes: 27 additions & 1 deletion src/optimizer/pushdown/pushdown_cross_product.cpp
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -22,6 +24,10 @@ unique_ptr<LogicalOperator> FilterPushdown::PushdownCrossProduct(unique_ptr<Logi
}
unordered_set<idx_t> left_bindings, right_bindings;
if (!filters.empty()) {
// We can only push down right side AsOf expressions
// that do not reference the inequality column
vector<unique_ptr<Filter>> asof_filters;
optional_ptr<Expression> 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);
Expand All @@ -37,16 +43,36 @@ unique_ptr<LogicalOperator> FilterPushdown::PushdownCrossProduct(unique_ptr<Logi
if (join_ref_type == JoinRefType::ASOF) {
// AsOf is really a table lookup, so we don't push filters
// down into the lookup (right) table
join_expressions.push_back(std::move(f->filter));
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<BoundComparisonExpression>().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]));
Expand Down
43 changes: 43 additions & 0 deletions test/sql/join/asof/test_asof_join_pushdown.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 2103713

Please sign in to comment.