From 1db0ece5b63102127d3fbb9ad3f826286ea51009 Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Mon, 27 Nov 2023 17:15:44 +0100 Subject: [PATCH] Convert left correlated join to inner join during transformation It's possible to simplify outer join to inner join when subquery side is at least scalar. This makes sure dynamic filters are propagated to left join side and more efficient (cross join) implementaiton is used. --- .../TransformUncorrelatedSubqueryToJoin.java | 13 +++++++--- ...stTransformUncorrelatedSubqueryToJoin.java | 25 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/TransformUncorrelatedSubqueryToJoin.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/TransformUncorrelatedSubqueryToJoin.java index ea7a5458e893..987d8946f5fd 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/TransformUncorrelatedSubqueryToJoin.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/TransformUncorrelatedSubqueryToJoin.java @@ -21,6 +21,7 @@ import io.trino.matching.Pattern; import io.trino.spi.type.Type; import io.trino.sql.planner.Symbol; +import io.trino.sql.planner.iterative.Lookup; import io.trino.sql.planner.iterative.Rule; import io.trino.sql.planner.plan.Assignments; import io.trino.sql.planner.plan.CorrelatedJoinNode; @@ -36,6 +37,7 @@ import static com.google.common.base.Preconditions.checkState; import static io.trino.matching.Pattern.empty; import static io.trino.sql.analyzer.TypeSignatureTranslator.toSqlType; +import static io.trino.sql.planner.optimizations.QueryCardinalityUtil.extractCardinality; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.FULL; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.INNER; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.LEFT; @@ -64,7 +66,8 @@ public Result apply(CorrelatedJoinNode correlatedJoinNode, Captures captures, Co return Result.ofPlanNode(rewriteToJoin( correlatedJoinNode, correlatedJoinNode.getType().toJoinNodeType(), - correlatedJoinNode.getFilter())); + correlatedJoinNode.getFilter(), + context.getLookup())); } checkState( @@ -79,7 +82,7 @@ public Result apply(CorrelatedJoinNode correlatedJoinNode, Captures captures, Co else { type = JoinNode.Type.LEFT; } - JoinNode joinNode = rewriteToJoin(correlatedJoinNode, type, TRUE_LITERAL); + JoinNode joinNode = rewriteToJoin(correlatedJoinNode, type, TRUE_LITERAL, context.getLookup()); if (correlatedJoinNode.getFilter().equals(TRUE_LITERAL)) { return Result.ofPlanNode(joinNode); @@ -109,8 +112,12 @@ public Result apply(CorrelatedJoinNode correlatedJoinNode, Captures captures, Co return Result.empty(); } - private JoinNode rewriteToJoin(CorrelatedJoinNode parent, JoinNode.Type type, Expression filter) + private JoinNode rewriteToJoin(CorrelatedJoinNode parent, JoinNode.Type type, Expression filter, Lookup lookup) { + if (type == JoinNode.Type.LEFT && extractCardinality(parent.getSubquery(), lookup).isAtLeastScalar() && filter.equals(TRUE_LITERAL)) { + // input rows will always be matched against subquery rows + type = JoinNode.Type.INNER; + } return new JoinNode( parent.getId(), type, diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java index f88bde2a1f88..38854596bc1e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java @@ -25,7 +25,6 @@ import static io.trino.sql.planner.assertions.PlanMatchPattern.project; import static io.trino.sql.planner.assertions.PlanMatchPattern.values; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.FULL; -import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.INNER; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.LEFT; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.RIGHT; import static io.trino.sql.planner.plan.JoinNode.Type; @@ -36,6 +35,26 @@ public class TestTransformUncorrelatedSubqueryToJoin extends BaseRuleTest { + @Test + public void testRewriteLeftCorrelatedJoinWithScalarSubquery() + { + tester().assertThat(new TransformUncorrelatedSubqueryToJoin()) + .on(p -> { + Symbol a = p.symbol("a"); + Symbol b = p.symbol("b"); + return p.correlatedJoin( + emptyList(), + p.values(a), + LEFT, + TRUE_LITERAL, + p.values(1, b)); + }) + .matches( + join(Type.INNER, builder -> builder + .left(values("a")) + .right(values("b")))); + } + @Test public void testRewriteInnerCorrelatedJoin() { @@ -46,7 +65,7 @@ public void testRewriteInnerCorrelatedJoin() return p.correlatedJoin( emptyList(), p.values(a), - INNER, + LEFT, new ComparisonExpression( GREATER_THAN, b.toSymbolReference(), @@ -54,7 +73,7 @@ public void testRewriteInnerCorrelatedJoin() p.values(b)); }) .matches( - join(Type.INNER, builder -> builder + join(Type.LEFT, builder -> builder .filter("b > a") .left(values("a")) .right(values("b"))));