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 2015/09/04 09:35:06 UTC

[08/10] incubator-calcite git commit: [CALCITE-844] Push Project through Window (Hsuan-Yi Chu)

[CALCITE-844] Push Project through Window (Hsuan-Yi Chu)

Close apache/incubator-calcite#123


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

Branch: refs/heads/master
Commit: 5219eb8cd4e8c1f0b1654da3136ce1c04e38a96e
Parents: 0a4fa37
Author: Hsuan-Yi Chu <hs...@usc.edu>
Authored: Fri Aug 21 23:20:15 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Sep 4 00:34:00 2015 -0700

----------------------------------------------------------------------
 .../rel/rules/ProjectWindowTransposeRule.java   | 245 +++++++++++++++++++
 .../apache/calcite/test/RelOptRulesTest.java    |  11 +
 .../org/apache/calcite/test/RelOptRulesTest.xml |  20 ++
 3 files changed, 276 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5219eb8c/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
new file mode 100644
index 0000000..2705420
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
@@ -0,0 +1,245 @@
+/*
+ * 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.calcite.rel.rules;
+
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.core.Window;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.logical.LogicalWindow;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.RelRecordType;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * Planner rule that pushes
+ * a {@link org.apache.calcite.rel.logical.LogicalProject}
+ * past a {@link org.apache.calcite.rel.logical.LogicalWindow}.
+ */
+public class ProjectWindowTransposeRule extends RelOptRule {
+  /** The default instance of
+   * {@link org.apache.calcite.rel.rules.ProjectWindowTransposeRule}. */
+  public static final ProjectWindowTransposeRule INSTANCE = new ProjectWindowTransposeRule();
+
+  private ProjectWindowTransposeRule() {
+    super(
+        operand(
+            LogicalProject.class,
+                operand(LogicalWindow.class,
+                    any())));
+  }
+
+  @Override
+  public void onMatch(RelOptRuleCall call) {
+    final LogicalProject project = call.rel(0);
+    final LogicalWindow window = call.rel(1);
+    final List<RelDataTypeField> rowTypeWindowInput = window.getInput().getRowType().getFieldList();
+    final int windowInputColumn = rowTypeWindowInput.size();
+
+    // Record the window input columns which are actually referred
+    // either in the LogicalProject above LogicalWindow or LogicalWindow itself
+    final BitSet beReferred = findReference(project, window);
+
+    // If all the the window input columns are referred,
+    // it is impossible to trim anyone of them out
+    if (beReferred.cardinality() == windowInputColumn) {
+      return;
+    }
+
+    // Put a DrillProjectRel below LogicalWindow
+    final List<RexNode> exps = new ArrayList<>();
+    final List<RelDataTypeField> fields = new ArrayList<>();
+
+    // Keep only the fields which are referred
+    for  (int index = beReferred.nextSetBit(0); index >= 0;
+        index = beReferred.nextSetBit(index + 1)) {
+      final RelDataTypeField relDataTypeField = rowTypeWindowInput.get(index);
+      exps.add(new RexInputRef(index, relDataTypeField.getType()));
+      fields.add(relDataTypeField);
+    }
+
+    final LogicalProject projectBelowWindow = new LogicalProject(
+        window.getCluster(),
+        window.getTraitSet(),
+        window.getInput(),
+        exps,
+        new RelRecordType(fields));
+
+    // Create a new LogicalWindow with necessary inputs only
+    final List<RelDataTypeField> inputWinFields = fields;
+    final List<RelDataTypeField> outputWindFields = new ArrayList<>(inputWinFields);
+    final List<Window.Group> groups = new ArrayList<>();
+
+    // As the un-referred columns are trimmed by the LogicalProject,
+    // the indices specified in LogicalWindow would need to be adjusted
+    final RexShuttle indexAdjustment = new RexShuttle() {
+      @Override
+      public RexNode visitInputRef(RexInputRef inputRef) {
+        final int index = inputRef.getIndex();
+        final int newIndex = beReferred.get(0, index).cardinality();
+        return new RexInputRef(newIndex, inputRef.getType());
+      }
+
+      @Override
+      public RexNode visitCall(final RexCall call) {
+        final List<RexNode> clonedOperands = visitList(call.operands, new boolean[]{false});
+        if (call instanceof Window.RexWinAggCall) {
+          return new Window.RexWinAggCall(
+              (SqlAggFunction) call.getOperator(),
+              call.getType(),
+              clonedOperands,
+              ((Window.RexWinAggCall) call).ordinal);
+        } else {
+          return call;
+        }
+      }
+    };
+
+    int aggCallIndex = windowInputColumn;
+    for (Window.Group group : window.groups) {
+      final ImmutableBitSet.Builder keys = ImmutableBitSet.builder();
+      final List<RelFieldCollation> orderKeys = new ArrayList<>();
+      final List<Window.RexWinAggCall> aggCalls = new ArrayList<>();
+
+      // Adjust keys
+      for (int index : group.keys) {
+        keys.set(beReferred.get(0, index).cardinality());
+      }
+
+      // Adjust orderKeys
+      for (RelFieldCollation relFieldCollation : group.orderKeys.getFieldCollations()) {
+        final int index = relFieldCollation.getFieldIndex();
+        orderKeys.add(relFieldCollation.copy(beReferred.get(0, index).cardinality()));
+      }
+
+      // Adjust Window Functions
+      for (Window.RexWinAggCall rexWinAggCall : group.aggCalls) {
+        aggCalls.add((Window.RexWinAggCall) rexWinAggCall.accept(indexAdjustment));
+
+        final RelDataTypeField relDataTypeField =
+            window.getRowType().getFieldList().get(aggCallIndex);
+        outputWindFields.add(relDataTypeField);
+        ++aggCallIndex;
+      }
+
+      groups.add(new Window.Group(
+          keys.build(),
+          group.isRows,
+          group.lowerBound,
+          group.upperBound,
+          RelCollations.of(orderKeys),
+          aggCalls));
+    }
+
+    final LogicalWindow newLogicalWindow = new LogicalWindow(
+        window.getCluster(),
+        window.getTraitSet(),
+        projectBelowWindow,
+        window.constants,
+        new RelRecordType(outputWindFields),
+        groups);
+
+    // Modify the top LogicalProject
+    final List<RexNode> topProjExps = new ArrayList<>();
+    final RexShuttle topProjAdjustment = new RexShuttle() {
+      @Override
+      public RexNode visitInputRef(RexInputRef inputRef) {
+        final int index = inputRef.getIndex();
+        final int newIndex;
+        if (index >= windowInputColumn) {
+          newIndex = beReferred.cardinality() + (index - windowInputColumn);
+        } else {
+          newIndex = beReferred.get(0, index).cardinality();
+        }
+        return new RexInputRef(newIndex, inputRef.getType());
+      }
+    };
+
+    for (RexNode rexNode : project.getChildExps()) {
+      topProjExps.add(rexNode.accept(topProjAdjustment));
+    }
+
+    final LogicalProject newTopProj = project.copy(
+        newLogicalWindow.getTraitSet(),
+        newLogicalWindow,
+        topProjExps,
+        project.getRowType());
+
+    if (ProjectRemoveRule.isTrivial(newTopProj)) {
+      call.transformTo(newLogicalWindow);
+    } else {
+      call.transformTo(newTopProj);
+    }
+  }
+
+  private BitSet findReference(final LogicalProject project, final LogicalWindow window) {
+    final int windowInputColumn = window.getInput().getRowType().getFieldCount();
+    final BitSet beReferred = new BitSet(window.getInput().getRowType().getFieldCount());
+
+    final RexShuttle referenceFinder = new RexShuttle() {
+      @Override
+      public RexNode visitInputRef(RexInputRef inputRef) {
+        final int index = inputRef.getIndex();
+        if (index < windowInputColumn) {
+          beReferred.set(index);
+        }
+        return inputRef;
+      }
+    };
+
+    // Reference in LogicalProject
+    for (RexNode rexNode : project.getChildExps()) {
+      rexNode.accept(referenceFinder);
+    }
+
+    // Reference in LogicalWindow
+    for (Window.Group group : window.groups) {
+      // Reference in Partition-By
+      for (int index : group.keys) {
+        if (index < windowInputColumn) {
+          beReferred.set(index);
+        }
+      }
+
+      // Reference in Order-By
+      for (RelFieldCollation relFieldCollation : group.orderKeys.getFieldCollations()) {
+        if (relFieldCollation.getFieldIndex() < windowInputColumn) {
+          beReferred.set(relFieldCollation.getFieldIndex());
+        }
+      }
+
+      // Reference in Window Functions
+      for (Window.RexWinAggCall rexWinAggCall : group.aggCalls) {
+        rexWinAggCall.accept(referenceFinder);
+      }
+    }
+    return beReferred;
+  }
+}
+
+// End ProjectWindowTransposeRule.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5219eb8c/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 bdea4af..e34b1bb 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -64,6 +64,7 @@ import org.apache.calcite.rel.rules.ProjectRemoveRule;
 import org.apache.calcite.rel.rules.ProjectSetOpTransposeRule;
 import org.apache.calcite.rel.rules.ProjectToCalcRule;
 import org.apache.calcite.rel.rules.ProjectToWindowRule;
+import org.apache.calcite.rel.rules.ProjectWindowTransposeRule;
 import org.apache.calcite.rel.rules.PruneEmptyRules;
 import org.apache.calcite.rel.rules.ReduceExpressionsRule;
 import org.apache.calcite.rel.rules.SemiJoinFilterTransposeRule;
@@ -1609,6 +1610,16 @@ public class RelOptRulesTest extends RelOptTestBase {
         FilterProjectTransposeRule.INSTANCE);
   }
 
+  @Test public void testProjectWindowTransposeRule() {
+    HepProgram program = new HepProgramBuilder()
+      .addRuleInstance(ProjectToWindowRule.PROJECT)
+      .addRuleInstance(ProjectWindowTransposeRule.INSTANCE)
+      .build();
+
+    final String sql = "select count(empno) over(), deptno from emp";
+    checkPlanning(program, sql);
+  }
+
   @Test public void testPushFilterWithRank() throws Exception {
     HepProgram program = new HepProgramBuilder().addRuleInstance(
         FilterProjectTransposeRule.INSTANCE).build();

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5219eb8c/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 4ab7a81..7b40b5d 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -3041,6 +3041,26 @@ LogicalAggregate(group=[{0, 1}])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testProjectWindowTransposeRule">
+        <Resource name="sql">
+            <![CDATA[select count(empno) over(), deptno from emp]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(EXPR$0=[$1], DEPTNO=[$0])
+  LogicalProject(DEPTNO=[$1], $1=[$2])
+    LogicalWindow(window#0=[window(partition {} order by [] range between UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING aggs [COUNT($0)])])
+      LogicalProject(EMPNO=[$0], DEPTNO=[$7])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(EXPR$0=[COUNT($0) OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)], DEPTNO=[$7])
+  LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
     <TestCase name="testPushFilterWithRank">
         <Resource name="sql">
             <![CDATA[select e1.ename, r