This is an automated email from the ASF dual-hosted git repository. hyuan pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push: new 39cb3e3 [CALCITE-3910] Enhance ProjectJoinTransposeRule to support SemiJoin and AntiJoin (Liya Fan) 39cb3e3 is described below commit 39cb3e3619a528bb39a677598f16a26f10afbfaf Author: liyafan82 <fan_li...@foxmail.com> AuthorDate: Wed Apr 15 15:38:38 2020 +0800 [CALCITE-3910] Enhance ProjectJoinTransposeRule to support SemiJoin and AntiJoin (Liya Fan) Close #1917 --- .../rel/rules/ProjectJoinTransposeRule.java | 4 -- .../apache/calcite/rel/rules/PushProjector.java | 33 ++++++--- .../org/apache/calcite/test/RelOptRulesTest.java | 78 ++++++++++++++++++++++ .../org/apache/calcite/test/RelOptRulesTest.xml | 58 ++++++++++++++++ 4 files changed, 158 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java index b975a3b..5d6510c 100644 --- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java +++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectJoinTransposeRule.java @@ -93,10 +93,6 @@ public class ProjectJoinTransposeRule extends RelOptRule implements Transformati Project origProj = call.rel(0); final Join join = call.rel(1); - if (!join.getJoinType().projectsRight()) { - return; // TODO: support SemiJoin / AntiJoin - } - // Normalize the join condition so we don't end up misidentified expanded // form of IS NOT DISTINCT FROM as PushProject also visit the filter condition // and push down expressions. diff --git a/core/src/main/java/org/apache/calcite/rel/rules/PushProjector.java b/core/src/main/java/org/apache/calcite/rel/rules/PushProjector.java index 7ec5482..fcbd60e 100644 --- a/core/src/main/java/org/apache/calcite/rel/rules/PushProjector.java +++ b/core/src/main/java/org/apache/calcite/rel/rules/PushProjector.java @@ -25,6 +25,7 @@ import org.apache.calcite.rel.core.Join; import org.apache.calcite.rel.core.JoinRelType; import org.apache.calcite.rel.core.Project; import org.apache.calcite.rel.core.SetOp; +import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexCall; @@ -40,6 +41,7 @@ import org.apache.calcite.util.Pair; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.BitSet; @@ -47,6 +49,8 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * PushProjector is a utility class used to perform operations used in push @@ -216,9 +220,14 @@ public class PushProjector { origProjExprs = origProj.getProjects(); } - childFields = childRel.getRowType().getFieldList(); + if (childRel instanceof Join) { + Join join = (Join) childRel; + childFields = Lists.newArrayList(join.getLeft().getRowType().getFieldList()); + childFields.addAll(join.getRight().getRowType().getFieldList()); + } else { + childFields = childRel.getRowType().getFieldList(); + } nChildFields = childFields.size(); - projRefs = new BitSet(nChildFields); if (childRel instanceof Join) { Join joinRel = (Join) childRel; @@ -227,14 +236,7 @@ public class PushProjector { List<RelDataTypeField> rightFields = joinRel.getRight().getRowType().getFieldList(); nFields = leftFields.size(); - switch (joinRel.getJoinType()) { - case SEMI: - case ANTI: - nFieldsRight = 0; - break; - default: - nFieldsRight = rightFields.size(); - } + nFieldsRight = rightFields.size(); nSysFields = joinRel.getSystemFieldList().size(); childBitmap = ImmutableBitSet.range(nSysFields, nFields + nSysFields); @@ -469,7 +471,8 @@ public class PushProjector { // referenced and there are no special preserve expressions; note // that we need to do this check after we've handled the 0-column // project cases - if (projRefs.cardinality() == nChildFields + boolean allFieldsReferenced = IntStream.range(0, nChildFields).allMatch(i -> projRefs.get(i)); + if (allFieldsReferenced && childPreserveExprs.size() == 0 && rightPreserveExprs.size() == 0) { return true; @@ -547,6 +550,14 @@ public class PushProjector { } else { newExpr = projExpr; } + + List<RelDataType> typeList = projChild.getRowType().getFieldList() + .stream().map(field -> field.getType()).collect(Collectors.toList()); + RexUtil.FixNullabilityShuttle fixer = + new RexUtil.FixNullabilityShuttle( + projChild.getCluster().getRexBuilder(), typeList); + newExpr = newExpr.accept(fixer); + newProjects.add( Pair.of( newExpr, diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java index 12ef011..9324d78 100644 --- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java @@ -868,6 +868,84 @@ class RelOptRulesTest extends RelOptTestBase { .check(); } + @Test void testSemiJoinProjectTranspose() { + final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build()); + // build a rel equivalent to sql: + // select a.name from dept a + // where a.deptno in (select b.deptno * 2 from dept); + + RelNode left = relBuilder.scan("DEPT").build(); + RelNode right = relBuilder.scan("DEPT") + .project( + relBuilder.call( + SqlStdOperatorTable.MULTIPLY, relBuilder.literal(2), relBuilder.field(0))) + .aggregate(relBuilder.groupKey(ImmutableBitSet.of(0))).build(); + + RelNode plan = relBuilder.push(left) + .push(right) + .semiJoin( + relBuilder.call(SqlStdOperatorTable.EQUALS, + relBuilder.field(2, 0, 0), + relBuilder.field(2, 1, 0))) + .project(relBuilder.field(1)) + .build(); + + final String planBefore = NL + RelOptUtil.toString(plan); + + HepProgram program = new HepProgramBuilder() + .addRuleInstance(ProjectJoinTransposeRule.INSTANCE) + .build(); + + HepPlanner hepPlanner = new HepPlanner(program); + hepPlanner.setRoot(plan); + RelNode output = hepPlanner.findBestExp(); + + final String planAfter = NL + RelOptUtil.toString(output); + final DiffRepository diffRepos = getDiffRepos(); + diffRepos.assertEquals("planBefore", "${planBefore}", planBefore); + diffRepos.assertEquals("planAfter", "${planAfter}", planAfter); + SqlToRelTestBase.assertValid(output); + } + + @Test void testAntiJoinProjectTranspose() { + final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build()); + // build a rel equivalent to sql: + // select a.name from dept a + // where a.deptno not in (select b.deptno * 2 from dept); + + RelNode left = relBuilder.scan("DEPT").build(); + RelNode right = relBuilder.scan("DEPT") + .project( + relBuilder.call( + SqlStdOperatorTable.MULTIPLY, relBuilder.literal(2), relBuilder.field(0))) + .aggregate(relBuilder.groupKey(ImmutableBitSet.of(0))).build(); + + RelNode plan = relBuilder.push(left) + .push(right) + .antiJoin( + relBuilder.call(SqlStdOperatorTable.EQUALS, + relBuilder.field(2, 0, 0), + relBuilder.field(2, 1, 0))) + .project(relBuilder.field(1)) + .build(); + + final String planBefore = NL + RelOptUtil.toString(plan); + + HepProgram program = new HepProgramBuilder() + .addRuleInstance(ProjectJoinTransposeRule.INSTANCE) + .build(); + + HepPlanner hepPlanner = new HepPlanner(program); + hepPlanner.setRoot(plan); + RelNode output = hepPlanner.findBestExp(); + + final String planAfter = NL + RelOptUtil.toString(output); + final DiffRepository diffRepos = getDiffRepos(); + diffRepos.assertEquals("planBefore", "${planBefore}", planBefore); + diffRepos.assertEquals("planAfter", "${planAfter}", planAfter); + SqlToRelTestBase.assertValid(output); + } + @Test void testJoinProjectTranspose1() { final HepProgram preProgram = HepProgram.builder() diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml index f5f3d3e..dd8a00a 100644 --- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml @@ -7402,6 +7402,64 @@ LogicalProject(NAME=[$1]) ]]> </Resource> </TestCase> + <TestCase name="testSemiJoinProjectTranspose"> + <Resource name="sql"> + <![CDATA[select a.name from dept a +where a.deptno in (select b.deptno * 2 from dept); +]]> + </Resource> + <Resource name="planBefore"> + <![CDATA[ +LogicalProject(DNAME=[$1]) + LogicalJoin(condition=[=($0, $3)], joinType=[semi]) + LogicalTableScan(table=[[scott, DEPT]]) + LogicalAggregate(group=[{0}]) + LogicalProject($f0=[*(2, $0)]) + LogicalTableScan(table=[[scott, DEPT]]) +]]> + </Resource> + <Resource name="planAfter"> + <![CDATA[ +LogicalProject(DNAME=[$1]) + LogicalJoin(condition=[=($0, $2)], joinType=[semi]) + LogicalProject(DEPTNO=[$0], DNAME=[$1]) + LogicalTableScan(table=[[scott, DEPT]]) + LogicalProject($f0=[$0]) + LogicalAggregate(group=[{0}]) + LogicalProject($f0=[*(2, $0)]) + LogicalTableScan(table=[[scott, DEPT]]) +]]> + </Resource> + </TestCase> + <TestCase name="testAntiJoinProjectTranspose"> + <Resource name="sql"> + <![CDATA[select a.name from dept a +where a.deptno not in (select b.deptno * 2 from dept); +]]> + </Resource> + <Resource name="planBefore"> + <![CDATA[ +LogicalProject(DNAME=[$1]) + LogicalJoin(condition=[=($0, $3)], joinType=[anti]) + LogicalTableScan(table=[[scott, DEPT]]) + LogicalAggregate(group=[{0}]) + LogicalProject($f0=[*(2, $0)]) + LogicalTableScan(table=[[scott, DEPT]]) +]]> + </Resource> + <Resource name="planAfter"> + <![CDATA[ +LogicalProject(DNAME=[$1]) + LogicalJoin(condition=[=($0, $2)], joinType=[anti]) + LogicalProject(DEPTNO=[$0], DNAME=[$1]) + LogicalTableScan(table=[[scott, DEPT]]) + LogicalProject($f0=[$0]) + LogicalAggregate(group=[{0}]) + LogicalProject($f0=[*(2, $0)]) + LogicalTableScan(table=[[scott, DEPT]]) +]]> + </Resource> + </TestCase> <TestCase name="testJoinProjectTranspose1"> <Resource name="sql"> <![CDATA[select a.name