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/12/27 09:04:06 UTC

[2/2] incubator-calcite git commit: [CALCITE-448] FilterIntoJoinRule creates filters containing invalid RexInputRef

[CALCITE-448] FilterIntoJoinRule creates filters containing invalid RexInputRef

Refactor FilterJoinRule so you can supply a predicate rather than sub-classing.


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

Branch: refs/heads/master
Commit: 011a6b4ce6cc999bab126dfc2fee58c23aaab074
Parents: 3a225ed
Author: julianhyde <jh...@apache.org>
Authored: Fri Dec 26 22:04:28 2014 -0800
Committer: julianhyde <jh...@apache.org>
Committed: Fri Dec 26 23:10:19 2014 -0800

----------------------------------------------------------------------
 .../org/apache/calcite/rel/core/Filter.java     |   7 +-
 .../calcite/rel/rules/FilterJoinRule.java       | 105 +++++++++++--------
 .../rel/rules/FilterProjectTransposeRule.java   |  31 ++++--
 .../apache/calcite/rex/RexProgramBuilder.java   |   2 +-
 .../java/org/apache/calcite/rex/RexUtil.java    |  45 +++++++-
 .../apache/calcite/test/RelOptRulesTest.java    |  43 ++++++++
 .../org/apache/calcite/test/RelOptRulesTest.xml |  36 +++++++
 7 files changed, 213 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/011a6b4c/core/src/main/java/org/apache/calcite/rel/core/Filter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Filter.java b/core/src/main/java/org/apache/calcite/rel/core/Filter.java
index ca271db..0dc3009 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Filter.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Filter.java
@@ -20,6 +20,7 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.prepare.CalcitePrepareImpl;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelWriter;
@@ -72,7 +73,7 @@ public abstract class Filter extends SingleRel {
     assert RexUtil.isFlat(condition) : condition;
     this.condition = condition;
     // Too expensive for everyday use:
-    // assert isValid(true);
+    assert !CalcitePrepareImpl.DEBUG || isValid(true);
   }
 
   /**
@@ -110,6 +111,10 @@ public abstract class Filter extends SingleRel {
   }
 
   @Override public boolean isValid(boolean fail) {
+    if (RexUtil.isNullabilityCast(getCluster().getTypeFactory(), condition)) {
+      assert !fail : "Cast for just nullability not allowed";
+      return false;
+    }
     final RexChecker checker = new RexChecker(getInput().getRowType(), fail);
     condition.accept(checker);
     if (checker.getFailureCount() > 0) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/011a6b4c/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java b/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
index bef2844..bb0b8cc 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
@@ -35,6 +35,7 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -42,18 +43,31 @@ import java.util.List;
  * within a join node into the join node and/or its children nodes.
  */
 public abstract class FilterJoinRule extends RelOptRule {
+  /** Predicate that always returns true. With this predicate, every filter
+   * will be pushed into the ON clause. */
+  public static final Predicate TRUE_PREDICATE =
+      new Predicate() {
+        public boolean apply(Join join, JoinRelType joinType, RexNode exp) {
+          return true;
+        }
+      };
+
+  /** Rule that pushes predicates from a Filter into the Join below them. */
   public static final FilterJoinRule FILTER_ON_JOIN =
-      new FilterIntoJoinRule(true);
+      new FilterIntoJoinRule(true, RelFactories.DEFAULT_FILTER_FACTORY,
+          RelFactories.DEFAULT_PROJECT_FACTORY, TRUE_PREDICATE);
 
   /** 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 FilterJoinRule DUMB_FILTER_ON_JOIN =
-      new FilterIntoJoinRule(false);
+      new FilterIntoJoinRule(false, RelFactories.DEFAULT_FILTER_FACTORY,
+          RelFactories.DEFAULT_PROJECT_FACTORY, TRUE_PREDICATE);
 
+  /** Rule that pushes predicates in a Join into the inputs to the join. */
   public static final FilterJoinRule JOIN =
       new JoinConditionPushRule(RelFactories.DEFAULT_FILTER_FACTORY,
-          RelFactories.DEFAULT_PROJECT_FACTORY);
+          RelFactories.DEFAULT_PROJECT_FACTORY, TRUE_PREDICATE);
 
   /** Whether to try to strengthen join-type. */
   private final boolean smart;
@@ -62,6 +76,11 @@ public abstract class FilterJoinRule extends RelOptRule {
 
   private final RelFactories.ProjectFactory projectFactory;
 
+  /** Predicate that returns whether a filter is valid in the ON clause of a
+   * join for this particular kind of join. If not, Calcite will push it back to
+   * above the join. */
+  private final Predicate predicate;
+
   //~ Constructors -----------------------------------------------------------
 
   /**
@@ -71,10 +90,22 @@ public abstract class FilterJoinRule extends RelOptRule {
   protected FilterJoinRule(RelOptRuleOperand operand, String id,
       boolean smart, RelFactories.FilterFactory filterFactory,
       RelFactories.ProjectFactory projectFactory) {
-    super(operand, "PushFilterRule:" + id);
+    this(operand, id, smart, filterFactory, projectFactory, TRUE_PREDICATE);
+  }
+
+  /**
+   * Creates a FilterProjectTransposeRule with an explicit root operand and
+   * factories.
+   */
+  protected FilterJoinRule(RelOptRuleOperand operand, String id,
+      boolean smart, RelFactories.FilterFactory filterFactory,
+      RelFactories.ProjectFactory projectFactory,
+      Predicate predicate) {
+    super(operand, "FilterJoinRule:" + id);
     this.smart = smart;
     this.filterFactory = filterFactory;
     this.projectFactory = projectFactory;
+    this.predicate = predicate;
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -218,7 +249,9 @@ public abstract class FilterJoinRule extends RelOptRule {
 
     // create a LogicalFilter on top of the join if needed
     RelNode newRel =
-        RelOptUtil.createFilter(newJoinRel, aboveFilters, filterFactory);
+        RelOptUtil.createFilter(newJoinRel,
+            RexUtil.fixUp(rexBuilder, aboveFilters, newJoinRel.getRowType()),
+            filterFactory);
 
     call.transformTo(newRel);
   }
@@ -236,45 +269,28 @@ public abstract class FilterJoinRule extends RelOptRule {
    * @param aboveFilters Filter above Join
    * @param joinFilters Filters in join condition
    * @param join Join
-   *
-   * @deprecated Use
-   * {@link #validateJoinFilters(java.util.List, java.util.List, org.apache.calcite.rel.core.Join, org.apache.calcite.rel.core.JoinRelType)};
-   * very short-term; will be removed before
-   * {@link org.apache.calcite.util.Bug#upgrade(String) calcite-0.9.2}.
-   */
-  protected void validateJoinFilters(List<RexNode> aboveFilters,
-      List<RexNode> joinFilters, Join join) {
-    validateJoinFilters(aboveFilters, joinFilters, join, join.getJoinType());
-  }
-
-  /**
-   * Validates that target execution framework can satisfy join filters.
-   *
-   * <p>If the join filter cannot be satisfied (for example, if it is
-   * {@code l.c1 > r.c2} and the join only supports equi-join), removes the
-   * filter from {@code joinFilters} and adds it to {@code aboveFilters}.
-   *
-   * <p>The default implementation does nothing; i.e. the join can handle all
-   * conditions.
-   *
-   * @param aboveFilters Filter above Join
-   * @param joinFilters Filters in join condition
-   * @param join Join
    * @param joinType JoinRelType could be different from type in Join due to
    * outer join simplification.
    */
   protected void validateJoinFilters(List<RexNode> aboveFilters,
       List<RexNode> joinFilters, Join join, JoinRelType joinType) {
-    return;
+    final Iterator<RexNode> filterIter = joinFilters.iterator();
+    while (filterIter.hasNext()) {
+      RexNode exp = filterIter.next();
+      if (!predicate.apply(join, joinType, exp)) {
+        aboveFilters.add(exp);
+        filterIter.remove();
+      }
+    }
   }
 
   /** Rule that pushes parts of the join condition to its inputs. */
   public static class JoinConditionPushRule extends FilterJoinRule {
     public JoinConditionPushRule(RelFactories.FilterFactory filterFactory,
-        RelFactories.ProjectFactory projectFactory) {
+        RelFactories.ProjectFactory projectFactory, Predicate predicate) {
       super(RelOptRule.operand(Join.class, RelOptRule.any()),
-          "FilterJoinRule:no-filter",
-          true, filterFactory, projectFactory);
+          "FilterJoinRule:no-filter", true, filterFactory, projectFactory,
+          predicate);
     }
 
     @Override public void onMatch(RelOptRuleCall call) {
@@ -286,19 +302,15 @@ public abstract class FilterJoinRule extends RelOptRule {
   /** Rule that tries to push filter expressions into a join
    * condition and into the inputs of the join. */
   public static class FilterIntoJoinRule extends FilterJoinRule {
-    public FilterIntoJoinRule(boolean smart) {
-      this(smart, RelFactories.DEFAULT_FILTER_FACTORY,
-          RelFactories.DEFAULT_PROJECT_FACTORY);
-    }
-
     public FilterIntoJoinRule(boolean smart,
         RelFactories.FilterFactory filterFactory,
-        RelFactories.ProjectFactory projectFactory) {
+        RelFactories.ProjectFactory projectFactory,
+        Predicate predicate) {
       super(
-          RelOptRule.operand(Filter.class,
-              RelOptRule.operand(Join.class, RelOptRule.any())),
-          "FilterJoinRule:filter",
-          smart, filterFactory, projectFactory);
+          operand(Filter.class,
+              operand(Join.class, RelOptRule.any())),
+          "FilterJoinRule:filter", smart, filterFactory, projectFactory,
+          predicate);
     }
 
     @Override public void onMatch(RelOptRuleCall call) {
@@ -307,6 +319,13 @@ public abstract class FilterJoinRule extends RelOptRule {
       perform(call, filter, join);
     }
   }
+
+  /** Predicate that returns whether a filter is valid in the ON clause of a
+   * join for this particular kind of join. If not, Calcite will push it back to
+   * above the join. */
+  public interface Predicate {
+    boolean apply(Join join, JoinRelType joinType, RexNode exp);
+  }
 }
 
 // End FilterJoinRule.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/011a6b4c/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
index 2f5b720..802cf87 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
@@ -23,8 +23,11 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rex.RexCall;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexOver;
+import org.apache.calcite.rex.RexUtil;
 
 /**
  * Planner rule that pushes
@@ -70,10 +73,10 @@ public class FilterProjectTransposeRule extends RelOptRule {
 
   // implement RelOptRule
   public void onMatch(RelOptRuleCall call) {
-    final Filter filterRel = call.rel(0);
-    final Project projRel = call.rel(1);
+    final Filter filter = call.rel(0);
+    final Project project = call.rel(1);
 
-    if (RexOver.containsOver(projRel.getProjects(), null)) {
+    if (RexOver.containsOver(project.getProjects(), null)) {
       // In general a filter cannot be pushed below a windowing calculation.
       // Applying the filter before the aggregation function changes
       // the results of the windowing invocation.
@@ -85,20 +88,28 @@ public class FilterProjectTransposeRule extends RelOptRule {
 
     // convert the filter to one that references the child of the project
     RexNode newCondition =
-        RelOptUtil.pushFilterPastProject(filterRel.getCondition(), projRel);
+        RelOptUtil.pushFilterPastProject(filter.getCondition(), project);
+
+    // Remove cast of BOOLEAN NOT NULL to BOOLEAN or vice versa. Filter accepts
+    // nullable and not-nullable conditions, but a CAST might get in the way of
+    // other rewrites.
+    final RelDataTypeFactory typeFactory = filter.getCluster().getTypeFactory();
+    if (RexUtil.isNullabilityCast(typeFactory, newCondition)) {
+      newCondition = ((RexCall) newCondition).getOperands().get(0);
+    }
 
     RelNode newFilterRel =
         filterFactory == null
-            ? filterRel.copy(filterRel.getTraitSet(), projRel.getInput(),
+            ? filter.copy(filter.getTraitSet(), project.getInput(),
                 newCondition)
-            : filterFactory.createFilter(projRel.getInput(), newCondition);
+            : filterFactory.createFilter(project.getInput(), newCondition);
 
     RelNode newProjRel =
         projectFactory == null
-            ? projRel.copy(projRel.getTraitSet(), newFilterRel,
-                projRel.getProjects(), projRel.getRowType())
-            : projectFactory.createProject(newFilterRel, projRel.getProjects(),
-                projRel.getRowType().getFieldNames());
+            ? project.copy(project.getTraitSet(), newFilterRel,
+                project.getProjects(), project.getRowType())
+            : projectFactory.createProject(newFilterRel, project.getProjects(),
+                project.getRowType().getFieldNames());
 
     call.transformTo(newProjRel);
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/011a6b4c/core/src/main/java/org/apache/calcite/rex/RexProgramBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexProgramBuilder.java b/core/src/main/java/org/apache/calcite/rex/RexProgramBuilder.java
index 2b70160..f0d5c3d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexProgramBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexProgramBuilder.java
@@ -36,7 +36,7 @@ import java.util.Map;
  *
  * <p>RexProgramBuilder is necessary because a {@link RexProgram} is immutable.
  * (The {@link String} class has the same problem: it is immutable, so they
- * introduced {@link StringBuffer}.)
+ * introduced {@link StringBuilder}.)
  */
 public class RexProgramBuilder {
   //~ Instance fields --------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/011a6b4c/core/src/main/java/org/apache/calcite/rex/RexUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
index 8fa85c0..c5efd29 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -204,7 +204,21 @@ public class RexUtil {
     }
   }
 
-  /**
+  /** Returns whether an expression is a cast just for the purposes of
+   * nullability, not changing any other aspect of the type. */
+  public static boolean isNullabilityCast(RelDataTypeFactory typeFactory,
+      RexNode node) {
+    switch (node.getKind()) {
+    case CAST:
+      final RexCall call = (RexCall) node;
+      final RexNode arg0 = call.getOperands().get(0);
+      return SqlTypeUtil.equalSansNullability(typeFactory, arg0.getType(),
+          call.getType());
+    }
+    return false;
+  }
+
+   /**
    * Returns whether a given node contains a RexCall with a specified operator
    *
    * @param operator to look for
@@ -1075,6 +1089,35 @@ public class RexUtil {
     return new CnfHelper(rexBuilder).pull(node);
   }
 
+  /** Fixes up the type of all {@link RexInputRef}s in an
+   * expression to match differences in nullability.
+   *
+   * <p>Such differences in nullability occur when expressions are moved
+   * through outer joins.
+   *
+   * <p>Throws if there any greater inconsistencies of type. */
+  public static List<RexNode> fixUp(final RexBuilder rexBuilder,
+      List<RexNode> nodes, final RelDataType rowType) {
+    final List<RelDataType> typeList = RelOptUtil.getFieldTypeList(rowType);
+    return new RexShuttle() {
+      @Override public RexNode visitInputRef(RexInputRef ref) {
+        final RelDataType rightType = typeList.get(ref.getIndex());
+        final RelDataType refType = ref.getType();
+        if (refType == rightType) {
+          return ref;
+        }
+        final RelDataType refType2 =
+            rexBuilder.getTypeFactory().createTypeWithNullability(refType,
+                rightType.isNullable());
+        if (refType2 == rightType) {
+          return new RexInputRef(ref.getIndex(), refType2);
+        }
+        throw new AssertionError("mismatched type " + ref + " " + rightType);
+      }
+      // CHECKSTYLE: IGNORE 1
+    }.apply(nodes);
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/011a6b4c/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 cd2afe4..4e50e89 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -24,6 +24,9 @@ import org.apache.calcite.plan.hep.HepProgram;
 import org.apache.calcite.plan.hep.HepProgramBuilder;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.logical.LogicalTableModify;
 import org.apache.calcite.rel.metadata.CachingRelMetadataProvider;
 import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider;
@@ -66,6 +69,7 @@ import org.apache.calcite.rel.rules.UnionToDistinctRule;
 import org.apache.calcite.rel.rules.ValuesReduceRule;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlValidator;
@@ -242,6 +246,45 @@ public class RelOptRulesTest extends RelOptTestBase {
             + "where dept1.c1 > 'c' and (dept1.c2 > 30 or dept1.c1 < 'z')");
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-448">[CALCITE-448],
+   * FilterIntoJoinRule creates filters containing invalid RexInputRef</a>. */
+  @Test public void testPushFilterPastProject() {
+    final HepProgram preProgram =
+        HepProgram.builder()
+            .addRuleInstance(ProjectMergeRule.INSTANCE)
+            .build();
+    final FilterJoinRule.Predicate predicate =
+        new FilterJoinRule.Predicate() {
+          public boolean apply(Join join, JoinRelType joinType, RexNode exp) {
+            return joinType != JoinRelType.INNER;
+          }
+        };
+    final FilterJoinRule join =
+        new FilterJoinRule.JoinConditionPushRule(
+            RelFactories.DEFAULT_FILTER_FACTORY,
+            RelFactories.DEFAULT_PROJECT_FACTORY, predicate);
+    final FilterJoinRule filterOnJoin =
+        new FilterJoinRule.FilterIntoJoinRule(true,
+            RelFactories.DEFAULT_FILTER_FACTORY,
+            RelFactories.DEFAULT_PROJECT_FACTORY, predicate);
+    final HepProgram program =
+        HepProgram.builder()
+            .addGroupBegin()
+            .addRuleInstance(FilterProjectTransposeRule.INSTANCE)
+            .addRuleInstance(join)
+            .addRuleInstance(filterOnJoin)
+            .addGroupEnd()
+            .build();
+    checkPlanning(tester,
+        preProgram,
+        new HepPlanner(program),
+        "select a.name\n"
+            + "from dept a\n"
+            + "left join dept b on b.deptno > 10\n"
+            + "right join dept c on b.deptno > 10\n");
+  }
+
   @Test public void testSemiJoinRule() {
     final HepProgram preProgram =
         HepProgram.builder()

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/011a6b4c/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 436e0b2..8603a5f 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -3167,6 +3167,42 @@ LogicalProject(C1=[$0])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testPushFilterPastProject">
+        <Resource name="sql">
+            <![CDATA[select a.name
+from dept a
+left join dept b on b.deptno > 10
+right join dept c on b.deptno > 10
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(NAME=[$1])
+  LogicalJoin(condition=[$4], joinType=[right])
+    LogicalProject(DEPTNO=[$0], NAME=[$1], DEPTNO0=[$2], NAME0=[$3], $f4=[>($2, 10)])
+      LogicalJoin(condition=[$4], joinType=[left])
+        LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+        LogicalProject(DEPTNO=[$0], NAME=[$1], $f2=[>($0, 10)])
+          LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(NAME=[$1])
+  LogicalJoin(condition=[true], joinType=[right])
+    LogicalProject(DEPTNO=[$0], NAME=[$1], DEPTNO0=[$2], NAME0=[$3], $f4=[>($2, 10)])
+      LogicalProject(DEPTNO=[$0], NAME=[$1], DEPTNO0=[CAST($2):INTEGER], NAME0=[CAST($3):VARCHAR(10) CHARACTER SET "ISO-8859-1" COLLATE "ISO-8859-1$en_US$primary"], $f2=[CAST($4):BOOLEAN])
+        LogicalJoin(condition=[true], joinType=[inner])
+          LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+          LogicalProject(DEPTNO=[$0], NAME=[$1], $f2=[>($0, 10)])
+            LogicalFilter(condition=[>($0, 10)])
+              LogicalFilter(condition=[>($0, 10)])
+                LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
     <TestCase name="testMergeFilter">
         <Resource name="sql">
             <![CDATA[select name from (