Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions axiom/optimizer/DerivedTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2405,6 +2405,25 @@ bool DerivedTable::addFilter(ExprCP conjunct) {
return true;
}

namespace {

// True if 'table' is the null-producing side of an outer join in 'dt'. Such a
// table cannot take on further join edges: any edge added to it would have to
// be applied after the outer join, which the join order search cannot express.
bool isNullProducingSide(const DerivedTableP dt, PlanObjectCP table) {
for (const auto* join : dt->joins) {
if (join->rightOptional() && join->rightTable() == table) {
return true;
}
if (join->leftOptional() && join->leftTable() == table) {
return true;
}
}
return false;
}

} // namespace

void DerivedTable::distributeConjuncts() {
if (!having.empty()) {
VELOX_CHECK_NOT_NULL(aggregation);
Expand Down Expand Up @@ -2503,6 +2522,14 @@ void DerivedTable::distributeConjuncts() {
continue;
}

if (isNullProducingSide(this, tables[0]) ||
isNullProducingSide(this, tables[1])) {
// The conjunct constrains a table whose join order position is already
// pinned by an outer join. Leave it in place to evaluate above that
// join.
continue;
}

ExprCP left = nullptr;
ExprCP right = nullptr;
// expr depends on 2 tables. If it is left = right or right = left and
Expand Down
7 changes: 4 additions & 3 deletions axiom/optimizer/tests/TpchPlanTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ TEST_F(TpchPlanTest, q01) {
TEST_F(TpchPlanTest, q02) {
// (
// ((partsupp INNER part) INNER (supplier INNER (nation INNER region)))
// INNER
// LEFT
// agg((
// (partsupp LEFT SEMI (FILTER) part)
// INNER
// (supplier INNER (nation INNER region))
// ))
// )
// ) FILTER ps_supplycost = min
auto matcher =
matchScan("partsupp")
.hashJoinInner(
Expand All @@ -164,7 +164,7 @@ TEST_F(TpchPlanTest, q02) {
matchScan("supplier")
.hashJoinInner(matchScan("nation").hashJoinInner(
matchScan("region").filter("r_name = 'EUROPE'"))))
.hashJoinInner(
.hashJoinLeft(
matchScan("partsupp")
.hashJoinLeftSemiFilter(matchScan("part").filter(
"p_size = 15 and p_type like '%BRASS'"))
Expand All @@ -176,6 +176,7 @@ TEST_F(TpchPlanTest, q02) {
.filter("region_name = 'EUROPE'"))))
.aggregation()
.project())
.filter("ps_supplycost = min")
.topN()
.project()
.build();
Expand Down
14 changes: 14 additions & 0 deletions axiom/optimizer/tests/sql/subquery.sql
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,20 @@ LEFT JOIN (VALUES (1, 'x')) AS u(k, b) ON t.a = u.k
INNER JOIN (VALUES (1)) AS v(c)
ON u.b IN (SELECT 'x' FROM (VALUES (1)) AS w(d) WHERE d = v.c)
----
-- A correlated scalar aggregate attaches to p through a LEFT join during
-- decorrelation. The comparison with sibling ps must remain a filter above
-- that join instead of becoming a second join edge on the null-producing
-- aggregate side.
SELECT ps.k, ps.cost
FROM (VALUES (1), (2)) AS p(k),
(VALUES (1, 100.0), (1, 200.0), (2, 50.0), (2, 30.0)) AS ps(k, cost)
WHERE p.k = ps.k
AND ps.cost = (
SELECT min(ps2.cost)
FROM (VALUES (1, 100.0), (1, 200.0), (2, 50.0), (2, 30.0)) AS ps2(k, cost)
WHERE ps2.k = p.k)
ORDER BY ps.k
----
-- Shared CTE with a nested-IN filter, referenced from both UNION legs,
-- second leg wrapping it in GROUP BY.
WITH s AS (
Expand Down
Loading