Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CALCITE-3783] PruneEmptyRules#JOIN_RIGHT_INSTANCE wrong behavior for JoinRelType.ANTI #1796

Merged
merged 1 commit into from Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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