You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by am...@apache.org on 2014/11/11 06:51:23 UTC

incubator-drill git commit: DRILL-1455: enable projection pushdown past join

Repository: incubator-drill
Updated Branches:
  refs/heads/master d5b9b11c9 -> 66cf6e274


DRILL-1455: enable projection pushdown past join

i) refactor project past filter to remove redundant code
ii) adding unit tests for projection past join & hybrid cases like projection past filter & join
iii) disable one TestExampleQueries#testSelectStartSubQueryJoinWithWhereClause until DRILL-1680 is fixed.


Project: http://git-wip-us.apache.org/repos/asf/incubator-drill/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-drill/commit/66cf6e27
Tree: http://git-wip-us.apache.org/repos/asf/incubator-drill/tree/66cf6e27
Diff: http://git-wip-us.apache.org/repos/asf/incubator-drill/diff/66cf6e27

Branch: refs/heads/master
Commit: 66cf6e274af2ac375b20530fd520f3ed32409101
Parents: d5b9b11
Author: Hanifi Gunes <hg...@maprtech.com>
Authored: Fri Nov 7 16:58:08 2014 -0800
Committer: Aman Sinha <as...@maprtech.com>
Committed: Mon Nov 10 18:18:47 2014 -0800

----------------------------------------------------------------------
 .../exec/planner/logical/DrillConditions.java   |  36 ++++++
 .../logical/DrillPushProjectPastFilterRule.java |  72 +----------
 .../logical/DrillPushProjectPastJoinRule.java   |  32 +++++
 .../exec/planner/logical/DrillRuleSets.java     |  13 +-
 .../org/apache/drill/TestExampleQueries.java    |  23 ++--
 .../org/apache/drill/TestProjectPushDown.java   | 127 +++++++++++++++++--
 .../test/resources/project/pushdown/empty0.json |   0
 .../test/resources/project/pushdown/empty1.json |   0
 .../test/resources/project/pushdown/empty2.json |   0
 9 files changed, 215 insertions(+), 88 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillConditions.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillConditions.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillConditions.java
new file mode 100644
index 0000000..310ef82
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillConditions.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.drill.exec.planner.logical;
+
+import org.eigenbase.rel.rules.PushProjector;
+import org.eigenbase.rex.RexCall;
+import org.eigenbase.rex.RexNode;
+
+public final class DrillConditions {
+
+  public static PushProjector.ExprCondition PRESERVE_ITEM = new PushProjector.ExprCondition() {
+    @Override
+    public boolean test(RexNode expr) {
+      if (expr instanceof RexCall) {
+        RexCall call = (RexCall)expr;
+        return "item".equals(call.getOperator().getName().toLowerCase());
+      }
+      return false;
+    }
+  };
+}

http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastFilterRule.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastFilterRule.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastFilterRule.java
index dcec68a..29e6559 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastFilterRule.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastFilterRule.java
@@ -17,78 +17,16 @@
  */
 package org.apache.drill.exec.planner.logical;
 
-import org.eigenbase.rel.FilterRel;
-import org.eigenbase.rel.ProjectRel;
-import org.eigenbase.rel.RelNode;
+import org.eigenbase.rel.rules.PushProjectPastFilterRule;
 import org.eigenbase.rel.rules.PushProjector;
 import org.eigenbase.relopt.RelOptRule;
-import org.eigenbase.relopt.RelOptRuleCall;
-import org.eigenbase.rex.RexCall;
-import org.eigenbase.rex.RexNode;
-import org.eigenbase.rex.RexOver;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-public class DrillPushProjectPastFilterRule extends RelOptRule {
+public class DrillPushProjectPastFilterRule extends PushProjectPastFilterRule {
 
-  private final static Logger logger = LoggerFactory.getLogger(DrillPushProjectPastFilterRule.class);
+  public final static RelOptRule INSTANCE = new DrillPushProjectPastFilterRule(DrillConditions.PRESERVE_ITEM);
 
-  public final static RelOptRule INSTANCE = new DrillPushProjectPastFilterRule(new PushProjector.ExprCondition() {
-    @Override
-    public boolean test(RexNode expr) {
-      if (expr instanceof RexCall) {
-        RexCall call = (RexCall)expr;
-        return "ITEM".equals(call.getOperator().getName());
-      }
-      return false;
-    }
-  });
-
-  /**
-   * Expressions that should be preserved in the projection
-   */
-  private final PushProjector.ExprCondition preserveExprCondition;
-
-  private DrillPushProjectPastFilterRule(PushProjector.ExprCondition preserveExprCondition) {
-    super(RelOptHelper.any(ProjectRel.class, FilterRel.class));
-    this.preserveExprCondition = preserveExprCondition;
-  }
-
-  @Override
-  public void onMatch(RelOptRuleCall call) {
-    ProjectRel origProj;
-    FilterRel filterRel;
-
-    if (call.rels.length == 2) {
-      origProj = call.rel(0);
-      filterRel = call.rel(1);
-    } else {
-      origProj = null;
-      filterRel = call.rel(0);
-    }
-    RelNode rel = filterRel.getChild();
-    RexNode origFilter = filterRel.getCondition();
-
-    if ((origProj != null) && RexOver.containsOver(origProj.getProjects(), null)) {
-      // Cannot push project through filter if project contains a windowed
-      // aggregate -- it will affect row counts. Abort this rule
-      // invocation; pushdown will be considered after the windowed
-      // aggregate has been implemented. It's OK if the filter contains a
-      // windowed aggregate.
-      return;
-    }
-
-    PushProjector pushProjector = createPushProjector(origProj, origFilter, rel, preserveExprCondition);
-    RelNode topProject = pushProjector.convertProject(null);
-
-    if (topProject != null) {
-      call.transformTo(topProject);
-    }
-  }
-
-  protected PushProjector createPushProjector(ProjectRel origProj, RexNode origFilter, RelNode rel,
-                                              PushProjector.ExprCondition preserveExprCondition) {
-    return new PushProjector(origProj, origFilter,rel, preserveExprCondition);
+  protected DrillPushProjectPastFilterRule(PushProjector.ExprCondition preserveExprCondition) {
+    super(preserveExprCondition);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastJoinRule.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastJoinRule.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastJoinRule.java
new file mode 100644
index 0000000..7296d08
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillPushProjectPastJoinRule.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.drill.exec.planner.logical;
+
+import org.eigenbase.rel.rules.PushProjectPastJoinRule;
+import org.eigenbase.rel.rules.PushProjector;
+import org.eigenbase.relopt.RelOptRule;
+
+public class DrillPushProjectPastJoinRule extends PushProjectPastJoinRule {
+
+  public static final RelOptRule INSTANCE = new DrillPushProjectPastJoinRule(DrillConditions.PRESERVE_ITEM);
+
+  protected DrillPushProjectPastJoinRule(PushProjector.ExprCondition preserveExprCondition) {
+    super(preserveExprCondition);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillRuleSets.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillRuleSets.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillRuleSets.java
index 7af541a..f4481cb 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillRuleSets.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillRuleSets.java
@@ -45,6 +45,7 @@ import org.eigenbase.rel.RelFactories;
 import org.eigenbase.rel.rules.PushFilterPastJoinRule;
 import org.eigenbase.rel.rules.PushFilterPastProjectRule;
 import org.eigenbase.rel.rules.PushJoinThroughJoinRule;
+import org.eigenbase.rel.rules.PushProjectPastFilterRule;
 import org.eigenbase.rel.rules.PushProjectPastJoinRule;
 import org.eigenbase.rel.rules.RemoveDistinctAggregateRule;
 import org.eigenbase.rel.rules.RemoveDistinctRule;
@@ -87,9 +88,15 @@ public class DrillRuleSets {
       DrillMergeProjectRule.getInstance(true, RelFactories.DEFAULT_PROJECT_FACTORY, context.getFunctionRegistry()),
       RemoveDistinctAggregateRule.INSTANCE, //
       // ReduceAggregatesRule.INSTANCE, // replaced by DrillReduceAggregatesRule
-      PushProjectPastJoinRule.INSTANCE,
+
+      /*
+      Projection push-down related rules
+      */
 //      PushProjectPastFilterRule.INSTANCE,
       DrillPushProjectPastFilterRule.INSTANCE,
+//      PushProjectPastJoinRule.INSTANCE,
+      DrillPushProjectPastJoinRule.INSTANCE,
+
 //      SwapJoinRule.INSTANCE, //
 //      PushJoinThroughJoinRule.RIGHT, //
 //      PushJoinThroughJoinRule.LEFT, //
@@ -109,8 +116,8 @@ public class DrillRuleSets {
       DrillLimitRule.INSTANCE,
       DrillSortRule.INSTANCE,
       DrillJoinRule.INSTANCE,
-      DrillUnionRule.INSTANCE
-      ,DrillReduceAggregatesRule.INSTANCE
+      DrillUnionRule.INSTANCE,
+      DrillReduceAggregatesRule.INSTANCE
       ));
     }
     return DRILL_BASIC_RULES;

http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/test/java/org/apache/drill/TestExampleQueries.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/java/org/apache/drill/TestExampleQueries.java b/exec/java-exec/src/test/java/org/apache/drill/TestExampleQueries.java
index a328ade..cd70b3c 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/TestExampleQueries.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/TestExampleQueries.java
@@ -19,6 +19,7 @@ package org.apache.drill;
 
 import org.apache.drill.common.util.FileUtils;
 import org.apache.drill.exec.rpc.RpcException;
+import org.junit.Ignore;
 import org.junit.Test;
 import static org.junit.Assert.assertEquals;
 
@@ -374,15 +375,6 @@ public class TestExampleQueries extends BaseTestQuery{
          " group by n.n_regionkey \n" +
          " order by n.n_regionkey; " );
 
-    // select clause, where, on, group by, order by.
-    test(" select n.n_regionkey, count(*) as cnt \n" +
-         " from   (select * from cp.`tpch/nation.parquet`) n  \n" +
-         "   join (select * from cp.`tpch/region.parquet`) r  \n" +
-         " on n.n_regionkey = r.r_regionkey \n" +
-         " where n.n_nationkey > 10 \n" +
-         " group by n.n_regionkey \n" +
-         " order by n.n_regionkey; " );
-
     // Outer query use select *. Join condition in where clause.
     test(" select *  \n" +
          " from (select * from cp.`tpch/nation.parquet`) n \n" +
@@ -396,6 +388,19 @@ public class TestExampleQueries extends BaseTestQuery{
          " on n.n_regionkey = r.r_regionkey " );
   }
 
+  @Test
+  @Ignore("This test has been ignored until DRILL-1680 is fixed")
+  public void testSelectStartSubQueryJoinWithWhereClause() throws Exception {
+    // select clause, where, on, group by, order by.
+    test(" select n.n_regionkey, count(*) as cnt \n" +
+        " from   (select * from cp.`tpch/nation.parquet`) n  \n" +
+        "   join (select * from cp.`tpch/region.parquet`) r  \n" +
+        " on n.n_regionkey = r.r_regionkey \n" +
+        " where n.n_nationkey > 10 \n" +
+        " group by n.n_regionkey \n" +
+        " order by n.n_regionkey; " );
+  }
+
   @Test // DRILL-595 : Select * in CTE WithClause : regular columns appear in select clause, where, group by, order by.
   public void testDRILL_595WithClause() throws Exception {
     test(" with x as (select * from cp.`region.json`) \n" +

http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/test/java/org/apache/drill/TestProjectPushDown.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/java/org/apache/drill/TestProjectPushDown.java b/exec/java-exec/src/test/java/org/apache/drill/TestProjectPushDown.java
index 828ffe9..6e998a3 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/TestProjectPushDown.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/TestProjectPushDown.java
@@ -106,9 +106,7 @@ public class TestProjectPushDown extends PlanTestBase {
   }
 
 
-  private static final String pushDownSql = "select %s from cp.`%s` t";
-  private static final String pushDownSqlWithFilter = pushDownSql + " where %s";
-  private final String[] inputTypes = new String[] {
+  private final String[] TABLES = new String[] {
       "project/pushdown/empty.json",
       "project/pushdown/empty.csv",
       "tpch/lineitem.parquet"
@@ -116,17 +114,124 @@ public class TestProjectPushDown extends PlanTestBase {
 
   @Test
   public void testProjectPushDown() throws Exception {
+    final String pushDownSqlPattern = "select %s from cp.`%s` t";
     final String projection = "t.trans_id, t.user_info.cust_id, t.marketing_info.keywords[0]";
     final String expected = "\"columns\" : [ \"`trans_id`\", \"`user_info`.`cust_id`\", \"`marketing_info`.`keywords`[0]\" ],";
+
+    for (String table: TABLES) {
+      testPushDown(new PushDownTestInstance(pushDownSqlPattern, expected, projection, table));
+    }
+  }
+
+  @Test
+  public void testProjectPastFilterPushDown() throws Exception {
+    final String pushDownSqlPattern = "select %s from cp.`%s` t where %s";
+    final String projection = "t.trans_id, t.user_info.cust_id, t.marketing_info.keywords[0]";
     final String filter = "t.another_field = 10 and t.columns[0] = 100 and t.columns[1] = t.other.columns[2]";
-    final String expectedWithFilter = "\"columns\" : [ \"`another_field`\", \"`trans_id`\", \"`user_info`.`cust_id`\", \"`marketing_info`.`keywords`[0]\", \"`columns`[0]\", \"`columns`[1]\", \"`other`.`columns`[2]\" ],";
+    final String expected = "\"columns\" : [ \"`another_field`\", \"`trans_id`\", \"`user_info`.`cust_id`\", \"`marketing_info`.`keywords`[0]\", \"`columns`[0]\", \"`columns`[1]\", \"`other`.`columns`[2]\" ],";
 
-    for (String inputType:inputTypes) {
-      testPushDown(new PushDownTestInstance(pushDownSql, expected, projection, inputType));
-      testPushDown(new PushDownTestInstance(pushDownSqlWithFilter, expectedWithFilter, projection, inputType, filter));
+    for (String table: TABLES) {
+      testPushDown(new PushDownTestInstance(pushDownSqlPattern, expected, projection, table, filter));
     }
   }
 
+  @Test
+  public void testProjectPastJoinPushDown() throws Exception {
+    final String pushDownSqlPattern = "select %s from cp.`%s` t0, cp.`%s` t1 where %s";
+    final String projection = "t0.fcolumns[0], t0.fmy.field, t0.freally.nested.field[0], t1.scolumns[0], t1.smy.field, t1.sreally.nested.field[0]";
+    final String filter = "t0.fname = t1.sname and t0.fcolumns[1]=10 and t1.scolumns[1]=100";
+    final String firstExpected = "\"columns\" : [ \"`fname`\", \"`fcolumns`[0]\", \"`fmy`.`field`\", \"`freally`.`nested`.`field`[0]\", \"`fcolumns`[1]\" ],";
+    final String secondExpected = "\"columns\" : [ \"`sname`\", \"`scolumns`[0]\", \"`smy`.`field`\", \"`sreally`.`nested`.`field`[0]\", \"`scolumns`[1]\" ],";
+
+    for (String table: TABLES) {
+      testPushDown(new PushDownTestInstance(pushDownSqlPattern, new String[]{firstExpected, secondExpected},
+          projection, table, table, filter));
+    }
+  }
+
+  @Test
+  public void testProjectPastFilterPastJoinPushDown() throws Exception {
+    final String pushDownSqlPattern = "select %s from cp.`%s` t0, cp.`%s` t1 where %s";
+    final String projection = "t0.fcolumns[0], t0.fmy.field, t0.freally.nested.field[0], t1.scolumns[0], t1.smy.field, t1.sreally.nested.field[0]";
+    final String filter = "t0.fname = t1.sname and t0.fcolumns[1] + t1.scolumns[1]=100";
+    final String firstExpected = "\"columns\" : [ \"`fname`\", \"`fcolumns`[0]\", \"`fmy`.`field`\", \"`freally`.`nested`.`field`[0]\", \"`fcolumns`[1]\" ],";
+    final String secondExpected = "\"columns\" : [ \"`sname`\", \"`scolumns`[0]\", \"`smy`.`field`\", \"`sreally`.`nested`.`field`[0]\", \"`scolumns`[1]\" ],";
+
+    for (String table: TABLES) {
+      testPushDown(new PushDownTestInstance(pushDownSqlPattern, new String[]{firstExpected, secondExpected},
+          projection, table, table, filter));
+    }
+  }
+
+  @Test
+  public void testProjectPastFilterPastJoinPushDownWhenItemsAreWithinNestedOperators() throws Exception {
+    final String pushDownSqlPattern = "select %s from cp.`%s` t0, cp.`%s` t1 where %s";
+    final String projection = "concat(t0.fcolumns[0], concat(t1.scolumns[0], t0.fmy.field, t0.freally.nested.field[0], t1.smy.field, t1.sreally.nested.field[0]))";
+    final String filter = "t0.fname = t1.sname and t0.fcolumns[1] + t1.scolumns[1]=100";
+    final String firstExpected = "\"columns\" : [ \"`fname`\", \"`fcolumns`[0]\", \"`fmy`.`field`\", \"`freally`.`nested`.`field`[0]\", \"`fcolumns`[1]\" ],";
+    final String secondExpected = "\"columns\" : [ \"`sname`\", \"`scolumns`[0]\", \"`smy`.`field`\", \"`sreally`.`nested`.`field`[0]\", \"`scolumns`[1]\" ],";
+
+    for (String table: TABLES) {
+      testPushDown(new PushDownTestInstance(pushDownSqlPattern, new String[]{firstExpected, secondExpected},
+          projection, table, table, filter));
+    }
+  }
+
+  @Test
+  public void testProjectPastFilterPastJoinPastJoinPushDown() throws Exception {
+    final String pushDownSqlPattern = "select %s from cp.`%s` t0, cp.`%s` t1, cp.`%s` t2 where %s";
+    final String projection = "t0.fcolumns[0], t0.fmy.field, t0.freally.nested.field[0], t1.scolumns[0], t1.smy.field, t1.sreally.nested.field[0], t2.tcolumns[0], t2.tmy.field, t2.treally.nested.field[0]";
+    final String filter = "t0.fname = t1.sname and t1.slastname = t2.tlastname and t0.fcolumns[1] + t1.scolumns[1] + t2.tcolumns[1]=100";
+    final String firstExpected = "\"columns\" : [ \"`fname`\", \"`fcolumns`[1]\", \"`fcolumns`[0]\", \"`fmy`.`field`\", \"`freally`.`nested`.`field`[0]\" ],";
+    final String secondExpected = "\"columns\" : [ \"`sname`\", \"`slastname`\", \"`scolumns`[1]\", \"`scolumns`[0]\", \"`smy`.`field`\", \"`sreally`.`nested`.`field`[0]\" ],";
+    final String thirdExpected = "\"columns\" : [ \"`tlastname`\", \"`tcolumns`[1]\", \"`tcolumns`[0]\", \"`tmy`.`field`\", \"`treally`.`nested`.`field`[0]\" ],";
+
+    for (String table: TABLES) {
+      testPushDown(new PushDownTestInstance(pushDownSqlPattern,
+          new String[]{firstExpected, secondExpected, thirdExpected}, projection, table, table, table, filter));
+    }
+  }
+
+  @Test
+  @Ignore("This query does not work when ProjectPastJoin is enabled. This is a known issue.")
+  public void testProjectPastJoinPastFilterPastJoinPushDown() throws Exception {
+    final String pushDownSqlPattern = "select %s from cp.`%s` t0, cp.`%s` t1, cp.`%s` t2 where %s";
+    final String projection = "t0.fcolumns[0], t0.fmy.field, t0.freally.nested.field[0], t1.scolumns[0], t1.smy.field, t1.sreally.nested.field[0], t2.tcolumns[0], t2.tmy.field, t2.treally.nested.field[0]";
+    final String filter = "t0.fname = t1.sname and t1.slastname = t2.tlastname and t0.fcolumns[1] + t1.scolumns[1] = 100";
+    final String firstExpected = "\"columns\" : [ \"`fname`\", \"`fcolumns`[1]\", \"`fcolumns`[0]\", \"`fmy`.`field`\", \"`freally`.`nested`.`field`[0]\" ],";
+    final String secondExpected = "\"columns\" : [ \"`sname`\", \"`slastname`\", \"`scolumns`[1]\", \"`scolumns`[0]\", \"`smy`.`field`\", \"`sreally`.`nested`.`field`[0]\" ],";
+    final String thirdExpected = "\"columns\" : [ \"`tlastname`\", \"`tcolumns`[1]\", \"`tcolumns`[0]\", \"`tmy`.`field`\", \"`treally`.`nested`.`field`[0]\" ],";
+
+    final String[] TABLES = new String[] {
+        "project/pushdown/empty0.json",
+        "project/pushdown/empty1.json",
+        "project/pushdown/empty2.json",
+    };
+//    for (String table: TABLES) {
+      testPushDown(new PushDownTestInstance(pushDownSqlPattern,
+          new String[]{firstExpected, secondExpected, thirdExpected}, projection, TABLES[0], TABLES[1], TABLES[2], filter));
+//    }
+  }
+
+  @Test
+  @Ignore("This query does not work when ProjectPastJoin is enabled. This is a known issue.")
+  public void testSimpleProjectPastJoinPastFilterPastJoinPushDown() throws Exception {
+    String sql = "select * " +
+        "from cp.`%s` t0, cp.`%s` t1, cp.`%s` t2 " +
+        "where t0.fname = t1.sname and t1.slastname = t2.tlastname and t0.fcolumns[0] + t1.scolumns = 100";
+
+    final String[] TABLES = new String[] {
+        "project/pushdown/empty0.json",
+        "project/pushdown/empty1.json",
+        "project/pushdown/empty2.json",
+    };
+
+    sql = "select t0.fa, t1.sa, t2.ta " +
+        " from cp.`%s` t0, cp.`%s` t1, cp.`%s` t2 " +
+        " where t0.a=t1.b and t1.c=t2.d and t0.fcolumns[0] + t1.a = 100";
+    testPushDown(new PushDownTestInstance(sql, "nothing", TABLES[0],TABLES[1],TABLES[2]));
+  }
+
   protected void testPushDown(PushDownTestInstance test) throws Exception {
     testPhysicalPlan(test.getSql(), test.getExpected());
   }
@@ -145,16 +250,20 @@ public class TestProjectPushDown extends PlanTestBase {
 
   protected static class PushDownTestInstance {
     private final String sqlPattern;
-    private final String expected;
+    private final String[] expected;
     private final Object[] params;
 
     public PushDownTestInstance(String sqlPattern, String expected, Object... params) {
+      this(sqlPattern, new String[]{expected}, params);
+    }
+
+    public PushDownTestInstance(String sqlPattern, String[] expected, Object... params) {
       this.sqlPattern = sqlPattern;
       this.expected = expected;
       this.params = params;
     }
 
-    public String getExpected() {
+    public String[] getExpected() {
       return expected;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/test/resources/project/pushdown/empty0.json
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/project/pushdown/empty0.json b/exec/java-exec/src/test/resources/project/pushdown/empty0.json
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/test/resources/project/pushdown/empty1.json
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/project/pushdown/empty1.json b/exec/java-exec/src/test/resources/project/pushdown/empty1.json
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/66cf6e27/exec/java-exec/src/test/resources/project/pushdown/empty2.json
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/test/resources/project/pushdown/empty2.json b/exec/java-exec/src/test/resources/project/pushdown/empty2.json
new file mode 100644
index 0000000..e69de29