Skip to content

Commit

Permalink
[CALCITE-3783] PruneEmptyRules#JOIN_RIGHT_INSTANCE wrong behavior for…
Browse files Browse the repository at this point in the history
… JoinRelType.ANTI
  • Loading branch information
rubenada committed Feb 12, 2020
1 parent eaa40cb commit 10a5b8a
Show file tree
Hide file tree
Showing 3 changed files with 464 additions and 9 deletions.
Expand Up @@ -26,6 +26,7 @@
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.Filter;
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.RelFactories;
import org.apache.calcite.rel.core.Sort;
Expand Down Expand Up @@ -286,6 +287,9 @@ private static boolean isEmpty(RelNode node) {
*
* <ul>
* <li>Join(Empty, Scan(Dept), INNER) becomes Empty
* <li>Join(Empty, Scan(Dept), LEFT) becomes Empty
* <li>Join(Empty, Scan(Dept), SEMI) becomes Empty
* <li>Join(Empty, Scan(Dept), ANTI) becomes Empty
* </ul>
*/
public static final RelOptRule JOIN_LEFT_INSTANCE =
Expand Down Expand Up @@ -314,6 +318,9 @@ private static boolean isEmpty(RelNode node) {
*
* <ul>
* <li>Join(Scan(Emp), Empty, INNER) becomes Empty
* <li>Join(Scan(Emp), Empty, RIGHT) becomes Empty
* <li>Join(Scan(Emp), Empty, SEMI) becomes Empty
* <li>Join(Scan(Emp), Empty, ANTI) becomes Scan(Emp)
* </ul>
*/
public static final RelOptRule JOIN_RIGHT_INSTANCE =
Expand All @@ -330,6 +337,14 @@ private static boolean isEmpty(RelNode node) {
// dept is empty
return;
}
if (join.getJoinType() == JoinRelType.ANTI) {
// "select * from emp anti join dept" is not necessarily empty if dept is empty
if (join.analyzeCondition().isEqui()) {
// In case of anti (equi) join: Join(X, Empty, ANTI) becomes X
call.transformTo(join.getLeft());
}
return;
}
call.transformTo(call.builder().push(join).empty().build());
}
};
Expand Down
245 changes: 241 additions & 4 deletions core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
Expand Up @@ -3040,7 +3040,7 @@ private void checkReduceNullableToNotNull(ReduceExpressionsRule rule) {
sql(sql).with(program).check();
}

@Test public void testEmptyJoin() {
@Test public void testLeftEmptyInnerJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
Expand All @@ -3055,7 +3055,7 @@ private void checkReduceNullableToNotNull(ReduceExpressionsRule rule) {
sql(sql).with(program).check();
}

@Test public void testEmptyJoinLeft() {
@Test public void testLeftEmptyLeftJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
Expand All @@ -3070,22 +3070,259 @@ private void checkReduceNullableToNotNull(ReduceExpressionsRule rule) {
sql(sql).with(program).check();
}

@Test public void testEmptyJoinRight() {
@Test public void testLeftEmptyRightJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

// Plan should be equivalent to "select * from emp join dept".
// Plan should be equivalent to "select * from emp right join dept".
// Cannot optimize away the join because of RIGHT.
final String sql = "select * from (\n"
+ " select * from emp where false) e\n"
+ "right join dept d on e.deptno = d.deptno";
sql(sql).with(program).check();
}

@Test public void testLeftEmptyFullJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

// Plan should be equivalent to "select * from emp full join dept".
// Cannot optimize away the join because of FULL.
final String sql = "select * from (\n"
+ " select * from emp where false) e\n"
+ "full join dept d on e.deptno = d.deptno";
sql(sql).with(program).check();
}

@Test public void testLeftEmptySemiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP").empty()
.scan("DEPT")
.semiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.project(relBuilder.field("EMPNO"))
.build();

HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();

final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be empty
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}

@Test public void testLeftEmptyAntiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP").empty()
.scan("DEPT")
.antiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.project(relBuilder.field("EMPNO"))
.build();

HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();

final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be empty
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}

@Test public void testRightEmptyInnerJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

// Plan should be empty
final String sql = "select * from emp e\n"
+ "join (select * from dept where false) as d\n"
+ "on e.deptno = d.deptno";
sql(sql).with(program).check();
}

@Test public void testRightEmptyLeftJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

// Plan should be equivalent to "select * from emp left join dept".
// Cannot optimize away the join because of LEFT.
final String sql = "select * from emp e\n"
+ "left join (select * from dept where false) as d\n"
+ "on e.deptno = d.deptno";
sql(sql).with(program).check();
}

@Test public void testRightEmptyRightJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

// Plan should be empty
final String sql = "select * from emp e\n"
+ "right join (select * from dept where false) as d\n"
+ "on e.deptno = d.deptno";
sql(sql).with(program).check();
}

@Test public void testRightEmptyFullJoin() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

// Plan should be equivalent to "select * from emp full join dept".
// Cannot optimize away the join because of FULL.
final String sql = "select * from emp e\n"
+ "full join (select * from dept where false) as d\n"
+ "on e.deptno = d.deptno";
sql(sql).with(program).check();
}

@Test public void testRightEmptySemiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP")
.scan("DEPT").empty()
.semiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.project(relBuilder.field("EMPNO"))
.build();

HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();

final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be empty
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}

@Test public void testRightEmptyAntiJoin() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP")
.scan("DEPT").empty()
.antiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")))
.project(relBuilder.field("EMPNO"))
.build();

HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();

final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Plan should be scan("EMP") (i.e. join's left child)
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}

@Test public void testRightEmptyAntiJoinNonEqui() {
final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
final RelNode relNode = relBuilder
.scan("EMP")
.scan("DEPT").empty()
.antiJoin(relBuilder
.equals(
relBuilder.field(2, 0, "DEPTNO"),
relBuilder.field(2, 1, "DEPTNO")),
relBuilder
.equals(
relBuilder.field(2, 0, "SAL"),
relBuilder.literal(2000)))
.project(relBuilder.field("EMPNO"))
.build();

HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
.addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_LEFT_INSTANCE)
.addRuleInstance(PruneEmptyRules.JOIN_RIGHT_INSTANCE)
.build();

final HepPlanner hepPlanner = new HepPlanner(program);
hepPlanner.setRoot(relNode);
final RelNode output = hepPlanner.findBestExp();

final String planBefore = NL + RelOptUtil.toString(relNode);
final String planAfter = NL + RelOptUtil.toString(output);
final DiffRepository diffRepos = getDiffRepos();
diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);
// Cannot optimize away the join because it is not equi join.
diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
}

@Test public void testEmptySort() {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
Expand Down

0 comments on commit 10a5b8a

Please sign in to comment.