You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by ti...@apache.org on 2018/06/17 18:10:43 UTC

[drill] branch master updated: DRILL-6212: Prevent recursive cast expressions

This is an automated email from the ASF dual-hosted git repository.

timothyfarkas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/drill.git


The following commit(s) were added to refs/heads/master by this push:
     new b447260  DRILL-6212: Prevent recursive cast expressions
b447260 is described below

commit b447260e49dc4a8c906f5b310c037fe6dd77166f
Author: chunhui-shi <cs...@maprtech.com>
AuthorDate: Wed Jun 6 12:25:31 2018 -0700

    DRILL-6212: Prevent recursive cast expressions
    
    closes #1319
---
 .../planner/logical/DrillMergeProjectRule.java     | 113 ++++++++++++++++++---
 .../org/apache/drill/TestProjectWithFunctions.java |  54 ++++++++++
 .../src/test/resources/view/emp_6212.view.drill    |  10 ++
 3 files changed, 165 insertions(+), 12 deletions(-)

diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillMergeProjectRule.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillMergeProjectRule.java
index 94964ef..6c151ed 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillMergeProjectRule.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/logical/DrillMergeProjectRule.java
@@ -17,22 +17,37 @@
  */
 package org.apache.drill.exec.planner.logical;
 
-
-import org.apache.calcite.plan.Convention;
-import org.apache.calcite.plan.ConventionTraitDef;
+import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Project;
-import org.apache.calcite.rel.rules.ProjectMergeRule;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.util.Permutation;
 import org.apache.drill.exec.expr.fn.FunctionImplementationRegistry;
 import org.apache.calcite.rel.core.RelFactories.ProjectFactory;
 import org.apache.calcite.rex.RexCall;
 import org.apache.calcite.rex.RexNode;
 import org.apache.drill.exec.planner.DrillRelBuilder;
 
-public class DrillMergeProjectRule extends ProjectMergeRule {
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Rule for merging two projects provided the projects aren't projecting identical sets of
+ * input references.
+ *
+ * NOTE: This rules does not extend the Calcite ProjectMergeRule
+ * because of CALCITE-2223. Once, fixed this rule be changed accordingly. Please see DRILL-6501.
+ */
+public class DrillMergeProjectRule extends RelOptRule {
 
   private FunctionImplementationRegistry functionRegistry;
   private static DrillMergeProjectRule INSTANCE = null;
+  private final boolean force;
 
   public static DrillMergeProjectRule getInstance(boolean force, ProjectFactory pFactory,
       FunctionImplementationRegistry functionRegistry) {
@@ -44,7 +59,12 @@ public class DrillMergeProjectRule extends ProjectMergeRule {
 
   private DrillMergeProjectRule(boolean force, ProjectFactory pFactory,
       FunctionImplementationRegistry functionRegistry) {
-    super(force, DrillRelBuilder.proto(pFactory));
+
+    super(operand(LogicalProject.class,
+        operand(LogicalProject.class, any())),
+        DrillRelBuilder.proto(pFactory),
+        "DrillMergeProjectRule" + (force ? ":force_mode" : ""));
+    this.force = force;
     this.functionRegistry = functionRegistry;
   }
 
@@ -53,12 +73,6 @@ public class DrillMergeProjectRule extends ProjectMergeRule {
     Project topProject = call.rel(0);
     Project bottomProject = call.rel(1);
 
-    // Make sure both projects be LogicalProject.
-    if (topProject.getTraitSet().getTrait(ConventionTraitDef.INSTANCE) != Convention.NONE ||
-        bottomProject.getTraitSet().getTrait(ConventionTraitDef.INSTANCE) != Convention.NONE) {
-      return false;
-    }
-
     // We have a complex output type do not fire the merge project rule
     if (checkComplexOutput(topProject) || checkComplexOutput(bottomProject)) {
       return false;
@@ -77,4 +91,79 @@ public class DrillMergeProjectRule extends ProjectMergeRule {
     }
     return false;
   }
+
+  @Override
+  public void onMatch(RelOptRuleCall call) {
+    final Project topProject = call.rel(0);
+    final Project bottomProject = call.rel(1);
+    final RelBuilder relBuilder = call.builder();
+
+    // If one or both projects are permutations, short-circuit the complex logic
+    // of building a RexProgram.
+    final Permutation topPermutation = topProject.getPermutation();
+    if (topPermutation != null) {
+      if (topPermutation.isIdentity()) {
+        // Let ProjectRemoveRule handle this.
+        return;
+      }
+      final Permutation bottomPermutation = bottomProject.getPermutation();
+      if (bottomPermutation != null) {
+        if (bottomPermutation.isIdentity()) {
+          // Let ProjectRemoveRule handle this.
+          return;
+        }
+        final Permutation product = topPermutation.product(bottomPermutation);
+        relBuilder.push(bottomProject.getInput());
+        relBuilder.project(relBuilder.fields(product),
+            topProject.getRowType().getFieldNames());
+        call.transformTo(relBuilder.build());
+        return;
+      }
+    }
+
+    // If we're not in force mode and the two projects reference identical
+    // inputs, then return and let ProjectRemoveRule replace the projects.
+    if (!force) {
+      if (RexUtil.isIdentity(topProject.getProjects(),
+          topProject.getInput().getRowType())) {
+        return;
+      }
+    }
+
+    final List<RexNode> pushedProjects =
+        RelOptUtil.pushPastProject(topProject.getProjects(), bottomProject);
+    final List<RexNode> newProjects = simplifyCast(pushedProjects);
+    final RelNode input = bottomProject.getInput();
+    if (RexUtil.isIdentity(newProjects, input.getRowType())) {
+      if (force
+          || input.getRowType().getFieldNames()
+          .equals(topProject.getRowType().getFieldNames())) {
+        call.transformTo(input);
+        return;
+      }
+    }
+
+    // replace the two projects with a combined projection
+    relBuilder.push(bottomProject.getInput());
+    relBuilder.project(newProjects, topProject.getRowType().getFieldNames());
+    call.transformTo(relBuilder.build());
+  }
+
+  public static List<RexNode> simplifyCast(List<RexNode> projectExprs) {
+    final List<RexNode> list = new ArrayList<>();
+    for (RexNode rex: projectExprs) {
+      if (rex.getKind() == SqlKind.CAST) {
+        RexNode operand = ((RexCall) rex).getOperands().get(0);
+        while (operand.getKind() == SqlKind.CAST
+            && operand.getType().equals(rex.getType())) {
+          rex = operand;
+          operand = ((RexCall) rex).getOperands().get(0);
+        }
+
+      }
+      list.add(rex);
+    }
+    return list;
+  }
+
 }
diff --git a/exec/java-exec/src/test/java/org/apache/drill/TestProjectWithFunctions.java b/exec/java-exec/src/test/java/org/apache/drill/TestProjectWithFunctions.java
new file mode 100644
index 0000000..58cbad8
--- /dev/null
+++ b/exec/java-exec/src/test/java/org/apache/drill/TestProjectWithFunctions.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import org.apache.drill.categories.PlannerTest;
+import org.apache.drill.test.ClusterFixture;
+import org.apache.drill.test.ClusterFixtureBuilder;
+import org.apache.drill.test.ClusterTest;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.nio.file.Paths;
+
+/**
+ * Test the optimizer plan in terms of projecting different functions e.g. cast
+ */
+@Category(PlannerTest.class)
+public class TestProjectWithFunctions extends ClusterTest {
+
+  @BeforeClass
+  public static void setupFiles() {
+    dirTestWatcher.copyResourceToRoot(Paths.get("view"));
+  }
+
+  @Before
+  public void setup() throws Exception {
+    ClusterFixtureBuilder builder = ClusterFixture.builder(dirTestWatcher);
+    startCluster(builder);
+  }
+
+  @Test
+  public void testCastFunctions() throws Exception {
+    String sql = "select t1.f from dfs.`view/emp_6212.view.drill` as t inner join dfs.`view/emp_6212.view.drill` as t1 " +
+        "on cast(t.f as int) = cast(t1.f as int) and cast(t.f as int) = 10 and cast(t1.f as int) = 10";
+    client.queryBuilder().sql(sql).run();
+  }
+}
diff --git a/exec/java-exec/src/test/resources/view/emp_6212.view.drill b/exec/java-exec/src/test/resources/view/emp_6212.view.drill
new file mode 100644
index 0000000..72dc5b0
--- /dev/null
+++ b/exec/java-exec/src/test/resources/view/emp_6212.view.drill
@@ -0,0 +1,10 @@
+{
+  "name" : "emp_6212",
+  "sql" : "SELECT CAST(`department_id` AS INTEGER) AS `f`\nFROM `cp`.`employee.json`",
+  "fields" : [ {
+    "name" : "f",
+    "type" : "INTEGER",
+    "isNullable" : true
+  } ],
+  "workspaceSchemaPath" : [ "dfs", "tmp" ]
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
timothyfarkas@apache.org.