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 2014/08/11 21:46:04 UTC

[7/8] git commit: [OPTIQ-367] PushFilterPastJoinRule should strengthen join type

[OPTIQ-367] PushFilterPastJoinRule should strengthen join type

Make RexNode.isAlwaysTrue smart enough to deal with "c IS NOT NULL" on a column declared NOT NULL.

Change how EXISTS is translated (was "c IS TRUE", now "c IS NOT NULL", where c is a marker column from the null-generating side).

Keep a dumber version of PushFilterPastJoinRule around, to keep some tests working.


Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/201d5917
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/201d5917
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/201d5917

Branch: refs/heads/master
Commit: 201d5917931bb5e2a455ad786a671764bc8b3833
Parents: 5a42f3f
Author: Julian Hyde <jh...@apache.org>
Authored: Sat Aug 9 02:33:00 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Sat Aug 9 02:33:00 2014 -0700

----------------------------------------------------------------------
 .../java/org/eigenbase/rel/JoinRelType.java     |  26 ++++
 .../org/eigenbase/rel/metadata/RelMdUtil.java   |   7 +-
 .../rel/rules/PushFilterPastJoinRule.java       |  99 +++++++++-----
 .../java/org/eigenbase/relopt/RelOptUtil.java   | 134 ++++++++++++++-----
 .../main/java/org/eigenbase/rex/RexCall.java    |  21 +++
 .../eigenbase/sql2rel/SqlToRelConverter.java    |   2 +-
 .../org/eigenbase/test/RelMetadataTest.java     |   8 +-
 .../org/eigenbase/test/RelOptRulesTest.java     |  43 ++++--
 .../org/eigenbase/test/SqlToRelTestBase.java    |  27 +++-
 .../org/eigenbase/test/RelOptRulesTest.xml      |  46 +++++++
 .../eigenbase/test/SqlToRelConverterTest.xml    |  14 +-
 11 files changed, 335 insertions(+), 92 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/rel/JoinRelType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/JoinRelType.java b/core/src/main/java/org/eigenbase/rel/JoinRelType.java
index 4c4339c..6c73c13 100644
--- a/core/src/main/java/org/eigenbase/rel/JoinRelType.java
+++ b/core/src/main/java/org/eigenbase/rel/JoinRelType.java
@@ -63,6 +63,32 @@ public enum JoinRelType {
       throw new IllegalArgumentException("invalid: " + i);
     }
   }
+
+  /** Returns a join type similar to this but that does not generate nulls on
+   * the left. */
+  public JoinRelType cancelNullsOnLeft() {
+    switch (this) {
+    case RIGHT:
+      return INNER;
+    case FULL:
+      return LEFT;
+    default:
+      return this;
+    }
+  }
+
+  /** Returns a join type similar to this but that does not generate nulls on
+   * the right. */
+  public JoinRelType cancelNullsOnRight() {
+    switch (this) {
+    case LEFT:
+      return INNER;
+    case FULL:
+      return RIGHT;
+    default:
+      return this;
+    }
+  }
 }
 
 // End JoinRelType.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java b/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java
index b409309..03e6302 100644
--- a/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java
+++ b/core/src/main/java/org/eigenbase/rel/metadata/RelMdUtil.java
@@ -25,6 +25,7 @@ import org.eigenbase.relopt.*;
 import org.eigenbase.rex.*;
 import org.eigenbase.sql.*;
 import org.eigenbase.sql.type.*;
+import org.eigenbase.util.Holder;
 import org.eigenbase.util14.*;
 
 import net.hydromatic.optiq.util.BitSets;
@@ -660,12 +661,14 @@ public class RelMdUtil {
       RelOptUtil.classifyFilters(
           joinRel,
           predList,
-          joinType == JoinRelType.INNER,
+          joinType,
           !joinType.generatesNullsOnLeft(),
           !joinType.generatesNullsOnRight(),
           joinFilters,
           leftFilters,
-          rightFilters);
+          rightFilters,
+          Holder.of(joinType),
+          false);
 
       RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder();
       leftPred =

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java b/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java
index 7514926..f97cda6 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/PushFilterPastJoinRule.java
@@ -22,6 +22,7 @@ import org.eigenbase.rel.*;
 import org.eigenbase.relopt.*;
 import org.eigenbase.rex.*;
 import org.eigenbase.util.Bug;
+import org.eigenbase.util.Holder;
 
 import com.google.common.collect.ImmutableList;
 
@@ -31,39 +32,29 @@ import com.google.common.collect.ImmutableList;
  */
 public abstract class PushFilterPastJoinRule extends RelOptRule {
   public static final PushFilterPastJoinRule FILTER_ON_JOIN =
-      new PushFilterPastJoinRule(
-          operand(
-              FilterRel.class,
-              operand(JoinRelBase.class, any())),
-          "PushFilterPastJoinRule:filter") {
-        @Override
-        public void onMatch(RelOptRuleCall call) {
-          FilterRel filter = call.rel(0);
-          JoinRelBase join = call.rel(1);
-          perform(call, filter, join);
-        }
-      };
+      new PushFilterIntoJoinRule(true);
+
+  /** Dumber version of {@link #FILTER_ON_JOIN}. Not intended for production
+   * use, but keeps some tests working for which {@code FILTER_ON_JOIN} is too
+   * smart. */
+  public static final PushFilterPastJoinRule DUMB_FILTER_ON_JOIN =
+      new PushFilterIntoJoinRule(false);
 
   public static final PushFilterPastJoinRule JOIN =
-      new PushFilterPastJoinRule(
-          operand(JoinRelBase.class, any()),
-          "PushFilterPastJoinRule:no-filter") {
-        @Override
-        public void onMatch(RelOptRuleCall call) {
-          JoinRelBase join = call.rel(0);
-          perform(call, null, join);
-        }
-      };
+      new PushDownJoinConditionRule();
+
+  /** Whether to try to strengthen join-type. */
+  private final boolean smart;
 
   //~ Constructors -----------------------------------------------------------
 
   /**
    * Creates a PushFilterPastJoinRule with an explicit root operand.
    */
-  private PushFilterPastJoinRule(
-      RelOptRuleOperand operand,
-      String id) {
+  private PushFilterPastJoinRule(RelOptRuleOperand operand, String id,
+      boolean smart) {
     super(operand, "PushFilterRule: " + id);
+    this.smart = smart;
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -109,15 +100,18 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
     // filters. They can be pushed down if they are not on the NULL
     // generating side.
     boolean filterPushed = false;
+    final Holder<JoinRelType> joinTypeHolder = Holder.of(join.getJoinType());
     if (RelOptUtil.classifyFilters(
         join,
         aboveFilters,
-        join.getJoinType() == JoinRelType.INNER,
+        join.getJoinType(),
         !join.getJoinType().generatesNullsOnLeft(),
         !join.getJoinType().generatesNullsOnRight(),
         joinFilters,
         leftFilters,
-        rightFilters)) {
+        rightFilters,
+        joinTypeHolder,
+        smart)) {
       filterPushed = true;
     }
 
@@ -127,12 +121,14 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
     if (RelOptUtil.classifyFilters(
         join,
         joinFilters,
-        false,
+        null,
         !join.getJoinType().generatesNullsOnRight(),
         !join.getJoinType().generatesNullsOnLeft(),
         joinFilters,
         leftFilters,
-        rightFilters)) {
+        rightFilters,
+        joinTypeHolder,
+        smart)) {
       filterPushed = true;
     }
 
@@ -142,7 +138,7 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
 
     // create FilterRels on top of the children if any filters were
     // pushed to them
-    RexBuilder rexBuilder = join.getCluster().getRexBuilder();
+    final RexBuilder rexBuilder = join.getCluster().getRexBuilder();
     RelNode leftRel =
         createFilterOnRel(
             rexBuilder,
@@ -161,7 +157,9 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
     if (joinFilters.size() == 0) {
       // if nothing actually got pushed and there is nothing leftover,
       // then this rule is a no-op
-      if ((leftFilters.size() == 0) && (rightFilters.size() == 0)) {
+      if (leftFilters.isEmpty()
+          && rightFilters.isEmpty()
+          && joinTypeHolder.get() == join.getJoinType()) {
         return;
       }
       joinFilter = rexBuilder.makeLiteral(true);
@@ -175,7 +173,7 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
             joinFilter,
             leftRel,
             rightRel,
-            join.getJoinType(),
+            joinTypeHolder.get(),
             join.isSemiJoinDone());
     call.getPlanner().onCopy(join, newJoinRel);
 
@@ -190,6 +188,10 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
       call.getPlanner().onCopy(filter, rightRel);
     }
 
+    // Create a project on top of the join if some of the columns have become
+    // NOT NULL due to the join-type getting stricter.
+    newJoinRel = RelOptUtil.createCastRel(newJoinRel, join.getRowType(), false);
+
     // create a FilterRel on top of the join if needed
     RelNode newRel =
         createFilterOnRel(rexBuilder, newJoinRel, aboveFilters);
@@ -217,6 +219,41 @@ public abstract class PushFilterPastJoinRule extends RelOptRule {
     }
     return CalcRel.createFilter(rel, andFilters);
   }
+
+  /** Rule that pushes parts of the join condition to its inputs. */
+  private static class PushDownJoinConditionRule
+      extends PushFilterPastJoinRule {
+    public PushDownJoinConditionRule() {
+      super(RelOptRule.operand(JoinRelBase.class, RelOptRule.any()),
+          "PushFilterPastJoinRule:no-filter",
+          true);
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+      JoinRelBase join = call.rel(0);
+      perform(call, null, join);
+    }
+  }
+
+  /** Rule that tries to push filter expressions into a join
+   * condition and into the inputs of the join. */
+  private static class PushFilterIntoJoinRule extends PushFilterPastJoinRule {
+    public PushFilterIntoJoinRule(boolean smart) {
+      super(
+          RelOptRule.operand(FilterRel.class,
+              RelOptRule.operand(JoinRelBase.class, RelOptRule.any())),
+          "PushFilterPastJoinRule:filter",
+          smart);
+    }
+
+    @Override
+    public void onMatch(RelOptRuleCall call) {
+      FilterRel filter = call.rel(0);
+      JoinRelBase join = call.rel(1);
+      perform(call, filter, join);
+    }
+  }
 }
 
 // End PushFilterPastJoinRule.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
index 9f00333..e96e425 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
@@ -35,6 +35,7 @@ import net.hydromatic.linq4j.Ord;
 import net.hydromatic.optiq.util.BitSets;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 /**
  * <code>RelOptUtil</code> defines static utility methods for use in optimizing
@@ -1845,26 +1846,29 @@ public abstract class RelOptUtil {
    *
    * @param joinRel      join node
    * @param filters      filters to be classified
-   * @param pushJoin     true if filters originated from above the join node and
-   *                     the join is an inner join
+   * @param joinType     join type; determines whether filters can be pushed
+   *                     into the ON clause
    * @param pushLeft     true if filters can be pushed to the left
    * @param pushRight    true if filters can be pushed to the right
    * @param joinFilters  list of filters to push to the join
    * @param leftFilters  list of filters to push to the left child
    * @param rightFilters list of filters to push to the right child
+   * @param smart        Whether to try to strengthen the join type
    * @return true if at least one filter was pushed
    */
   public static boolean classifyFilters(
       RelNode joinRel,
       List<RexNode> filters,
-      boolean pushJoin,
+      JoinRelType joinType,
       boolean pushLeft,
       boolean pushRight,
       List<RexNode> joinFilters,
       List<RexNode> leftFilters,
-      List<RexNode> rightFilters) {
+      List<RexNode> rightFilters,
+      Holder<JoinRelType> joinTypeHolder,
+      boolean smart) {
     RexBuilder rexBuilder = joinRel.getCluster().getRexBuilder();
-    boolean filterPushed = false;
+    final JoinRelType oldJoinType = joinType;
     List<RelDataTypeField> joinFields = joinRel.getRowType().getFieldList();
     final int nTotalFields = joinFields.size();
     final int nSysFields = 0; // joinRel.getSystemFieldList().size();
@@ -1882,11 +1886,9 @@ public abstract class RelOptUtil {
     BitSet rightBitmap =
         BitSets.range(nSysFields + nFieldsLeft, nTotalFields);
 
-    ListIterator<RexNode> filterIter = filters.listIterator();
-    while (filterIter.hasNext()) {
-      RexNode filter = filterIter.next();
-
-      final BitSet filterBitmap = InputFinder.bits(filter);
+    final List<RexNode> filtersToRemove = Lists.newArrayList();
+    for (RexNode filter : filters) {
+      final InputFinder inputFinder = InputFinder.analyze(filter);
 
       // REVIEW - are there any expressions that need special handling
       // and therefore cannot be pushed?
@@ -1894,9 +1896,7 @@ public abstract class RelOptUtil {
       // filters can be pushed to the left child if the left child
       // does not generate NULLs and the only columns referenced in
       // the filter originate from the left child
-      if (pushLeft && BitSets.contains(leftBitmap, filterBitmap)) {
-        filterPushed = true;
-
+      if (pushLeft && BitSets.contains(leftBitmap, inputFinder.inputBitSet)) {
         // ignore filters that always evaluate to true
         if (!filter.isAlwaysTrue()) {
           // adjust the field references in the filter to reflect
@@ -1915,15 +1915,13 @@ public abstract class RelOptUtil {
 
           leftFilters.add(shiftedFilter);
         }
-        filterIter.remove();
+        filtersToRemove.add(filter);
 
         // filters can be pushed to the right child if the right child
         // does not generate NULLs and the only columns referenced in
         // the filter originate from the right child
-      } else if (
-          pushRight
-              && BitSets.contains(rightBitmap, filterBitmap)) {
-        filterPushed = true;
+      } else if (pushRight
+          && BitSets.contains(rightBitmap, inputFinder.inputBitSet)) {
         if (!filter.isAlwaysTrue()) {
           // adjust the field references in the filter to reflect
           // that fields in the right now shift over to the left;
@@ -1931,7 +1929,7 @@ public abstract class RelOptUtil {
           // child, the types of the source should match the dest
           // so we don't need to explicitly pass the destination
           // fields to RexInputConverter
-          final RexNode shilftedFilter =
+          final RexNode shiftedFilter =
               shiftFilter(
                   nSysFields + nFieldsLeft,
                   nTotalFields,
@@ -1941,23 +1939,55 @@ public abstract class RelOptUtil {
                   nTotalFields,
                   rightFields,
                   filter);
-          rightFilters.add(shilftedFilter);
+          rightFilters.add(shiftedFilter);
         }
-        filterIter.remove();
+        filtersToRemove.add(filter);
 
         // if the filter can't be pushed to either child and the join
         // is an inner join, push them to the join if they originated
         // from above the join
-      } else if (pushJoin) {
-        filterPushed = true;
+      } else if (joinType == JoinRelType.INNER) {
         joinFilters.add(filter);
-        filterIter.remove();
+        filtersToRemove.add(filter);
+
+        // If the filter will only evaluate to true if fields from the left
+        // are not null, and the left is null-generating, then we can make the
+        // left. Similarly for the right.
+      } else {
+        if (smart
+            && joinType != null
+            && joinType.generatesNullsOnRight()
+            && inputFinder.strongBitSet.intersects(rightBitmap)) {
+          filtersToRemove.add(filter);
+          joinType = joinType.cancelNullsOnRight();
+          joinTypeHolder.set(joinType);
+          if (!joinFilters.contains(filter)) {
+            joinFilters.add(filter);
+          }
+        }
+        if (smart
+            && joinType != null
+            && joinType.generatesNullsOnLeft()
+            && inputFinder.strongBitSet.intersects(leftBitmap)) {
+          filtersToRemove.add(filter);
+          joinType = joinType.cancelNullsOnLeft();
+          joinTypeHolder.set(joinType);
+          if (!joinFilters.contains(filter)) {
+            joinFilters.add(filter);
+          }
+        }
       }
 
       // else, leave the filter where it is
     }
 
-    return filterPushed;
+    // Remove filters after the loop, to prevent concurrent modification.
+    if (!filtersToRemove.isEmpty()) {
+      filters.removeAll(filtersToRemove);
+    }
+
+    // Did anything change?
+    return !filtersToRemove.isEmpty() || joinType != oldJoinType;
   }
 
   private static RexNode shiftFilter(
@@ -2488,28 +2518,60 @@ public abstract class RelOptUtil {
    * Visitor which builds a bitmap of the inputs used by an expression.
    */
   public static class InputFinder extends RexVisitorImpl<Void> {
-    private final BitSet rexRefSet;
+    final BitSet inputBitSet;
+    final BitSet strongBitSet = new BitSet();
     private final Set<RelDataTypeField> extraFields;
 
-    public InputFinder(BitSet rexRefSet) {
-      this(rexRefSet, null);
+    public InputFinder(BitSet inputBitSet) {
+      this(inputBitSet, null);
     }
 
-    public InputFinder(
-        BitSet rexRefSet,
-        Set<RelDataTypeField> extraFields) {
+    public InputFinder(BitSet inputBitSet, Set<RelDataTypeField> extraFields) {
       super(true);
-      this.rexRefSet = rexRefSet;
+      this.inputBitSet = inputBitSet;
       this.extraFields = extraFields;
     }
 
+    /** Returns an input finder that has analyzed a given expression. */
+    public static InputFinder analyze(RexNode node) {
+      final InputFinder inputFinder = new InputFinder(new BitSet());
+      node.accept(inputFinder);
+      inputFinder.strong(node);
+      return inputFinder;
+    }
+
+    private byte strong(RexNode node) {
+      switch (node.getKind()) {
+      case IS_TRUE:
+      case IS_NOT_NULL:
+      case AND:
+      case EQUALS:
+      case NOT_EQUALS:
+      case LESS_THAN:
+      case LESS_THAN_OR_EQUAL:
+      case GREATER_THAN:
+      case GREATER_THAN_OR_EQUAL:
+        return strong(((RexCall) node).getOperands());
+      case INPUT_REF:
+        strongBitSet.set(((RexInputRef) node).getIndex());
+        return 0;
+      default:
+        return 0;
+      }
+    }
+
+    private byte strong(List<RexNode> operands) {
+      for (RexNode operand : operands) {
+        strong(operand);
+      }
+      return 0;
+    }
+
     /**
      * Returns a bit set describing the inputs used by an expression.
      */
     public static BitSet bits(RexNode node) {
-      final BitSet inputBitSet = new BitSet();
-      node.accept(new InputFinder(inputBitSet));
-      return inputBitSet;
+      return analyze(node).inputBitSet;
     }
 
     /**
@@ -2523,7 +2585,7 @@ public abstract class RelOptUtil {
     }
 
     public Void visitInputRef(RexInputRef inputRef) {
-      rexRefSet.set(inputRef.getIndex());
+      inputBitSet.set(inputRef.getIndex());
       return null;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/rex/RexCall.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rex/RexCall.java b/core/src/main/java/org/eigenbase/rex/RexCall.java
index 3baf669..9d712b8 100644
--- a/core/src/main/java/org/eigenbase/rex/RexCall.java
+++ b/core/src/main/java/org/eigenbase/rex/RexCall.java
@@ -109,6 +109,27 @@ public class RexCall extends RexNode {
     return type;
   }
 
+  @Override
+  public boolean isAlwaysTrue() {
+    // "c IS NOT NULL" occurs when we expand EXISTS.
+    // This reduction allows us to convert it to a semi-join.
+    switch (getKind()) {
+    case IS_NOT_NULL:
+      return !operands.get(0).getType().isNullable();
+    default:
+      return false;
+    }
+  }
+
+  @Override public boolean isAlwaysFalse() {
+    switch (getKind()) {
+    case IS_NULL:
+      return !operands.get(0).getType().isNullable();
+    default:
+      return false;
+    }
+  }
+
   public SqlKind getKind() {
     return op.kind;
   }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
index c56c788..a03eb1b 100644
--- a/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
@@ -4084,7 +4084,7 @@ public class SqlToRelConverter {
         if (needTruthTest) {
           fieldAccess =
               rexBuilder.makeCall(
-                  SqlStdOperatorTable.IS_TRUE,
+                  SqlStdOperatorTable.IS_NOT_NULL,
                   fieldAccess);
         }
         return fieldAccess;

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/java/org/eigenbase/test/RelMetadataTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/RelMetadataTest.java b/core/src/test/java/org/eigenbase/test/RelMetadataTest.java
index 646ed09..a3484ae 100644
--- a/core/src/test/java/org/eigenbase/test/RelMetadataTest.java
+++ b/core/src/test/java/org/eigenbase/test/RelMetadataTest.java
@@ -439,10 +439,16 @@ public class RelMetadataTest extends SqlToRelTestBase {
 
   @Test public void testSelectivityIsNotNullFilter() {
     checkFilterSelectivity(
-        "select * from emp where deptno is not null",
+        "select * from emp where mgr is not null",
         DEFAULT_NOTNULL_SELECTIVITY);
   }
 
+  @Test public void testSelectivityIsNotNullFilterOnNotNullColumn() {
+    checkFilterSelectivity(
+        "select * from emp where deptno is not null",
+        1.0d);
+  }
+
   @Test public void testSelectivityComparisonFilter() {
     checkFilterSelectivity(
         "select * from emp where deptno > 10",

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java b/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
index 91347a0..5ebc1ea 100644
--- a/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/eigenbase/test/RelOptRulesTest.java
@@ -128,6 +128,27 @@ public class RelOptRulesTest extends RelOptTestBase {
         "select 1 from emp inner join dept on emp.deptno = dept.deptno");
   }
 
+  @Test public void testStrengthenJoinType() {
+    // The "Filter(... , right.c IS NOT NULL)" above a left join is pushed into
+    // the join, makes it an inner join, and then disappears because c is NOT
+    // NULL.
+    final HepProgram preProgram =
+        HepProgram.builder()
+            .addRuleInstance(MergeProjectRule.INSTANCE)
+            .addRuleInstance(PushFilterPastProjectRule.INSTANCE)
+            .build();
+    final HepProgram program =
+        HepProgram.builder()
+            .addRuleInstance(PushFilterPastJoinRule.FILTER_ON_JOIN)
+            .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)");
+  }
+
   @Test public void testPushFilterThroughOuterJoin() {
     checkPlanning(
         PushFilterPastJoinRule.FILTER_ON_JOIN,
@@ -776,10 +797,12 @@ public class RelOptRulesTest extends RelOptTestBase {
     final DiffRepository diffRepos = getDiffRepos();
     String sql = diffRepos.expand(null, "${sql}");
 
-    HepProgram program = new HepProgramBuilder().addRuleCollection(
-        Lists.newArrayList(PushFilterPastJoinRule.FILTER_ON_JOIN,
-            PushFilterPastJoinRule.JOIN, PushFilterPastProjectRule.INSTANCE,
-            PushFilterPastSetOpRule.INSTANCE)).build();
+    HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(PushFilterPastJoinRule.DUMB_FILTER_ON_JOIN)
+        .addRuleInstance(PushFilterPastJoinRule.JOIN)
+        .addRuleInstance(PushFilterPastProjectRule.INSTANCE)
+        .addRuleInstance(PushFilterPastSetOpRule.INSTANCE)
+        .build();
     HepPlanner planner = new HepPlanner(program);
 
     RelNode relInitial = tester.convertSqlToRel(sql);
@@ -803,12 +826,12 @@ public class RelOptRulesTest extends RelOptTestBase {
 
     HepProgram program2 = new HepProgramBuilder()
         .addMatchOrder(HepMatchOrder.BOTTOM_UP)
-        .addRuleCollection(
-            Lists.newArrayList(PushFilterPastJoinRule.FILTER_ON_JOIN,
-                PushFilterPastJoinRule.JOIN,
-                PushFilterPastProjectRule.INSTANCE,
-                PushFilterPastSetOpRule.INSTANCE,
-                TransitivePredicatesOnJoinRule.INSTANCE)).build();
+        .addRuleInstance(PushFilterPastJoinRule.DUMB_FILTER_ON_JOIN)
+        .addRuleInstance(PushFilterPastJoinRule.JOIN)
+        .addRuleInstance(PushFilterPastProjectRule.INSTANCE)
+        .addRuleInstance(PushFilterPastSetOpRule.INSTANCE)
+        .addRuleInstance(TransitivePredicatesOnJoinRule.INSTANCE)
+        .build();
     HepPlanner planner2 = new HepPlanner(program2);
     planner.registerMetadataProviders(list);
     planner2.setRoot(relAfter);

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java b/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java
index d8b78a8..5c761dc 100644
--- a/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java
+++ b/core/src/test/java/org/eigenbase/test/SqlToRelTestBase.java
@@ -66,7 +66,7 @@ public abstract class SqlToRelTestBase {
   }
 
   protected Tester createTester() {
-    return new TesterImpl(getDiffRepos(), true, null);
+    return new TesterImpl(getDiffRepos(), true, false, null);
   }
 
   /**
@@ -172,6 +172,9 @@ public abstract class SqlToRelTestBase {
 
     Tester withCatalogReaderFactory(
         Function<RelDataTypeFactory, Prepare.CatalogReader> factory);
+
+    /** Returns a tester that optionally trims unused fields. */
+    Tester withTrim(boolean enable);
   }
 
   //~ Inner Classes ----------------------------------------------------------
@@ -394,6 +397,7 @@ public abstract class SqlToRelTestBase {
     private SqlOperatorTable opTab;
     private final DiffRepository diffRepos;
     private final boolean enableDecorrelate;
+    private final boolean enableTrim;
     private final Function<RelDataTypeFactory, Prepare.CatalogReader>
     catalogReaderFactory;
     private RelDataTypeFactory typeFactory;
@@ -403,13 +407,16 @@ public abstract class SqlToRelTestBase {
      *
      * @param diffRepos Diff repository
      * @param enableDecorrelate Whether to decorrelate
+     * @param enableTrim Whether to trim unused fields
      * @param catalogReaderFactory Function to create catalog reader, or null
      */
     protected TesterImpl(DiffRepository diffRepos, boolean enableDecorrelate,
+        boolean enableTrim,
         Function<RelDataTypeFactory, Prepare.CatalogReader>
             catalogReaderFactory) {
       this.diffRepos = diffRepos;
       this.enableDecorrelate = enableDecorrelate;
+      this.enableTrim = enableTrim;
       this.catalogReaderFactory = catalogReaderFactory;
     }
 
@@ -437,10 +444,16 @@ public abstract class SqlToRelTestBase {
       RelNode rel =
           converter.convertQuery(validatedQuery, false, true);
       assert rel != null;
-      if (enableDecorrelate) {
+      if (enableDecorrelate || enableTrim) {
         rel = converter.flattenTypes(rel, true);
+      }
+      if (enableDecorrelate) {
         rel = converter.decorrelate(sqlQuery, rel);
       }
+      if (enableTrim) {
+        converter.setTrimUnusedFields(true);
+        rel = converter.trimUnusedFields(rel);
+      }
       return rel;
     }
 
@@ -591,12 +604,18 @@ public abstract class SqlToRelTestBase {
 
     public TesterImpl withDecorrelation(boolean enable) {
       return this.enableDecorrelate == enable ? this
-          : new TesterImpl(diffRepos, enable, catalogReaderFactory);
+          : new TesterImpl(diffRepos, enable, enableTrim, catalogReaderFactory);
+    }
+
+    public Tester withTrim(boolean enable) {
+      return this.enableTrim == enable ? this
+          : new TesterImpl(diffRepos, enableDecorrelate, enable,
+              catalogReaderFactory);
     }
 
     public Tester withCatalogReaderFactory(
         Function<RelDataTypeFactory, Prepare.CatalogReader> factory) {
-      return new TesterImpl(diffRepos, enableDecorrelate, factory);
+      return new TesterImpl(diffRepos, enableDecorrelate, false, factory);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml b/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
index e33ca4a..bee6e64 100644
--- a/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/eigenbase/test/RelOptRulesTest.xml
@@ -2003,4 +2003,50 @@ ProjectRel(EXPR$0=[1])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testStrengthenJoinType">
+        <Resource name="sql">
+            <![CDATA[select * from dept where exists (
+  select * from emp
+  where emp.deptno = dept.deptno
+  and emp.sal > 100)]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+ProjectRel(DEPTNO=[$0], NAME=[$1])
+  ProjectRel(DEPTNO=[$0], NAME=[$1], $f0=[$3])
+    FilterRel(condition=[IS NOT NULL($3)])
+      JoinRel(condition=[=($0, $2)], joinType=[left])
+        TableAccessRel(table=[[CATALOG, SALES, DEPT]])
+        AggregateRel(group=[{0}], agg#0=[MIN($1)])
+          ProjectRel($f01=[$2], $f0=[true])
+            FilterRel(condition=[AND(=($1, $2), >($0, 100))])
+              JoinRel(condition=[true], joinType=[inner])
+                ProjectRel(SAL=[$5], DEPTNO=[$7])
+                  TableAccessRel(table=[[CATALOG, SALES, EMP]])
+                AggregateRel(group=[{0}])
+                  ProjectRel($f0=[$0])
+                    ProjectRel(DEPTNO=[$0])
+                      TableAccessRel(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+ProjectRel(DEPTNO=[$0], NAME=[$1])
+  ProjectRel(DEPTNO=[$0], NAME=[$1], $f0=[$3])
+    ProjectRel(DEPTNO=[$0], NAME=[$1], $f01=[CAST($2):INTEGER], $f1=[CAST($3):BOOLEAN])
+      JoinRel(condition=[=($0, $2)], joinType=[inner])
+        TableAccessRel(table=[[CATALOG, SALES, DEPT]])
+        AggregateRel(group=[{0}], agg#0=[MIN($1)])
+          ProjectRel($f01=[$2], $f0=[true])
+            JoinRel(condition=[=($1, $2)], joinType=[inner])
+              FilterRel(condition=[>($0, 100)])
+                ProjectRel(SAL=[$5], DEPTNO=[$7])
+                  TableAccessRel(table=[[CATALOG, SALES, EMP]])
+              AggregateRel(group=[{0}])
+                ProjectRel($f0=[$0])
+                  ProjectRel(DEPTNO=[$0])
+                    TableAccessRel(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/201d5917/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml
index ae448bc..1e4d24b 100644
--- a/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/eigenbase/test/SqlToRelConverterTest.xml
@@ -348,7 +348,7 @@ ProjectRel(DEPTNO=[$0], NAME=[$1], EMPSET=[$2])
         <Resource name="plan">
             <![CDATA[
 ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
-  FilterRel(condition=[IS TRUE($9)])
+  FilterRel(condition=[IS NOT NULL($9)])
     JoinRel(condition=[true], joinType=[left])
       TableAccessRel(table=[[CATALOG, SALES, EMP]])
       AggregateRel(group=[{}], agg#0=[MIN($0)])
@@ -378,7 +378,7 @@ ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5],
         <Resource name="plan">
             <![CDATA[
 ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
-  FilterRel(condition=[IS TRUE($9)])
+  FilterRel(condition=[IS NOT NULL($9)])
     CorrelatorRel(condition=[true], joinType=[left], correlations=[[var0=offset7]])
       TableAccessRel(table=[[CATALOG, SALES, EMP]])
       AggregateRel(group=[{}], agg#0=[MIN($0)])
@@ -1594,7 +1594,7 @@ where exists (
         <Resource name="plan">
             <![CDATA[
 ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
-  FilterRel(condition=[IS TRUE($9)])
+  FilterRel(condition=[IS NOT NULL($9)])
     CorrelatorRel(condition=[true], joinType=[left], correlations=[[var1=offset7, var0=offset7]])
       TableAccessRel(table=[[CATALOG, SALES, EMP]])
       AggregateRel(group=[{}], agg#0=[MIN($0)])
@@ -1698,7 +1698,7 @@ where exists (
         <Resource name="plan">
             <![CDATA[
 ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
-  FilterRel(condition=[IS TRUE($9)])
+  FilterRel(condition=[IS NOT NULL($9)])
     ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f0=[$11])
       JoinRel(condition=[AND(=($7, $10), =($7, $9))], joinType=[left])
         TableAccessRel(table=[[CATALOG, SALES, EMP]])
@@ -1728,7 +1728,7 @@ ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5],
         <Resource name="plan">
             <![CDATA[
 ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
-  FilterRel(condition=[IS TRUE($9)])
+  FilterRel(condition=[IS NOT NULL($9)])
     ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f0=[$10])
       JoinRel(condition=[=($7, $9)], joinType=[left])
         TableAccessRel(table=[[CATALOG, SALES, EMP]])
@@ -1753,7 +1753,7 @@ ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5],
         <Resource name="plan">
             <![CDATA[
 ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
-  FilterRel(condition=[IS TRUE($9)])
+  FilterRel(condition=[IS NOT NULL($9)])
     CorrelatorRel(condition=[true], joinType=[left], correlations=[[var0=offset7]])
       TableAccessRel(table=[[CATALOG, SALES, EMP]])
       AggregateRel(group=[{}], agg#0=[MIN($0)])
@@ -1773,7 +1773,7 @@ ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5],
         <Resource name="plan">
             <![CDATA[
 ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
-  FilterRel(condition=[IS TRUE($9)])
+  FilterRel(condition=[IS NOT NULL($9)])
     ProjectRel(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f0=[$9])
       CorrelatorRel(condition=[true], joinType=[left], correlations=[[var0=offset7]])
         TableAccessRel(table=[[CATALOG, SALES, EMP]])