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 2016/11/12 22:05:53 UTC

calcite git commit: [CALCITE-1489] Add rule, AggregateValuesRule, that applies to an Aggregate on an empty relation (Gian Merlino)

Repository: calcite
Updated Branches:
  refs/heads/master b7e99bca9 -> 3f92157d5


[CALCITE-1489] Add rule, AggregateValuesRule, that applies to an Aggregate on an empty relation (Gian Merlino)

Close apache/calcite#324


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

Branch: refs/heads/master
Commit: 3f92157d5742dd10f3b828d22d7a753e0a2899cc
Parents: b7e99bc
Author: Gian Merlino <gi...@gmail.com>
Authored: Fri Nov 11 15:56:34 2016 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Sat Nov 12 09:19:45 2016 -0800

----------------------------------------------------------------------
 .../calcite/prepare/CalcitePrepareImpl.java     |   4 +-
 .../calcite/rel/rules/AggregateValuesRule.java  | 100 +++++++++++++++++++
 .../calcite/rel/rules/PruneEmptyRules.java      |   2 +
 .../apache/calcite/test/RelOptRulesTest.java    |  15 +++
 .../org/apache/calcite/test/RelOptTestBase.java |  22 ++--
 .../org/apache/calcite/test/RelOptRulesTest.xml |  16 +++
 6 files changed, 150 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
index cd2ad50..196254c 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -76,6 +76,7 @@ import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.rules.AggregateExpandDistinctAggregatesRule;
 import org.apache.calcite.rel.rules.AggregateReduceFunctionsRule;
 import org.apache.calcite.rel.rules.AggregateStarTableRule;
+import org.apache.calcite.rel.rules.AggregateValuesRule;
 import org.apache.calcite.rel.rules.FilterAggregateTransposeRule;
 import org.apache.calcite.rel.rules.FilterJoinRule;
 import org.apache.calcite.rel.rules.FilterProjectTransposeRule;
@@ -243,7 +244,8 @@ public class CalcitePrepareImpl implements CalcitePrepare {
           ReduceExpressionsRule.JOIN_INSTANCE,
           ValuesReduceRule.FILTER_INSTANCE,
           ValuesReduceRule.PROJECT_FILTER_INSTANCE,
-          ValuesReduceRule.PROJECT_INSTANCE);
+          ValuesReduceRule.PROJECT_INSTANCE,
+          AggregateValuesRule.INSTANCE);
 
   public CalcitePrepareImpl() {
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
new file mode 100644
index 0000000..b921dd9
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
@@ -0,0 +1,100 @@
+/*
+ * 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.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.util.Util;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Rule that applies {@link Aggregate} to a {@link Values} (currently just an
+ * empty {@code Value}s).
+ *
+ * <p>This is still useful because {@link PruneEmptyRules#AGGREGATE_INSTANCE}
+ * doesn't handle {@code Aggregate}, which is in turn because {@code Aggregate}
+ * of empty relations need some special handling: a single row will be
+ * generated, where each column's value depends on the specific aggregate calls
+ * (e.g. COUNT is 0, SUM is NULL).
+ *
+ * <p>Sample query where this matters:
+ *
+ * <blockquote><code>SELECT COUNT(*) FROM s.foo WHERE 1 = 0</code></blockquote>
+ *
+ * <p>This rule only applies to "grand totals", that is, {@code GROUP BY ()}.
+ * Any non-empty {@code GROUP BY} clause will return one row per group key
+ * value, and each group will consist of at least one row.
+ */
+public class AggregateValuesRule extends RelOptRule {
+  public static final AggregateValuesRule INSTANCE = new AggregateValuesRule();
+
+  private AggregateValuesRule() {
+    super(
+        operand(Aggregate.class, null, Predicates.not(Aggregate.IS_NOT_GRAND_TOTAL),
+            operand(Values.class, null, Values.IS_EMPTY, none())));
+  }
+
+  @Override public void onMatch(RelOptRuleCall call) {
+    final Aggregate aggregate = call.rel(0);
+    final Values values = call.rel(1);
+    Util.discard(values);
+    final RelBuilder relBuilder = call.builder();
+    final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+
+    final List<RexLiteral> literals = new ArrayList<>();
+    for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
+      switch (aggregateCall.getAggregation().getKind()) {
+      case COUNT:
+      case SUM0:
+        literals.add((RexLiteral) rexBuilder.makeLiteral(
+            BigDecimal.ZERO, aggregateCall.getType(), false));
+        break;
+
+      case MIN:
+      case MAX:
+      case SUM:
+        literals.add(rexBuilder.constantNull());
+        break;
+
+      default:
+        // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
+        return;
+      }
+    }
+
+    call.transformTo(
+        relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
+            .build());
+
+    // New plan is absolutely better than old plan.
+    call.getPlanner().setImportance(aggregate, 0.0);
+  }
+}
+
+// End AggregateValuesRule.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java b/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
index 094c290..51aefe1 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
@@ -265,6 +265,8 @@ public abstract class PruneEmptyRules {
    * <li>{@code Aggregate(key: [], Empty)} is unchanged, because an aggregate
    * without a GROUP BY key always returns 1 row, even over empty input
    * </ul>
+   *
+   * @see AggregateValuesRule
    */
   public static final RelOptRule AGGREGATE_INSTANCE =
       new RemoveEmptySingleRule(Aggregate.class, Aggregate.IS_NOT_GRAND_TOTAL,

http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/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 8ca9130..82269b3 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -47,6 +47,7 @@ import org.apache.calcite.rel.rules.AggregateProjectPullUpConstantsRule;
 import org.apache.calcite.rel.rules.AggregateReduceFunctionsRule;
 import org.apache.calcite.rel.rules.AggregateUnionAggregateRule;
 import org.apache.calcite.rel.rules.AggregateUnionTransposeRule;
+import org.apache.calcite.rel.rules.AggregateValuesRule;
 import org.apache.calcite.rel.rules.CalcMergeRule;
 import org.apache.calcite.rel.rules.CoerceInputsRule;
 import org.apache.calcite.rel.rules.DateRangeRules;
@@ -1705,6 +1706,20 @@ public class RelOptRulesTest extends RelOptTestBase {
     checkPlanning(tester, preProgram, new HepPlanner(program), sql, unchanged);
   }
 
+  @Test public void testEmptyAggregateEmptyKeyWithAggregateValuesRule() {
+    HepProgram preProgram = HepProgram
+        .builder()
+        .addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
+        .addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
+        .build();
+    HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(AggregateValuesRule.INSTANCE)
+        .build();
+
+    final String sql = "select count(*), sum(empno) from emp where false";
+    sql(sql).withPre(preProgram).with(program).check();
+  }
+
   @Test public void testReduceCasts() throws Exception {
     HepProgram program = new HepProgramBuilder()
         .addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)

http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java b/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
index e01fd5b..4c57833 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
@@ -176,37 +176,43 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
 
   /** Sets the SQL statement for a test. */
   Sql sql(String sql) {
-    return new Sql(sql, null, true, ImmutableMap.<Hook, Function>of());
+    return new Sql(sql, null, null, true, ImmutableMap.<Hook, Function>of());
   }
 
   /** Allows fluent testing. */
   class Sql {
     private final String sql;
+    private HepProgram preProgram;
     private final HepPlanner hepPlanner;
     private final boolean expand;
     private final ImmutableMap<Hook, Function> hooks;
 
-    Sql(String sql, HepPlanner hepPlanner, boolean expand,
-        ImmutableMap<Hook, Function> hooks) {
+    Sql(String sql, HepProgram preProgram, HepPlanner hepPlanner,
+        boolean expand, ImmutableMap<Hook, Function> hooks) {
       this.sql = sql;
+      this.preProgram = preProgram;
       this.hepPlanner = hepPlanner;
       this.expand = expand;
       this.hooks = hooks;
     }
 
+    public Sql withPre(HepProgram preProgram) {
+      return new Sql(sql, preProgram, hepPlanner, expand, hooks);
+    }
+
     public Sql with(HepPlanner hepPlanner) {
-      return new Sql(sql, hepPlanner, expand, hooks);
+      return new Sql(sql, preProgram, hepPlanner, expand, hooks);
     }
 
     public Sql with(HepProgram program) {
-      return new Sql(sql, new HepPlanner(program), expand, hooks);
+      return new Sql(sql, preProgram, new HepPlanner(program), expand, hooks);
     }
 
     /** Adds a hook and a handler for that hook. Calcite will create a thread
      * hook (by calling {@link Hook#addThread(com.google.common.base.Function)})
      * just before running the query, and remove the hook afterwards. */
     public <T> Sql withHook(Hook hook, Function<T, Void> handler) {
-      return new Sql(sql, hepPlanner, expand,
+      return new Sql(sql, preProgram, hepPlanner, expand,
           ImmutableMap.<Hook, Function>builder().putAll(hooks)
               .put(hook, handler).build());
     }
@@ -216,7 +222,7 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
     }
 
     public Sql expand(boolean expand) {
-      return new Sql(sql, hepPlanner, expand, hooks);
+      return new Sql(sql, preProgram, hepPlanner, expand, hooks);
     }
 
     public void check() {
@@ -232,7 +238,7 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
         for (Map.Entry<Hook, Function> entry : hooks.entrySet()) {
           closer.add(entry.getKey().addThread(entry.getValue()));
         }
-        checkPlanning(tester.withExpand(expand), null, hepPlanner, sql,
+        checkPlanning(tester.withExpand(expand), preProgram, hepPlanner, sql,
             unchanged);
       }
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/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 92fa0f7..6c97d6a 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -208,6 +208,22 @@ LogicalAggregate(group=[{}], EXPR$0=[SUM($0)])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testEmptyAggregateEmptyKeyWithAggregateValuesRule">
+        <Resource name="sql">
+            <![CDATA[select count(*), sum(empno) from emp where false]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[COUNT()], EXPR$1=[SUM($0)])
+  LogicalValues(tuples=[[]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalValues(tuples=[[{ 0, null }]])
+]]>
+        </Resource>
+    </TestCase>
     <TestCase name="testEmptyIntersect">
         <Resource name="sql">
             <![CDATA[select * from (values (30, 3))intersect