You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2016/12/02 05:24:52 UTC

[1/3] calcite git commit: [CALCITE-1495] SemiJoinRule should not apply to RIGHT and FULL JOIN, and should strip LEFT JOIN

Repository: calcite
Updated Branches:
  refs/heads/master 46654ad2a -> 565d63926


[CALCITE-1495] SemiJoinRule should not apply to RIGHT and FULL JOIN, and should strip LEFT JOIN

Also, update SemiJoinRule to accept RelBuilderFactory and class parameters.


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/565d6392
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/565d6392
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/565d6392

Branch: refs/heads/master
Commit: 565d639261d126462a78e57ff7f21527f38697a9
Parents: bac9ee7
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Dec 1 16:02:13 2016 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Dec 1 20:10:18 2016 -0800

----------------------------------------------------------------------
 .../apache/calcite/rel/rules/SemiJoinRule.java  |  79 +++++++++----
 .../apache/calcite/test/RelOptRulesTest.java    |  95 ++++++++++++++-
 .../org/apache/calcite/test/RelOptRulesTest.xml | 115 ++++++++++++++++---
 core/src/test/resources/sql/misc.iq             |  29 +++--
 4 files changed, 271 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/565d6392/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
index db87da3..13ad991 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SemiJoinRule.java
@@ -25,12 +25,15 @@ import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinInfo;
 import org.apache.calcite.rel.core.Project;
-import org.apache.calcite.rel.core.SemiJoin;
+import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
 
+import com.google.common.base.Predicate;
 import com.google.common.collect.Lists;
 
 import java.util.List;
@@ -41,15 +44,34 @@ import java.util.List;
  * {@link org.apache.calcite.rel.logical.LogicalAggregate}.
  */
 public class SemiJoinRule extends RelOptRule {
-  public static final SemiJoinRule INSTANCE = new SemiJoinRule();
+  private static final Predicate<Join> IS_LEFT_OR_INNER =
+      new Predicate<Join>() {
+        public boolean apply(Join input) {
+          switch (input.getJoinType()) {
+          case LEFT:
+          case INNER:
+            return true;
+          default:
+            return false;
+          }
+        }
+      };
 
-  private SemiJoinRule() {
+  public static final SemiJoinRule INSTANCE =
+      new SemiJoinRule(Project.class, Join.class, Aggregate.class,
+          RelFactories.LOGICAL_BUILDER, "SemiJoinRule");
+
+  /** Creates a SemiJoinRule. */
+  public SemiJoinRule(Class<Project> projectClass, Class<Join> joinClass,
+      Class<Aggregate> aggregateClass, RelBuilderFactory relBuilderFactory,
+      String description) {
     super(
-        operand(Project.class,
+        operand(projectClass,
             some(
-                operand(Join.class,
+                operand(joinClass, null, IS_LEFT_OR_INNER,
                     some(operand(RelNode.class, any()),
-                        operand(Aggregate.class, any()))))));
+                        operand(aggregateClass, any()))))),
+        relBuilderFactory, description);
   }
 
   @Override public void onMatch(RelOptRuleCall call) {
@@ -77,24 +99,35 @@ public class SemiJoinRule extends RelOptRule {
     if (!joinInfo.isEqui()) {
       return;
     }
-    final List<Integer> newRightKeyBuilder = Lists.newArrayList();
-    final List<Integer> aggregateKeys = aggregate.getGroupSet().asList();
-    for (int key : joinInfo.rightKeys) {
-      newRightKeyBuilder.add(aggregateKeys.get(key));
+    final RelBuilder relBuilder = call.builder();
+    relBuilder.push(left);
+    switch (join.getJoinType()) {
+    case INNER:
+      final List<Integer> newRightKeyBuilder = Lists.newArrayList();
+      final List<Integer> aggregateKeys = aggregate.getGroupSet().asList();
+      for (int key : joinInfo.rightKeys) {
+        newRightKeyBuilder.add(aggregateKeys.get(key));
+      }
+      final ImmutableIntList newRightKeys = ImmutableIntList.copyOf(newRightKeyBuilder);
+      relBuilder.push(aggregate.getInput());
+      final RexNode newCondition =
+          RelOptUtil.createEquiJoinCondition(relBuilder.peek(2, 0),
+              joinInfo.leftKeys, relBuilder.peek(2, 1), newRightKeys,
+              rexBuilder);
+      relBuilder.semiJoin(newCondition);
+      break;
+
+    case LEFT:
+      // The right-hand side produces no more than 1 row (because of the
+      // Aggregate) and no fewer than 1 row (because of LEFT), and therefore
+      // we can eliminate the semi-join.
+      break;
+
+    default:
+      throw new AssertionError(join.getJoinType());
     }
-    final ImmutableIntList newRightKeys =
-        ImmutableIntList.copyOf(newRightKeyBuilder);
-    final RelNode newRight = aggregate.getInput();
-    final RexNode newCondition =
-        RelOptUtil.createEquiJoinCondition(left, joinInfo.leftKeys, newRight,
-            newRightKeys, rexBuilder);
-    final SemiJoin semiJoin =
-        SemiJoin.create(left, newRight, newCondition, joinInfo.leftKeys,
-            newRightKeys);
-    final Project newProject =
-        project.copy(project.getTraitSet(), semiJoin, project.getProjects(),
-            project.getRowType());
-    call.transformTo(ProjectRemoveRule.strip(newProject));
+    relBuilder.project(project.getProjects(), project.getRowType().getFieldNames());
+    call.transformTo(relBuilder.build());
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/565d6392/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
----------------------------------------------------------------------
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 3097d47..ebc0e99 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -493,7 +493,7 @@ public class RelOptRulesTest extends RelOptTestBase {
     checkPlanning(program, sql);
   }
 
-  @Test public void testSemiJoinRule() {
+  @Test public void testSemiJoinRuleExists() {
     final HepProgram preProgram =
         HepProgram.builder()
             .addRuleInstance(FilterProjectTransposeRule.INSTANCE)
@@ -516,6 +516,99 @@ public class RelOptRulesTest extends RelOptTestBase {
         .check();
   }
 
+  @Test public void testSemiJoinRule() {
+    final HepProgram preProgram =
+        HepProgram.builder()
+            .addRuleInstance(FilterProjectTransposeRule.INSTANCE)
+            .addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
+            .addRuleInstance(ProjectMergeRule.INSTANCE)
+            .build();
+    final HepProgram program =
+        HepProgram.builder()
+            .addRuleInstance(SemiJoinRule.INSTANCE)
+            .build();
+    final String sql = "select dept.* from dept join (\n"
+        + "  select distinct deptno from emp\n"
+        + "  where sal > 100) using (deptno)";
+    sql(sql)
+        .withDecorrelation(true)
+        .withTrim(true)
+        .withPre(preProgram)
+        .with(program)
+        .check();
+  }
+
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-1495">[CALCITE-1495]
+   * SemiJoinRule should not apply to RIGHT and FULL JOIN</a>. */
+  @Test public void testSemiJoinRuleRight() {
+    final HepProgram preProgram =
+        HepProgram.builder()
+            .addRuleInstance(FilterProjectTransposeRule.INSTANCE)
+            .addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
+            .addRuleInstance(ProjectMergeRule.INSTANCE)
+            .build();
+    final HepProgram program =
+        HepProgram.builder()
+            .addRuleInstance(SemiJoinRule.INSTANCE)
+            .build();
+    final String sql = "select dept.* from dept right join (\n"
+        + "  select distinct deptno from emp\n"
+        + "  where sal > 100) using (deptno)";
+    sql(sql)
+        .withPre(preProgram)
+        .with(program)
+        .withDecorrelation(true)
+        .withTrim(true)
+        .checkUnchanged();
+  }
+
+  /** Similar to {@link #testSemiJoinRuleRight()} but FULL. */
+  @Test public void testSemiJoinRuleFull() {
+    final HepProgram preProgram =
+        HepProgram.builder()
+            .addRuleInstance(FilterProjectTransposeRule.INSTANCE)
+            .addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
+            .addRuleInstance(ProjectMergeRule.INSTANCE)
+            .build();
+    final HepProgram program =
+        HepProgram.builder()
+            .addRuleInstance(SemiJoinRule.INSTANCE)
+            .build();
+    final String sql = "select dept.* from dept full join (\n"
+        + "  select distinct deptno from emp\n"
+        + "  where sal > 100) using (deptno)";
+    sql(sql)
+        .withPre(preProgram)
+        .with(program)
+        .withDecorrelation(true)
+        .withTrim(true)
+        .checkUnchanged();
+  }
+
+  /** Similar to {@link #testSemiJoinRule()} but LEFT. */
+  @Test public void testSemiJoinRuleLeft() {
+    final HepProgram preProgram =
+        HepProgram.builder()
+            .addRuleInstance(FilterProjectTransposeRule.INSTANCE)
+            .addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
+            .addRuleInstance(ProjectMergeRule.INSTANCE)
+            .build();
+    final HepProgram program =
+        HepProgram.builder()
+            .addRuleInstance(SemiJoinRule.INSTANCE)
+            .build();
+    final String sql = "select name from dept left join (\n"
+        + "  select distinct deptno from emp\n"
+        + "  where sal > 100) using (deptno)";
+    sql(sql)
+        .withPre(preProgram)
+        .with(program)
+        .withDecorrelation(true)
+        .withTrim(true)
+        .check();
+  }
+
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-438">[CALCITE-438]
    * Push predicates through SemiJoin</a>. */

http://git-wip-us.apache.org/repos/asf/calcite/blob/565d6392/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
----------------------------------------------------------------------
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 ba017bd..5313698 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -3842,7 +3842,7 @@ LogicalProject(DEPTNO=[$0], NAME=[$1], EMPNO=[$2], ENAME=[$3], JOB=[$4], MGR=[$5
 ]]>
         </Resource>
     </TestCase>
-    <TestCase name="testSemiJoinRule">
+    <TestCase name="testSemiJoinRuleExists">
         <Resource name="sql">
             <![CDATA[select * from dept where exists (
   select * from emp
@@ -5130,6 +5130,93 @@ LogicalProject(DEPTNO=[$3], SUM_SAL=[$1], C=[$2])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testSemiJoinRule">
+        <Resource name="sql">
+            <![CDATA[select dept.* from dept join (
+  select distinct deptno from emp
+  where sal > 100) using (deptno)]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalJoin(condition=[=($0, $2)], joinType=[inner])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalAggregate(group=[{0}])
+      LogicalProject(DEPTNO=[$7])
+        LogicalFilter(condition=[>($5, 100)])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+SemiJoin(condition=[=($0, $2)], joinType=[inner])
+  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+  LogicalProject(DEPTNO=[$7])
+    LogicalFilter(condition=[>($5, 100)])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testSemiJoinRuleFull">
+        <Resource name="sql">
+            <![CDATA[select dept.* from dept full join (
+  select distinct deptno from emp
+  where sal > 100) using (deptno)]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalJoin(condition=[=($0, $2)], joinType=[full])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalAggregate(group=[{0}])
+      LogicalProject(DEPTNO=[$7])
+        LogicalFilter(condition=[>($5, 100)])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testSemiJoinRuleLeft">
+        <Resource name="sql">
+            <![CDATA[select name from dept left join (
+  select distinct deptno from emp
+  where sal > 100) using (deptno)]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(NAME=[$1])
+  LogicalJoin(condition=[=($0, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalAggregate(group=[{0}])
+      LogicalProject(DEPTNO=[$7])
+        LogicalFilter(condition=[>($5, 100)])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(NAME=[$1])
+  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testSemiJoinRuleRight">
+        <Resource name="sql">
+            <![CDATA[select dept.* from dept right join (
+  select distinct deptno from emp
+  where sal > 100) using (deptno)]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1])
+  LogicalJoin(condition=[=($0, $2)], joinType=[right])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalAggregate(group=[{0}])
+      LogicalProject(DEPTNO=[$7])
+        LogicalFilter(condition=[>($5, 100)])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
     <TestCase name="testSortJoinTranspose1">
         <Resource name="sql">
             <![CDATA[select * from sales.emp e left join (
@@ -6192,7 +6279,10 @@ LogicalProject(JOB=[$0], EMPNO=[10], SAL=[$1], S=[$2])
     </TestCase>
     <TestCase name="testWhereNotInCorrelated">
         <Resource name="sql">
-            <![CDATA[select sal from emp where empno NOT IN (select deptno from dept where emp.job = dept.name)]]>
+            <![CDATA[select sal from emp
+where empno NOT IN (
+  select deptno from dept
+  where emp.job = dept.name)]]>
         </Resource>
         <Resource name="planBefore">
             <![CDATA[
@@ -6209,20 +6299,15 @@ LogicalProject(DEPTNO=[$0])
             <![CDATA[
 LogicalProject(SAL=[$5])
   LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
-    LogicalFilter(condition=[IS NULL($11)])
-      LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[CAST($9):INTEGER], JOB0=[CAST($10):VARCHAR(10) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary"], $f2=[CAST($11):BOOLEAN])
-        LogicalJoin(condition=[AND(=($2, $10), =($0, $9))], joinType=[inner])
+    LogicalFilter(condition=[IS NULL($10)])
+      LogicalFilter(condition=[=($0, $9)])
+        LogicalCorrelate(correlation=[$cor0], joinType=[LEFT], requiredColumns=[{2}])
           LogicalTableScan(table=[[CATALOG, SALES, EMP]])
-          LogicalProject(DEPTNO=[$0], JOB=[$1], $f2=[true])
-            LogicalAggregate(group=[{0, 1}])
-              LogicalProject(DEPTNO=[$0], JOB=[$2], i=[$1])
-                LogicalProject(DEPTNO=[$0], i=[true], JOB=[$1])
-                  LogicalProject(DEPTNO=[$0], JOB=[$2])
-                    LogicalJoin(condition=[=($2, $1)], joinType=[inner])
-                      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
-                      LogicalAggregate(group=[{0}])
-                        LogicalProject(JOB=[$2])
-                          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+          LogicalAggregate(group=[{0, 1}])
+            LogicalProject(DEPTNO=[$0], i=[true])
+              LogicalProject(DEPTNO=[$0])
+                LogicalFilter(condition=[=($cor0.JOB, $1)])
+                  LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
 ]]>
         </Resource>
     </TestCase>

http://git-wip-us.apache.org/repos/asf/calcite/blob/565d6392/core/src/test/resources/sql/misc.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/misc.iq b/core/src/test/resources/sql/misc.iq
index 0378e26..972b73c 100644
--- a/core/src/test/resources/sql/misc.iq
+++ b/core/src/test/resources/sql/misc.iq
@@ -424,18 +424,31 @@ EnumerableCalc(expr#0..7=[{inputs}], expr#8=[IS NULL($t5)], expr#9=[IS NULL($t7)
         EnumerableJoin(condition=[=($1, $3)], joinType=[inner])
           EnumerableCalc(expr#0=[{inputs}], expr#1=[CAST($t0):INTEGER NOT NULL], proj#0..1=[{exprs}])
             EnumerableAggregate(group=[{0}])
-              EnumerableSemiJoin(condition=[=($1, $2)], joinType=[inner])
-                EnumerableCalc(expr#0..4=[{inputs}], proj#0..1=[{exprs}])
-                  EnumerableTableScan(table=[[hr, emps]])
-                EnumerableJoin(condition=[=($0, $1)], joinType=[inner])
-                  EnumerableAggregate(group=[{1}])
-                    EnumerableTableScan(table=[[hr, emps]])
-                  EnumerableCalc(expr#0..3=[{inputs}], deptno=[$t0])
-                    EnumerableTableScan(table=[[hr, depts]])
+              EnumerableTableScan(table=[[hr, emps]])
           EnumerableCalc(expr#0..3=[{inputs}], expr#4=[90], expr#5=[+($t0, $t4)], deptno=[$t0], $f1=[$t5])
             EnumerableTableScan(table=[[hr, depts]])
 !plan
 
+# Left join to a relation with one row is recognized as a trivial semi-join
+# and eliminated.
+select e."deptno"
+from "hr"."emps" as e
+left join (select count(*) from "hr"."depts") on true;
++--------+
+| deptno |
++--------+
+|     10 |
+|     10 |
+|     10 |
+|     20 |
++--------+
+(4 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], deptno=[$t1])
+  EnumerableTableScan(table=[[hr, emps]])
+!plan
+
 # Filter combined with an OR filter.
 select * from (
   select * from "hr"."emps" as e


[2/3] calcite git commit: Test case for [CALCITE-1493], and clean up test infrastructure

Posted by jh...@apache.org.
Test case for [CALCITE-1493], and clean up test infrastructure

Also, fix a minor performance issue when tracing is disabled.


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/bac9ee7c
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/bac9ee7c
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/bac9ee7c

Branch: refs/heads/master
Commit: bac9ee7cb7e76d6307e34091b5a132e3d068dfd7
Parents: 751e2b0
Author: Julian Hyde <jh...@apache.org>
Authored: Wed Nov 30 11:57:20 2016 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Dec 1 20:10:18 2016 -0800

----------------------------------------------------------------------
 .../org/apache/calcite/prepare/Prepare.java     |   6 +-
 .../org/apache/calcite/runtime/FlatLists.java   |  14 +++
 .../org/apache/calcite/test/CalciteAssert.java  |   8 +-
 .../java/org/apache/calcite/test/JdbcTest.java  |   6 +-
 .../apache/calcite/test/RelOptRulesTest.java    | 108 +++++++++++++------
 .../org/apache/calcite/test/RelOptTestBase.java |  84 ++++++++++++---
 .../apache/calcite/test/SqlToRelTestBase.java   |  65 +++++++----
 .../org/apache/calcite/test/RelOptRulesTest.xml |  36 +++++++
 8 files changed, 250 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/bac9ee7c/core/src/main/java/org/apache/calcite/prepare/Prepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/Prepare.java b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
index 2064567..aa4ba8b 100644
--- a/core/src/main/java/org/apache/calcite/prepare/Prepare.java
+++ b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
@@ -156,8 +156,10 @@ public abstract class Prepare {
     }
 
     final RelNode rootRel4 = program.run(planner, root.rel, desiredTraits);
-    LOGGER.debug("Plan after physical tweaks: {}",
-        RelOptUtil.toString(rootRel4, SqlExplainLevel.ALL_ATTRIBUTES));
+    if (LOGGER.isDebugEnabled()) {
+      LOGGER.debug("Plan after physical tweaks: {}",
+          RelOptUtil.toString(rootRel4, SqlExplainLevel.ALL_ATTRIBUTES));
+    }
 
     return root.withRel(rootRel4);
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/bac9ee7c/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
index 4ab4ef2..73344c4 100644
--- a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
+++ b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
@@ -19,6 +19,7 @@ package org.apache.calcite.runtime;
 import org.apache.calcite.util.ImmutableNullableList;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
 import java.util.AbstractList;
 import java.util.ArrayList;
@@ -26,6 +27,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.RandomAccess;
 
@@ -252,6 +254,18 @@ public class FlatLists {
     return FlatLists.of(newList);
   }
 
+  /** Returns a list that consists of a given list plus an element, guaranteed
+   * to be an {@link ImmutableList}. */
+  public static <E> ImmutableList<E> append(ImmutableList<E> list, E e) {
+    return ImmutableList.<E>builder().addAll(list).add(e).build();
+  }
+
+  /** Returns a map that consists of a given map plus an (key, value),
+   * guaranteed to be an {@link ImmutableMap}. */
+  public static <K, V> ImmutableMap<K, V> append(Map<K, V> map, K k, V v) {
+    return ImmutableMap.<K, V>builder().putAll(map).put(k, v).build();
+  }
+
   /** Base class for flat lists. */
   public abstract static class AbstractFlatList<T>
       extends AbstractImmutableList<T> implements RandomAccess {

http://git-wip-us.apache.org/repos/asf/calcite/blob/bac9ee7c/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index c5efd87..e0f8a6b 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -28,6 +28,7 @@ import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.materialize.Lattice;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
@@ -1124,10 +1125,9 @@ public class CalciteAssert {
     }
 
     public ConnectionFactory with(String property, Object value) {
-      ImmutableMap.Builder<String, String> b = ImmutableMap.builder();
-      b.putAll(this.map);
-      b.put(property, value.toString());
-      return new MapConnectionFactory(b.build(), postProcessors);
+      return new MapConnectionFactory(
+          FlatLists.append(this.map, property, value.toString()),
+          postProcessors);
     }
 
     public ConnectionFactory with(

http://git-wip-us.apache.org/repos/asf/calcite/blob/bac9ee7c/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index bc214c2..2740c41 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -99,7 +99,6 @@ import org.apache.calcite.util.Util;
 import com.google.common.base.Function;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 
 import org.hsqldb.jdbcDriver;
 
@@ -6892,10 +6891,7 @@ public class JdbcTest {
           final Map<String, Table> tableMap = super.getTableMap();
           final Table table = tableMap.get("emps");
           final String tableName = (String) operand.get("tableName");
-          return ImmutableMap.<String, Table>builder()
-              .putAll(tableMap)
-              .put(tableName, table)
-              .build();
+          return FlatLists.append(tableMap, tableName, table);
         }
 
         @Override public boolean isMutable() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/bac9ee7c/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
----------------------------------------------------------------------
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 74e2b03..3097d47 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -274,8 +274,12 @@ public class RelOptRulesTest extends RelOptTestBase {
     final String sql = "select *\n"
         + "from dept left join emp using (deptno)\n"
         + "where emp.deptno is not null and emp.sal > 100";
-    checkPlanning(tester.withDecorrelation(true).withTrim(true), preProgram,
-        new HepPlanner(program), sql);
+    sql(sql)
+        .withDecorrelation(true)
+        .withTrim(true)
+        .withPre(preProgram)
+        .with(program)
+        .check();
   }
 
   @Test public void testFullOuterJoinSimplificationToLeftOuter() {
@@ -500,12 +504,16 @@ public class RelOptRulesTest extends RelOptTestBase {
         HepProgram.builder()
             .addRuleInstance(SemiJoinRule.INSTANCE)
             .build();
-    checkPlanning(tester.withDecorrelation(true).withTrim(true), preProgram,
-        new HepPlanner(program),
-        "select * from dept where exists (\n"
-            + "  select * from emp\n"
-            + "  where emp.deptno = dept.deptno\n"
-            + "  and emp.sal > 100)");
+    final String sql = "select * from dept where exists (\n"
+        + "  select * from emp\n"
+        + "  where emp.deptno = dept.deptno\n"
+        + "  and emp.sal > 100)";
+    sql(sql)
+        .withDecorrelation(true)
+        .withTrim(true)
+        .withPre(preProgram)
+        .with(program)
+        .check();
   }
 
   /** Test case for
@@ -523,11 +531,16 @@ public class RelOptRulesTest extends RelOptTestBase {
             .addRuleInstance(FilterJoinRule.FILTER_ON_JOIN)
             .addRuleInstance(FilterJoinRule.JOIN)
             .build();
-    checkPlanning(tester.withDecorrelation(true).withTrim(false), preProgram,
-        new HepPlanner(program),
-        "select * from (select * from dept where dept.deptno in (\n"
-            + "  select emp.deptno from emp\n"
-            + "  ))R where R.deptno <=10 ");
+    final String sql = "select * from (\n"
+        + "  select * from dept where dept.deptno in (\n"
+        + "    select emp.deptno from emp))R\n"
+        + "where R.deptno <=10";
+    sql(sql)
+        .withDecorrelation(true)
+        .withTrim(false)
+        .withPre(preProgram)
+        .with(program)
+        .check();
   }
 
   /** Test case for
@@ -545,8 +558,12 @@ public class RelOptRulesTest extends RelOptTestBase {
         + "from (select * from emp where deptno = 200) as e1\n"
         + "where e1.deptno in (\n"
         + "  select e2.deptno from emp e2 where e2.sal = 100)";
-    checkPlanning(tester.withDecorrelation(false).withTrim(true), preProgram,
-        new HepPlanner(program), sql, true);
+    sql(sql)
+        .withDecorrelation(false)
+        .withTrim(true)
+        .withPre(preProgram)
+        .with(program)
+        .checkUnchanged();
   }
 
   @Test public void testSemiJoinTrim() {
@@ -1973,7 +1990,6 @@ public class RelOptRulesTest extends RelOptTestBase {
 
   @Test public void testPullConstantThroughUnion()
       throws Exception {
-    HepProgram preProgram = HepProgram.builder().build();
     HepProgram program = HepProgram.builder()
         .addRuleInstance(UnionPullUpConstantsRule.INSTANCE)
         .addRuleInstance(ProjectMergeRule.INSTANCE)
@@ -1981,7 +1997,10 @@ public class RelOptRulesTest extends RelOptTestBase {
     final String sql = "select 2, deptno, job from emp as e1\n"
         + "union all\n"
         + "select 2, deptno, job from emp as e2";
-    checkPlanning(tester.withTrim(true), preProgram, new HepPlanner(program), sql);
+    sql(sql)
+        .withTrim(true)
+        .with(program)
+        .check();
   }
 
   @Test public void testPullConstantThroughUnion2()
@@ -2000,7 +2019,6 @@ public class RelOptRulesTest extends RelOptTestBase {
   @Test public void testPullConstantThroughUnion3()
       throws Exception {
     // We should leave at least a single column in each Union input
-    HepProgram preProgram = HepProgram.builder().build();
     HepProgram program = HepProgram.builder()
         .addRuleInstance(UnionPullUpConstantsRule.INSTANCE)
         .addRuleInstance(ProjectMergeRule.INSTANCE)
@@ -2008,7 +2026,10 @@ public class RelOptRulesTest extends RelOptTestBase {
     final String sql = "select 2, 3 from emp as e1\n"
         + "union all\n"
         + "select 2, 3 from emp as e2";
-    checkPlanning(tester.withTrim(true), preProgram, new HepPlanner(program), sql);
+    sql(sql)
+        .withTrim(true)
+        .with(program)
+        .check();
   }
 
   @Test public void testAggregateProjectMerge() throws Exception {
@@ -2257,8 +2278,6 @@ public class RelOptRulesTest extends RelOptTestBase {
    * <a href="https://issues.apache.org/jira/browse/CALCITE-841">[CALCITE-841]
    * Redundant windows when window function arguments are expressions</a>. */
   @Test public void testExpressionInWindowFunction() {
-    HepProgram preProgram =  new HepProgramBuilder().build();
-
     HepProgramBuilder builder = new HepProgramBuilder();
     builder.addRuleClass(ProjectToWindowRule.class);
 
@@ -2269,16 +2288,15 @@ public class RelOptRulesTest extends RelOptTestBase {
         + " sum(deptno) over(partition by deptno order by sal) as sum1,\n"
         + "sum(deptno + sal) over(partition by deptno order by sal) as sum2\n"
         + "from emp";
-    checkPlanning(tester, preProgram, hepPlanner, sql);
+    sql(sql)
+        .with(hepPlanner)
+        .check();
   }
 
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-888">[CALCITE-888]
    * Overlay window loses PARTITION BY list</a>. */
   @Test public void testWindowInParenthesis() {
-    HepProgram preProgram =  new HepProgramBuilder()
-        .build();
-
     HepProgramBuilder builder = new HepProgramBuilder();
     builder.addRuleClass(ProjectToWindowRule.class);
     HepPlanner hepPlanner = new HepPlanner(builder.build());
@@ -2287,7 +2305,9 @@ public class RelOptRulesTest extends RelOptTestBase {
     final String sql = "select count(*) over (w), count(*) over w\n"
         + "from emp\n"
         + "window w as (partition by empno order by empno)";
-    checkPlanning(tester, preProgram, hepPlanner, sql);
+    sql(sql)
+        .with(hepPlanner)
+        .check();
   }
 
   /** Test case for
@@ -2479,7 +2499,8 @@ public class RelOptRulesTest extends RelOptTestBase {
    * Wrong collation trait in SortJoinTransposeRule for right joins</a>. */
   @Test public void testSortJoinTranspose4() {
     // Create a customized test with RelCollation trait in the test cluster.
-    Tester tester = new TesterImpl(getDiffRepos(), true, true, false, null, null) {
+    Tester tester = new TesterImpl(getDiffRepos(), true, true, false, false,
+        null, null) {
       @Override public RelOptPlanner createPlanner() {
         return new MockRelOptPlanner() {
           @Override public List<RelTraitDef> getRelTraitDefs() {
@@ -2591,6 +2612,18 @@ public class RelOptRulesTest extends RelOptTestBase {
     checkSubQuery(sql).check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-1493">[CALCITE-1493]
+   * Wrong plan for NOT IN correlated queries</a>. */
+  @Ignore("[CALCITE-1493] is not fixed yet")
+  @Test public void testWhereNotInCorrelated() {
+    final String sql = "select sal from emp\n"
+        + "where empno NOT IN (\n"
+        + "  select deptno from dept\n"
+        + "  where emp.job = dept.name)";
+    checkSubQuery(sql).withLateDecorrelation(false).check();
+  }
+
   @Test public void testExpandProjectIn() throws Exception {
     final String sql = "select empno,\n"
         + "  deptno in (select deptno from sales.emp where empno < 20) as d\n"
@@ -2754,8 +2787,11 @@ public class RelOptRulesTest extends RelOptTestBase {
         .addRuleInstance(SubQueryRemoveRule.FILTER)
         .addRuleInstance(SubQueryRemoveRule.JOIN)
         .build();
-    final Tester tester = this.tester.withTrim(true).withExpand(false);
-    checkPlanning(tester, null, new HepPlanner(program), sql);
+    sql(sql)
+        .withTrim(true)
+        .expand(false)
+        .with(program)
+        .check();
   }
 
   @Test public void testCustomColumnResolvingInCorrelatedSubQuery() {
@@ -2768,8 +2804,11 @@ public class RelOptRulesTest extends RelOptTestBase {
         .addRuleInstance(SubQueryRemoveRule.FILTER)
         .addRuleInstance(SubQueryRemoveRule.JOIN)
         .build();
-    final Tester tester = this.tester.withTrim(true).withExpand(false);
-    checkPlanning(tester, null, new HepPlanner(program), sql);
+    sql(sql)
+        .withTrim(true)
+        .expand(false)
+        .with(program)
+        .check();
   }
 
   @Test public void testCustomColumnResolvingInCorrelatedSubQuery2() {
@@ -2782,8 +2821,11 @@ public class RelOptRulesTest extends RelOptTestBase {
         .addRuleInstance(SubQueryRemoveRule.FILTER)
         .addRuleInstance(SubQueryRemoveRule.JOIN)
         .build();
-    final Tester tester = this.tester.withTrim(true).withExpand(false);
-    checkPlanning(tester, null, new HepPlanner(program), sql);
+    sql(sql)
+        .withTrim(true)
+        .expand(false)
+        .with(program)
+        .check();
   }
 
   /** Test case for

http://git-wip-us.apache.org/repos/asf/calcite/blob/bac9ee7c/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java b/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
index 4c57833..277d368 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
@@ -27,10 +27,13 @@ import org.apache.calcite.rel.RelRoot;
 import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider;
 import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
+import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.Hook;
+import org.apache.calcite.sql2rel.RelDecorrelator;
 import org.apache.calcite.util.Closer;
 
 import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 
@@ -160,8 +163,11 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
 
     planner.setRoot(relBefore);
     RelNode relAfter = planner.findBestExp();
-
     String planAfter = NL + RelOptUtil.toString(relAfter);
+    if (tester.isLateDecorrelate()) {
+      relAfter = RelDecorrelator.decorrelateQuery(relAfter);
+      planAfter = NL + RelOptUtil.toString(relAfter);
+    }
     if (unchanged) {
       assertThat(planAfter, is(planBefore));
     } else {
@@ -176,7 +182,9 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
 
   /** Sets the SQL statement for a test. */
   Sql sql(String sql) {
-    return new Sql(sql, null, null, true, ImmutableMap.<Hook, Function>of());
+    return new Sql(sql, null, null,
+        ImmutableMap.<Hook, Function>of(),
+        ImmutableList.<Function<Tester, Tester>>of());
   }
 
   /** Allows fluent testing. */
@@ -184,47 +192,88 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
     private final String sql;
     private HepProgram preProgram;
     private final HepPlanner hepPlanner;
-    private final boolean expand;
     private final ImmutableMap<Hook, Function> hooks;
+    private ImmutableList<Function<Tester, Tester>> transforms;
 
     Sql(String sql, HepProgram preProgram, HepPlanner hepPlanner,
-        boolean expand, ImmutableMap<Hook, Function> hooks) {
+        ImmutableMap<Hook, Function> hooks,
+        ImmutableList<Function<Tester, Tester>> transforms) {
       this.sql = sql;
       this.preProgram = preProgram;
       this.hepPlanner = hepPlanner;
-      this.expand = expand;
       this.hooks = hooks;
+      this.transforms = transforms;
     }
 
     public Sql withPre(HepProgram preProgram) {
-      return new Sql(sql, preProgram, hepPlanner, expand, hooks);
+      return new Sql(sql, preProgram, hepPlanner, hooks, transforms);
     }
 
     public Sql with(HepPlanner hepPlanner) {
-      return new Sql(sql, preProgram, hepPlanner, expand, hooks);
+      return new Sql(sql, preProgram, hepPlanner, hooks, transforms);
     }
 
     public Sql with(HepProgram program) {
-      return new Sql(sql, preProgram, new HepPlanner(program), expand, hooks);
+      return new Sql(sql, preProgram, new HepPlanner(program), hooks,
+          transforms);
+    }
+
+    /** Adds a transform that will be applied to {@link #tester}
+     * just before running the query. */
+    private Sql withTransform(Function<Tester, Tester> transform) {
+      return new Sql(sql, preProgram, hepPlanner, hooks,
+          FlatLists.append(transforms, transform));
     }
 
     /** Adds a hook and a handler for that hook. Calcite will create a thread
      * hook (by calling {@link Hook#addThread(com.google.common.base.Function)})
      * just before running the query, and remove the hook afterwards. */
     public <T> Sql withHook(Hook hook, Function<T, Void> handler) {
-      return new Sql(sql, preProgram, hepPlanner, expand,
-          ImmutableMap.<Hook, Function>builder().putAll(hooks)
-              .put(hook, handler).build());
+      return new Sql(sql, preProgram, hepPlanner,
+          FlatLists.append(hooks, hook, handler), transforms);
     }
 
     public <V> Sql withProperty(Hook hook, V value) {
       return withHook(hook, Hook.property(value));
     }
 
-    public Sql expand(boolean expand) {
-      return new Sql(sql, preProgram, hepPlanner, expand, hooks);
+    public Sql expand(final boolean b) {
+      return withTransform(
+          new Function<Tester, Tester>() {
+            public Tester apply(Tester tester) {
+              return tester.withExpand(b);
+            }
+          });
+    }
+
+    public Sql withLateDecorrelation(final boolean b) {
+      return withTransform(
+          new Function<Tester, Tester>() {
+            public Tester apply(Tester tester) {
+              return tester.withLateDecorrelation(b);
+            }
+          });
+    }
+
+    public Sql withDecorrelation(final boolean b) {
+      return withTransform(
+          new Function<Tester, Tester>() {
+            public Tester apply(Tester tester) {
+              return tester.withDecorrelation(b);
+            }
+          });
+    }
+
+    public Sql withTrim(final boolean b) {
+      return withTransform(
+          new Function<Tester, Tester>() {
+            public Tester apply(Tester tester) {
+              return tester.withTrim(b);
+            }
+          });
     }
 
+
     public void check() {
       check(false);
     }
@@ -238,11 +287,16 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
         for (Map.Entry<Hook, Function> entry : hooks.entrySet()) {
           closer.add(entry.getKey().addThread(entry.getValue()));
         }
-        checkPlanning(tester.withExpand(expand), preProgram, hepPlanner, sql,
-            unchanged);
+        Tester t = tester;
+        for (Function<Tester, Tester> transform : transforms) {
+          t = transform.apply(t);
+        }
+        checkPlanning(t, preProgram, hepPlanner, sql, unchanged);
       }
     }
+
   }
+
 }
 
 // End RelOptTestBase.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/bac9ee7c/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
index 166db72..3cfbf35 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelTestBase.java
@@ -93,7 +93,8 @@ public abstract class SqlToRelTestBase {
   }
 
   protected Tester createTester() {
-    return new TesterImpl(getDiffRepos(), false, false, true, null, null);
+    return new TesterImpl(getDiffRepos(), false, false, true, false, null,
+        null);
   }
 
   /**
@@ -210,6 +211,10 @@ public abstract class SqlToRelTestBase {
     /** Returns a tester that optionally decorrelates queries. */
     Tester withDecorrelation(boolean enable);
 
+    /** Returns a tester that optionally decorrelates queries after planner
+     * rules have fired. */
+    Tester withLateDecorrelation(boolean enable);
+
     /** Returns a tester that optionally expands sub-queries.
      * If {@code expand} is false, the plan contains a
      * {@link org.apache.calcite.rex.RexSubQuery} for each sub-query.
@@ -228,6 +233,8 @@ public abstract class SqlToRelTestBase {
     Tester withTrim(boolean enable);
 
     Tester withClusterFactory(Function<RelOptCluster, RelOptCluster> function);
+
+    boolean isLateDecorrelate();
   }
 
   //~ Inner Classes ----------------------------------------------------------
@@ -466,6 +473,7 @@ public abstract class SqlToRelTestBase {
     private SqlOperatorTable opTab;
     private final DiffRepository diffRepos;
     private final boolean enableDecorrelate;
+    private final boolean enableLateDecorrelate;
     private final boolean enableTrim;
     private final boolean enableExpand;
     private final Function<RelDataTypeFactory, Prepare.CatalogReader>
@@ -486,15 +494,18 @@ public abstract class SqlToRelTestBase {
      */
     protected TesterImpl(DiffRepository diffRepos, boolean enableDecorrelate,
         boolean enableTrim, boolean enableExpand,
+        boolean enableLateDecorrelate,
         Function<RelDataTypeFactory, Prepare.CatalogReader>
             catalogReaderFactory,
         Function<RelOptCluster, RelOptCluster> clusterFactory) {
       this(diffRepos, enableDecorrelate, enableTrim, enableExpand,
-          catalogReaderFactory, clusterFactory, SqlToRelConverter.Config.DEFAULT);
+          enableLateDecorrelate, catalogReaderFactory, clusterFactory,
+          SqlToRelConverter.Config.DEFAULT);
     }
 
     protected TesterImpl(DiffRepository diffRepos, boolean enableDecorrelate,
         boolean enableTrim, boolean enableExpand,
+        boolean enableLateDecorrelate,
         Function<RelDataTypeFactory, Prepare.CatalogReader>
             catalogReaderFactory,
         Function<RelOptCluster, RelOptCluster> clusterFactory,
@@ -503,6 +514,7 @@ public abstract class SqlToRelTestBase {
       this.enableDecorrelate = enableDecorrelate;
       this.enableTrim = enableTrim;
       this.enableExpand = enableExpand;
+      this.enableLateDecorrelate = enableLateDecorrelate;
       this.catalogReaderFactory = catalogReaderFactory;
       this.clusterFactory = clusterFactory;
       this.config = config;
@@ -689,44 +701,61 @@ public abstract class SqlToRelTestBase {
       return createValidator(catalogReader, typeFactory);
     }
 
-    public TesterImpl withDecorrelation(boolean enable) {
-      return this.enableDecorrelate == enable
+    public TesterImpl withDecorrelation(boolean enableDecorrelate) {
+      return this.enableDecorrelate == enableDecorrelate
+          ? this
+          : new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
+              enableExpand, enableLateDecorrelate, catalogReaderFactory,
+              clusterFactory);
+    }
+
+    public Tester withLateDecorrelation(boolean enableLateDecorrelate) {
+      return this.enableLateDecorrelate == enableLateDecorrelate
           ? this
-          : new TesterImpl(diffRepos, enable, enableTrim, enableExpand,
-              catalogReaderFactory, clusterFactory);
+          : new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
+              enableExpand, enableLateDecorrelate, catalogReaderFactory,
+              clusterFactory);
     }
 
     public TesterImpl withConfig(SqlToRelConverter.Config config) {
       return this.config == config
           ? this
           : new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
-              enableExpand, catalogReaderFactory, clusterFactory, config);
+              enableExpand, enableLateDecorrelate, catalogReaderFactory,
+              clusterFactory, config);
     }
 
-    public Tester withTrim(boolean enable) {
-      return this.enableTrim == enable
+    public Tester withTrim(boolean enableTrim) {
+      return this.enableTrim == enableTrim
           ? this
-          : new TesterImpl(diffRepos, enableDecorrelate, enable, enableExpand,
-              catalogReaderFactory, clusterFactory);
+          : new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
+              enableExpand, enableLateDecorrelate, catalogReaderFactory,
+              clusterFactory);
     }
 
-    public Tester withExpand(boolean expand) {
-      return this.enableExpand == expand
+    public Tester withExpand(boolean enableExpand) {
+      return this.enableExpand == enableExpand
           ? this
-          : new TesterImpl(diffRepos, enableDecorrelate, enableTrim, expand,
-              catalogReaderFactory, clusterFactory);
+          : new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
+              enableExpand, enableLateDecorrelate, catalogReaderFactory,
+              clusterFactory);
     }
 
     public Tester withCatalogReaderFactory(
         Function<RelDataTypeFactory, Prepare.CatalogReader> factory) {
-      return new TesterImpl(diffRepos, enableDecorrelate, false, enableExpand,
-          factory, clusterFactory);
+      return new TesterImpl(diffRepos, enableDecorrelate, false,
+          enableExpand, enableLateDecorrelate, factory,
+          clusterFactory);
     }
 
     public Tester withClusterFactory(
         Function<RelOptCluster, RelOptCluster> clusterFactory) {
       return new TesterImpl(diffRepos, enableDecorrelate, false, enableExpand,
-          catalogReaderFactory, clusterFactory);
+          enableLateDecorrelate, catalogReaderFactory, clusterFactory);
+    }
+
+    public boolean isLateDecorrelate() {
+      return enableLateDecorrelate;
     }
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/bac9ee7c/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
----------------------------------------------------------------------
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 e0836dd..ba017bd 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -6190,4 +6190,40 @@ LogicalProject(JOB=[$0], EMPNO=[10], SAL=[$1], S=[$2])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testWhereNotInCorrelated">
+        <Resource name="sql">
+            <![CDATA[select sal from emp where empno NOT IN (select deptno from dept where emp.job = dept.name)]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(SAL=[$5])
+  LogicalFilter(condition=[NOT(IN($0, {
+LogicalProject(DEPTNO=[$0])
+  LogicalFilter(condition=[=($cor0.JOB, $1)])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+}))], variablesSet=[[$cor0]])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(SAL=[$5])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalFilter(condition=[IS NULL($11)])
+      LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], DEPTNO0=[CAST($9):INTEGER], JOB0=[CAST($10):VARCHAR(10) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary"], $f2=[CAST($11):BOOLEAN])
+        LogicalJoin(condition=[AND(=($2, $10), =($0, $9))], joinType=[inner])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+          LogicalProject(DEPTNO=[$0], JOB=[$1], $f2=[true])
+            LogicalAggregate(group=[{0, 1}])
+              LogicalProject(DEPTNO=[$0], JOB=[$2], i=[$1])
+                LogicalProject(DEPTNO=[$0], i=[true], JOB=[$1])
+                  LogicalProject(DEPTNO=[$0], JOB=[$2])
+                    LogicalJoin(condition=[=($2, $1)], joinType=[inner])
+                      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+                      LogicalAggregate(group=[{0}])
+                        LogicalProject(JOB=[$2])
+                          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>


[3/3] calcite git commit: [CALCITE-1524] Add a project to the planner root so that rules know which output fields are used

Posted by jh...@apache.org.
[CALCITE-1524] Add a project to the planner root so that rules know which output fields are used


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/751e2b0c
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/751e2b0c
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/751e2b0c

Branch: refs/heads/master
Commit: 751e2b0c6d6e19bef7f07b1e0acf1ce491b59311
Parents: 46654ad
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Dec 1 17:51:24 2016 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Dec 1 20:10:18 2016 -0800

----------------------------------------------------------------------
 .../org/apache/calcite/plan/RelOptMaterialization.java  |  2 ++
 .../main/java/org/apache/calcite/prepare/Prepare.java   |  7 +++++--
 core/src/main/java/org/apache/calcite/rel/RelRoot.java  | 12 +++++++++++-
 3 files changed, 18 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/751e2b0c/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java b/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
index df3fac5..7759e7b 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
@@ -31,6 +31,7 @@ import org.apache.calcite.rel.rules.FilterJoinRule;
 import org.apache.calcite.rel.rules.JoinProjectTransposeRule;
 import org.apache.calcite.rel.rules.ProjectFilterTransposeRule;
 import org.apache.calcite.rel.rules.ProjectMergeRule;
+import org.apache.calcite.rel.rules.ProjectRemoveRule;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.schema.Table;
@@ -272,6 +273,7 @@ public class RelOptMaterialization {
             JoinProjectTransposeRule.RIGHT_PROJECT,
             JoinProjectTransposeRule.LEFT_PROJECT,
             FilterJoinRule.FilterIntoJoinRule.FILTER_ON_JOIN,
+            ProjectRemoveRule.INSTANCE,
             ProjectMergeRule.INSTANCE),
         false,
         DefaultRelMetadataProvider.INSTANCE);

http://git-wip-us.apache.org/repos/asf/calcite/blob/751e2b0c/core/src/main/java/org/apache/calcite/prepare/Prepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/Prepare.java b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
index 2d97bb9..2064567 100644
--- a/core/src/main/java/org/apache/calcite/prepare/Prepare.java
+++ b/core/src/main/java/org/apache/calcite/prepare/Prepare.java
@@ -122,12 +122,15 @@ public abstract class Prepare {
    * @param lattices Lattices
    * @return an equivalent optimized relational expression
    */
-  protected RelRoot optimize(final RelRoot root,
+  protected RelRoot optimize(RelRoot root,
       final List<Materialization> materializations,
       final List<CalciteSchema.LatticeEntry> lattices) {
     final RelOptPlanner planner = root.rel.getCluster().getPlanner();
 
-    planner.setRoot(root.rel);
+    // Add a project to the root. Even if the project is trivial, it informs
+    // rules firing on the relational expression below it which of the fields
+    // are used. SemiJoinRule, for instance.
+    planner.setRoot(root.project(true));
 
     final RelTraitSet desiredTraits = getDesiredRootTraitSet(root);
     final Program program = getProgram();

http://git-wip-us.apache.org/repos/asf/calcite/blob/751e2b0c/core/src/main/java/org/apache/calcite/rel/RelRoot.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/RelRoot.java b/core/src/main/java/org/apache/calcite/rel/RelRoot.java
index 82506d2..a2fa71a 100644
--- a/core/src/main/java/org/apache/calcite/rel/RelRoot.java
+++ b/core/src/main/java/org/apache/calcite/rel/RelRoot.java
@@ -140,7 +140,17 @@ public class RelRoot {
   /** Returns the root relational expression, creating a {@link LogicalProject}
    * if necessary to remove fields that are not needed. */
   public RelNode project() {
-    if (isRefTrivial()) {
+    return project(false);
+  }
+
+  /** Returns the root relational expression as a {@link LogicalProject}.
+   *
+   * @param force Create a Project even if all fields are used */
+  public RelNode project(boolean force) {
+    if (isRefTrivial()
+        && (SqlKind.DML.contains(kind)
+            || !force
+            || rel instanceof LogicalProject)) {
       return rel;
     }
     final List<RexNode> projects = new ArrayList<>();