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/12/13 23:01:32 UTC

[6/7] calcite git commit: [CALCITE-1023] Planner rule that removes Aggregate keys that are constant

[CALCITE-1023] Planner rule that removes Aggregate keys that are constant

Recognize that if there is a "CAST(c) = literal" predicate, and the cast is widening, then c must be constant.


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

Branch: refs/heads/master
Commit: 5197a714705c7bfaa6a46a0907267b47870b18d1
Parents: d55ff83
Author: Julian Hyde <jh...@apache.org>
Authored: Fri Dec 11 16:32:47 2015 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Sat Dec 12 13:41:15 2015 -0800

----------------------------------------------------------------------
 .../calcite/plan/RelOptPredicateList.java       |  20 ++-
 .../org/apache/calcite/plan/RelOptUtil.java     |   2 +
 .../calcite/rel/metadata/RelMdPredicates.java   |  25 +++-
 .../rel/rules/AggregateConstantKeyRule.java     | 130 +++++++++++++++++++
 .../rel/rules/ReduceExpressionsRule.java        |  87 +++++++++----
 .../main/java/org/apache/calcite/util/Bug.java  |   5 +
 .../java/org/apache/calcite/test/JdbcTest.java  |  16 ++-
 .../apache/calcite/test/RelOptRulesTest.java    |  43 +++++-
 .../org/apache/calcite/test/RelOptRulesTest.xml |  89 ++++++++++++-
 core/src/test/resources/sql/agg.iq              |  27 ++++
 10 files changed, 408 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/core/src/main/java/org/apache/calcite/plan/RelOptPredicateList.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptPredicateList.java b/core/src/main/java/org/apache/calcite/plan/RelOptPredicateList.java
index aad89b4..658ae7f 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptPredicateList.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptPredicateList.java
@@ -20,7 +20,6 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 
 /**
  * Predicates that are known to hold in the output of a particular relational
@@ -74,10 +73,21 @@ public class RelOptPredicateList {
 
   public RelOptPredicateList union(RelOptPredicateList list) {
     return RelOptPredicateList.of(
-        Iterables.concat(pulledUpPredicates, list.pulledUpPredicates),
-        Iterables.concat(leftInferredPredicates, list.leftInferredPredicates),
-        Iterables.concat(rightInferredPredicates,
-            list.rightInferredPredicates));
+        concat(pulledUpPredicates, list.pulledUpPredicates),
+        concat(leftInferredPredicates, list.leftInferredPredicates),
+        concat(rightInferredPredicates, list.rightInferredPredicates));
+  }
+
+  /** Concatenates two immutable lists, avoiding a copy it possible. */
+  private static <E> ImmutableList<E> concat(ImmutableList<E> list1,
+      ImmutableList<E> list2) {
+    if (list1.isEmpty()) {
+      return list2;
+    } else if (list2.isEmpty()) {
+      return list1;
+    } else {
+      return ImmutableList.<E>builder().addAll(list1).addAll(list2).build();
+    }
   }
 
   public RelOptPredicateList shift(int offset) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index acbd512..a7b3e08 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -39,6 +39,7 @@ import org.apache.calcite.rel.logical.LogicalCalc;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.rules.AggregateConstantKeyRule;
 import org.apache.calcite.rel.rules.AggregateProjectPullUpConstantsRule;
 import org.apache.calcite.rel.rules.FilterMergeRule;
 import org.apache.calcite.rel.rules.MultiJoin;
@@ -1564,6 +1565,7 @@ public abstract class RelOptUtil {
 
   public static void registerAbstractRels(RelOptPlanner planner) {
     planner.addRule(AggregateProjectPullUpConstantsRule.INSTANCE);
+    planner.addRule(AggregateConstantKeyRule.INSTANCE);
     planner.addRule(PruneEmptyRules.UNION_INSTANCE);
     planner.addRule(PruneEmptyRules.PROJECT_INSTANCE);
     planner.addRule(PruneEmptyRules.FILTER_INSTANCE);

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
index 7b20b68..35eb56b 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
@@ -21,6 +21,7 @@ import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.linq4j.function.Predicate1;
 import org.apache.calcite.plan.RelOptPredicateList;
 import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.volcano.RelSubset;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Exchange;
@@ -44,8 +45,10 @@ import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.util.BitSets;
+import org.apache.calcite.util.Bug;
 import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.Mapping;
 import org.apache.calcite.util.mapping.MappingType;
 import org.apache.calcite.util.mapping.Mappings;
@@ -207,9 +210,10 @@ public class RelMdPredicates {
     RelOptPredicateList childInfo =
         RelMetadataQuery.getPulledUpPredicates(child);
 
-    return RelOptPredicateList.of(
-        Iterables.concat(childInfo.pulledUpPredicates,
-            RelOptUtil.conjunctions(filter.getCondition())));
+    return Util.first(childInfo, RelOptPredicateList.EMPTY)
+        .union(
+            RelOptPredicateList.of(
+                RelOptUtil.conjunctions(filter.getCondition())));
   }
 
   /** Infers predicates for a {@link org.apache.calcite.rel.core.SemiJoin}. */
@@ -330,6 +334,21 @@ public class RelMdPredicates {
     return RelMetadataQuery.getPulledUpPredicates(child);
   }
 
+  /** @see RelMetadataQuery#getPulledUpPredicates(RelNode) */
+  public RelOptPredicateList getPredicates(RelSubset r) {
+    if (!Bug.CALCITE_794_FIXED) {
+      return RelOptPredicateList.EMPTY;
+    }
+    RelOptPredicateList list = null;
+    for (RelNode r2 : r.getRels()) {
+      RelOptPredicateList list2 = RelMetadataQuery.getPulledUpPredicates(r2);
+      if (list2 != null) {
+        list = list == null ? list2 : list.union(list2);
+      }
+    }
+    return Util.first(list, RelOptPredicateList.EMPTY);
+  }
+
   /**
    * Utility to infer predicates from one side of the join that apply on the
    * other side.

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/core/src/main/java/org/apache/calcite/rel/rules/AggregateConstantKeyRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateConstantKeyRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateConstantKeyRule.java
new file mode 100644
index 0000000..ea31178
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateConstantKeyRule.java
@@ -0,0 +1,130 @@
+/*
+ * 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.RelOptPredicateList;
+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.RelFactories;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.tools.RelBuilderFactory;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+/**
+ * Planner rule that removes constant keys from an
+ * a {@link Aggregate}.
+ *
+ * <p>It never removes the last column, because {@code Aggregate([])} returns
+ * 1 row even if its input is empty.
+ */
+public class AggregateConstantKeyRule extends RelOptRule {
+  public static final AggregateConstantKeyRule INSTANCE =
+      new AggregateConstantKeyRule(RelFactories.LOGICAL_BUILDER,
+          "AggregateConstantKeyRule");
+
+  //~ Constructors -----------------------------------------------------------
+
+  /** Creates an AggregateConstantKeyRule. */
+  private AggregateConstantKeyRule(RelBuilderFactory relBuilderFactory,
+      String description) {
+    super(operand(Aggregate.class, null, Aggregate.IS_SIMPLE, any()),
+        relBuilderFactory, description);
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  public void onMatch(RelOptRuleCall call) {
+    final Aggregate aggregate = call.rel(0);
+    assert !aggregate.indicator : "predicate ensured no grouping sets";
+
+    final RexBuilder rexBuilder = aggregate.getCluster().getRexBuilder();
+    final RelOptPredicateList predicates =
+        RelMetadataQuery.getPulledUpPredicates(aggregate.getInput());
+    if (predicates == null) {
+      return;
+    }
+    final ImmutableMap<RexNode, RexLiteral> constants =
+        ReduceExpressionsRule.predicateConstants(rexBuilder, predicates);
+    final NavigableMap<Integer, RexLiteral> map = new TreeMap<>();
+    for (int key : aggregate.getGroupSet()) {
+      final RexInputRef ref =
+          rexBuilder.makeInputRef(aggregate.getInput(), key);
+      if (constants.containsKey(ref)) {
+        map.put(key, constants.get(ref));
+      }
+    }
+
+    if (map.isEmpty()) {
+      return; // none of the keys are constant
+    }
+
+    if (map.size() == aggregate.getGroupCount()) {
+      if (map.size() == 1) {
+        // There is one key, and it is constant. We cannot remove it.
+        return;
+      }
+      map.remove(map.descendingKeySet().descendingIterator().next());
+    }
+
+    ImmutableBitSet newGroupSet = aggregate.getGroupSet();
+    for (int key : map.keySet()) {
+      newGroupSet = newGroupSet.clear(key);
+    }
+    final Aggregate newAggregate =
+        aggregate.copy(aggregate.getTraitSet(), aggregate.getInput(),
+            false, newGroupSet, ImmutableList.of(newGroupSet),
+            aggregate.getAggCallList());
+    final RelBuilder relBuilder = call.builder();
+    relBuilder.push(newAggregate);
+
+    final List<RexNode> projects = new ArrayList<>();
+    int offset = 0;
+    for (RelDataTypeField field : aggregate.getRowType().getFieldList()) {
+      RexNode node = null;
+      if (field.getIndex() < aggregate.getGroupCount()) {
+        node = map.get(aggregate.getGroupSet().nth(field.getIndex()));
+        if (node != null) {
+          node = relBuilder.getRexBuilder().makeCast(field.getType(), node, true);
+          node = relBuilder.alias(node, field.getName());
+          ++offset;
+        }
+      }
+      if (node == null) {
+        node = relBuilder.field(field.getIndex() - offset);
+      }
+      projects.add(node);
+    }
+    call.transformTo(relBuilder.project(projects).build());
+  }
+}
+
+// End AggregateConstantKeyRule.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
index 75943b8..8be202e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
@@ -396,7 +396,7 @@ public abstract class ReduceExpressionsRule extends RelOptRule {
     List<Boolean> addCasts = Lists.newArrayList();
     final List<RexNode> removableCasts = Lists.newArrayList();
     final ImmutableMap<RexNode, RexLiteral> constants =
-        predicateConstants(predicates);
+        predicateConstants(rexBuilder, predicates);
     findReducibleExps(rel.getCluster().getTypeFactory(), expList, constants,
         constExps, addCasts, removableCasts);
     if (constExps.isEmpty() && removableCasts.isEmpty()) {
@@ -508,7 +508,7 @@ public abstract class ReduceExpressionsRule extends RelOptRule {
   }
 
   protected static ImmutableMap<RexNode, RexLiteral> predicateConstants(
-      RelOptPredicateList predicates) {
+      RexBuilder rexBuilder, RelOptPredicateList predicates) {
     // We cannot use an ImmutableMap.Builder here. If there are multiple entries
     // with the same key (e.g. "WHERE deptno = 1 AND deptno = 2"), it doesn't
     // matter which we take, so the latter will replace the former.
@@ -521,7 +521,7 @@ public abstract class ReduceExpressionsRule extends RelOptRule {
     final Map<RexNode, RexLiteral> map = new HashMap<>();
     final Set<RexNode> excludeSet = new HashSet<>();
     for (RexNode predicate : predicates.pulledUpPredicates) {
-      gatherConstraints(map, predicate, excludeSet);
+      gatherConstraints(predicate, map, excludeSet, rexBuilder);
     }
     final ImmutableMap.Builder<RexNode, RexLiteral> builder =
         ImmutableMap.builder();
@@ -559,8 +559,9 @@ public abstract class ReduceExpressionsRule extends RelOptRule {
     }
   }
 
-  private static void gatherConstraints(Map<RexNode, RexLiteral> map,
-      RexNode predicate, Set<RexNode> excludeSet) {
+  private static void gatherConstraints(RexNode predicate,
+      Map<RexNode, RexLiteral> map, Set<RexNode> excludeSet,
+      RexBuilder rexBuilder) {
     if (predicate.getKind() != SqlKind.EQUALS) {
       decompose(excludeSet, predicate);
       return;
@@ -575,28 +576,68 @@ public abstract class ReduceExpressionsRule extends RelOptRule {
     final RexNode right = operands.get(1);
     // note that literals are immutable too and they can only be compared through
     // values.
-    if (right instanceof RexLiteral && !excludeSet.contains(left)) {
-      RexLiteral existedValue = map.get(left);
-      if (existedValue == null) {
-        map.put(left, (RexLiteral) right);
-      } else {
-        if (!existedValue.getValue().equals(((RexLiteral) right).getValue())) {
-          // we found conflict values.
-          map.remove(left);
-          excludeSet.add(left);
+    if (right instanceof RexLiteral) {
+      foo(left, (RexLiteral) right, map, excludeSet, rexBuilder);
+    }
+    if (left instanceof RexLiteral) {
+      foo(right, (RexLiteral) left, map, excludeSet, rexBuilder);
+    }
+  }
+
+  private static void foo(RexNode left, RexLiteral right,
+      Map<RexNode, RexLiteral> map, Set<RexNode> excludeSet,
+      RexBuilder rexBuilder) {
+    if (excludeSet.contains(left)) {
+      return;
+    }
+    final RexLiteral existedValue = map.get(left);
+    if (existedValue == null) {
+      switch (left.getKind()) {
+      case CAST:
+        // Convert "CAST(c) = literal" to "c = literal", as long as it is a
+        // widening cast.
+        final RexNode operand = ((RexCall) left).getOperands().get(0);
+        if (canAssignFrom(left.getType(), operand.getType())) {
+          final RexNode castRight =
+              rexBuilder.makeCast(operand.getType(), right);
+          if (castRight instanceof RexLiteral) {
+            left = operand;
+            right = (RexLiteral) castRight;
+          }
         }
       }
-    } else if (left instanceof RexLiteral && !excludeSet.contains(right)) {
-      RexLiteral existedValue = map.get(right);
-      if (existedValue == null) {
-        map.put(right, (RexLiteral) left);
-      } else {
-        if (!existedValue.getValue().equals(((RexLiteral) left).getValue())) {
-          map.remove(right);
-          excludeSet.add(right);
-        }
+      map.put(left, right);
+    } else {
+      if (!existedValue.getValue().equals(right.getValue())) {
+        // we found conflicting values, e.g. left = 10 and left = 20
+        map.remove(left);
+        excludeSet.add(left);
+      }
+    }
+  }
+
+  /** Returns whether a value of {@code type2} can be assigned to a variable
+   * of {@code type1}.
+   *
+   * <p>For example:
+   * <ul>
+   *   <li>{@code canAssignFrom(BIGINT, TINYINT)} returns {@code true}</li>
+   *   <li>{@code canAssignFrom(TINYINT, BIGINT)} returns {@code false}</li>
+   *   <li>{@code canAssignFrom(BIGINT, VARCHAR)} returns {@code false}</li>
+   * </ul>
+   */
+  private static boolean canAssignFrom(RelDataType type1, RelDataType type2) {
+    final SqlTypeName name1 = type1.getSqlTypeName();
+    final SqlTypeName name2 = type2.getSqlTypeName();
+    if (name1.getFamily() == name2.getFamily()) {
+      switch (name1.getFamily()) {
+      case NUMERIC:
+        return name1.compareTo(name2) >= 0;
+      default:
+        return true;
       }
     }
+    return false;
   }
 
   /** Pushes predicates into a CASE.

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/core/src/main/java/org/apache/calcite/util/Bug.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Bug.java b/core/src/main/java/org/apache/calcite/util/Bug.java
index a1309ab..6248684 100644
--- a/core/src/main/java/org/apache/calcite/util/Bug.java
+++ b/core/src/main/java/org/apache/calcite/util/Bug.java
@@ -170,6 +170,11 @@ public abstract class Bug {
    * Timeout executing joins against MySQL</a> is fixed. */
   public static final boolean CALCITE_673_FIXED = false;
 
+  /** Whether
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-794">[CALCITE-794]
+   * Detect cycles when computing statistics</a> is fixed. */
+  public static final boolean CALCITE_794_FIXED = false;
+
   /**
    * Use this to flag temporary code.
    */

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 9d2597f..0eccd4a 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -4688,10 +4688,22 @@ public class JdbcTest {
     final Function<String, Object> env =
         new Function<String, Object>() {
           public Object apply(String varName) {
-            if (varName.equals("jdk18")) {
+            switch (varName) {
+            case "jdk18":
               return System.getProperty("java.version").startsWith("1.8");
+            case "fixed":
+              return new Function<String, Object>() {
+                public Object apply(String v) {
+                  switch (v) {
+                  case "calcite794":
+                    return Bug.CALCITE_794_FIXED;
+                  }
+                  return null;
+                }
+              };
+            default:
+              return null;
             }
-            return null;
           }
         };
     final Quidem.NewConnectionFactory connectionFactory =

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/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 1be8331..4061b0d 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -36,6 +36,7 @@ import org.apache.calcite.rel.metadata.CachingRelMetadataProvider;
 import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider;
 import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
+import org.apache.calcite.rel.rules.AggregateConstantKeyRule;
 import org.apache.calcite.rel.rules.AggregateExpandDistinctAggregatesRule;
 import org.apache.calcite.rel.rules.AggregateFilterTransposeRule;
 import org.apache.calcite.rel.rules.AggregateJoinTransposeRule;
@@ -486,7 +487,7 @@ public class RelOptRulesTest extends RelOptTestBase {
         .addRuleInstance(ReduceExpressionsRule.JOIN_INSTANCE)
         .build();
     final String sql = "select e1.sal\n"
-        + " from (select * from emp where deptno = 200) as e1\n"
+        + "from (select * from emp where deptno = 200) as e1\n"
         + "where e1.deptno in (\n"
         + "  select e2.deptno from emp e2 where e2.sal = 100)";
     checkPlanning(tester.withDecorrelation(false).withTrim(true), preProgram,
@@ -2021,6 +2022,46 @@ public class RelOptRulesTest extends RelOptTestBase {
     checkPlanning(tester, preProgram, new HepPlanner(program), sql);
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-1023">[CALCITE-1023]
+   * Planner rule that removes Aggregate keys that are constant</a>. */
+  @Test public void testAggregateConstantKeyRule() {
+    final HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(AggregateConstantKeyRule.INSTANCE)
+        .build();
+    final String sql = "select count(*) as c\n"
+        + "from sales.emp\n"
+        + "where deptno = 10\n"
+        + "group by deptno, sal";
+    checkPlanning(new HepPlanner(program), sql);
+  }
+
+  /** Tests {@link AggregateConstantKeyRule} where reduction is not possible
+   * because "deptno" is the only key. */
+  @Test public void testAggregateConstantKeyRule2() {
+    final HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(AggregateConstantKeyRule.INSTANCE)
+        .build();
+    final String sql = "select count(*) as c\n"
+        + "from sales.emp\n"
+        + "where deptno = 10\n"
+        + "group by deptno";
+    checkPlanUnchanged(new HepPlanner(program), sql);
+  }
+
+  /** Tests {@link AggregateConstantKeyRule} where both keys are constants but
+   * only one can be removed. */
+  @Test public void testAggregateConstantKeyRule3() {
+    final HepProgram program = new HepProgramBuilder()
+        .addRuleInstance(AggregateConstantKeyRule.INSTANCE)
+        .build();
+    final String sql = "select job\n"
+        + "from sales.emp\n"
+        + "where sal is null and job = 'Clerk'\n"
+        + "group by sal, job\n"
+        + "having count(*) > 3";
+    checkPlanning(new HepPlanner(program), sql);
+  }
 }
 
 // End RelOptRulesTest.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/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 fc82b09..38edf69 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -199,7 +199,8 @@ ProjectRel(EXPR$0=[1])
     </TestCase>
     <TestCase name="testSemiJoinReduceConstants">
         <Resource name="sql">
-            <![CDATA[select e1.sal from (select * from emp where deptno = 200) as e1
+            <![CDATA[select e1.sal
+from (select * from emp where deptno = 200) as e1
 where e1.deptno in (
   select e2.deptno from emp e2 where e2.sal = 100)]]>
         </Resource>
@@ -3914,7 +3915,8 @@ LogicalAggregate(group=[{}], EXPR$0=[COUNT()])
     </TestCase>
     <TestCase name="testPushFilterPastAggThree">
         <Resource name="sql">
-            <![CDATA[select deptno from emp group by deptno having count(*) > 1]]>
+            <![CDATA[select deptno from emp
+group by deptno having count(*) > 1]]>
         </Resource>
         <Resource name="planBefore">
             <![CDATA[
@@ -4234,4 +4236,87 @@ LogicalSort(sort0=[$0], dir0=[ASC], fetch=[0])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testAggregateConstantKeyRule">
+        <Resource name="sql">
+            <![CDATA[select count(*) as c
+from sales.emp
+where deptno = 10
+group by deptno, sal]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(C=[$2])
+  LogicalAggregate(group=[{0, 1}], C=[COUNT()])
+    LogicalProject(DEPTNO=[$7], SAL=[$5])
+      LogicalFilter(condition=[=($7, 10)])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(C=[$2])
+  LogicalProject(DEPTNO=[10], SAL=[$0], C=[$1])
+    LogicalAggregate(group=[{1}], C=[COUNT()])
+      LogicalProject(DEPTNO=[$7], SAL=[$5])
+        LogicalFilter(condition=[=($7, 10)])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testAggregateConstantKeyRule2">
+        <Resource name="sql">
+            <![CDATA[select count(*) as c
+from sales.emp
+where deptno = 10
+group by deptno]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(C=[$1])
+  LogicalAggregate(group=[{0}], C=[COUNT()])
+    LogicalProject(DEPTNO=[$7])
+      LogicalFilter(condition=[=($7, 10)])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(C=[$1])
+  LogicalAggregate(group=[{0}], C=[COUNT()])
+    LogicalProject(DEPTNO=[$7])
+      LogicalFilter(condition=[=($7, 10)])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testAggregateConstantKeyRule3">
+        <Resource name="sql">
+            <![CDATA[select job
+from sales.emp
+where sal is null and job = 'Clerk'
+group by sal, job
+having count(*) > 3]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(JOB=[$1])
+  LogicalFilter(condition=[>($2, 3)])
+    LogicalAggregate(group=[{0, 1}], agg#0=[COUNT()])
+      LogicalProject(SAL=[$5], JOB=[$2])
+        LogicalFilter(condition=[AND(IS NULL($5), =($2, 'Clerk'))])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(JOB=[$1])
+  LogicalFilter(condition=[>($2, 3)])
+    LogicalProject(SAL=[$0], JOB=['Clerk'], $f1=[$1])
+      LogicalAggregate(group=[{0}], agg#0=[COUNT()])
+        LogicalProject(SAL=[$5], JOB=[$2])
+          LogicalFilter(condition=[AND(IS NULL($5), =($2, 'Clerk'))])
+            LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>

http://git-wip-us.apache.org/repos/asf/calcite/blob/5197a714/core/src/test/resources/sql/agg.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/agg.iq b/core/src/test/resources/sql/agg.iq
index 28aca38..720b3df 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -1378,6 +1378,33 @@ select count('1') from "scott".emp join "scott".dept using (deptno) where false;
 
 !ok
 
+# [CALCITE-1023] Planner rule that removes Aggregate keys that are constant
+select job, sum(sal) as sum_sal, deptno
+from "scott".emp
+where deptno = 10
+group by deptno, job;
++-----------+---------+--------+
+| JOB       | SUM_SAL | DEPTNO |
++-----------+---------+--------+
+| CLERK     | 1300.00 |     10 |
+| MANAGER   | 2450.00 |     10 |
+| PRESIDENT | 5000.00 |     10 |
++-----------+---------+--------+
+(3 rows)
+
+!ok
+!if (fixed.calcite794) {
+select job, sum(sal) as sum_sal, deptno
+from "scott".emp
+where deptno = 10
+group by deptno, job;
+EnumerableCalc(expr#0..2=[{inputs}], JOB=[$t0], SUM_SAL=[$t2], DEPTNO=[$t1])
+  EnumerableAggregate(group=[{2, 7}], SUM_SAL=[SUM($5)])
+    EnumerableCalc(expr#0..7=[{inputs}], expr#8=[CAST($t7):INTEGER], expr#9=[10], expr#10=[=($t8, $t9)], proj#0..7=[{exprs}], $condition=[$t10])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+!}
+
 # [CALCITE-729] IndexOutOfBoundsException in ROLLUP query on JDBC data source
 !use jdbc_scott
 select deptno, job, count(*) as c