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 2019/04/15 03:37:23 UTC

[calcite] branch master updated (38e3c30 -> 9538374)

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

jhyde pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git.


    from 38e3c30  [CALCITE-2977] Exception is not thrown when there are ambiguous field in select list
     new 53561d2  [CALCITE-1338] JoinProjectTransposeRule should not pull a literal up through the null-generating side of a join (Chunwei Lei)
     new 9538374  [CALCITE-2991] getMaxRowCount should return 1 for an Aggregate with constant keys (Vineet Garg)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../main/java/org/apache/calcite/plan/Strong.java  |  32 ++++
 .../calcite/rel/metadata/RelMdMaxRowCount.java     |  24 +++
 .../rel/rules/JoinProjectTransposeRule.java        |  13 ++
 .../org/apache/calcite/test/RelMetadataTest.java   |  11 ++
 .../org/apache/calcite/test/RelOptRulesTest.java   | 103 ++++++++++-
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 201 ++++++++++++++++++++-
 6 files changed, 379 insertions(+), 5 deletions(-)


[calcite] 01/02: [CALCITE-1338] JoinProjectTransposeRule should not pull a literal up through the null-generating side of a join (Chunwei Lei)

Posted by jh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 53561d2af027bbeaeef4843c7915d33b1af8fb4f
Author: Chunwei Lei <ch...@alibaba-inc.com>
AuthorDate: Wed Apr 10 12:20:36 2019 +0800

    [CALCITE-1338] JoinProjectTransposeRule should not pull a literal up through the null-generating side of a join (Chunwei Lei)
    
    Close apache/calcite#1153
---
 .../main/java/org/apache/calcite/plan/Strong.java  |  32 ++++
 .../rel/rules/JoinProjectTransposeRule.java        |  13 ++
 .../org/apache/calcite/test/RelOptRulesTest.java   | 103 ++++++++++-
 .../org/apache/calcite/test/RelOptRulesTest.xml    | 201 ++++++++++++++++++++-
 4 files changed, 344 insertions(+), 5 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/plan/Strong.java b/core/src/main/java/org/apache/calcite/plan/Strong.java
index 3b2b8d8..a987e7e 100644
--- a/core/src/main/java/org/apache/calcite/plan/Strong.java
+++ b/core/src/main/java/org/apache/calcite/plan/Strong.java
@@ -21,6 +21,7 @@ import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.rex.RexVisitorImpl;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.util.ImmutableBitSet;
 
@@ -89,6 +90,37 @@ public class Strong {
     return MAP.getOrDefault(kind, Policy.AS_IS);
   }
 
+  /**
+   * Returns whether a given expression is strong.
+   *
+   * <p>Examples:</p>
+   * <ul>
+   *   <li>Returns true for {@code c = 1} since it returns null if and only if
+   *   c is null
+   *   <li>Returns false for {@code c IS NULL} since it always returns TRUE
+   *   or FALSE
+   *</ul>
+   *
+   * @param e Expression
+   * @return true if the expression is strong, false otherwise
+   */
+  public static boolean isStrong(RexNode e) {
+    final ImmutableBitSet.Builder nullColumns = ImmutableBitSet.builder();
+    e.accept(
+        new RexVisitorImpl<Void>(true) {
+          public Void visitInputRef(RexInputRef inputRef) {
+            nullColumns.set(inputRef.getIndex());
+            return super.visitInputRef(inputRef);
+          }
+        });
+    return isNull(e, nullColumns.build());
+  }
+
+  /** Returns whether all expressions in a list are strong. */
+  public static boolean allStrong(List<RexNode> operands) {
+    return operands.stream().allMatch(Strong::isStrong);
+  }
+
   /** Returns whether an expression is definitely not true. */
   public boolean isNotTrue(RexNode node) {
     switch (node.getKind()) {
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/JoinProjectTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/JoinProjectTransposeRule.java
index 6e53ec2..4b6b613 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/JoinProjectTransposeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/JoinProjectTransposeRule.java
@@ -21,6 +21,7 @@ import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptRuleOperand;
 import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.Strong;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
@@ -171,6 +172,18 @@ public class JoinProjectTransposeRule extends RelOptRule {
       return;
     }
 
+    if (includeOuter) {
+      if (leftProj != null && joinType.generatesNullsOnLeft()
+          && !Strong.allStrong(leftProj.getProjects())) {
+        return;
+      }
+
+      if (rightProj != null && joinType.generatesNullsOnRight()
+          && !Strong.allStrong(rightProj.getProjects())) {
+        return;
+      }
+    }
+
     // Construct two RexPrograms and combine them.  The bottom program
     // is a join of the projection expressions from the left and/or
     // right projects that feed into the join.  The top program contains
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 b7f21f3..bf3eb8e 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -491,7 +491,7 @@ public class RelOptRulesTest extends RelOptTestBase {
     checkPlanning(tester, preProgram, new HepPlanner(program), sql);
   }
 
-  @Test public void testJoinProjectTranspose() {
+  @Test public void testJoinProjectTranspose1() {
     final HepProgram preProgram =
         HepProgram.builder()
             .addRuleInstance(ProjectJoinTransposeRule.INSTANCE)
@@ -513,6 +513,101 @@ public class RelOptRulesTest extends RelOptTestBase {
   }
 
   /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-1338">[CALCITE-1338]
+   * JoinProjectTransposeRule should not pull a literal above the
+   * null-generating side of a join</a>. */
+  @Test public void testJoinProjectTranspose2() {
+    final String sql = "select *\n"
+        + "from dept a\n"
+        + "left join (select name, 1 from dept) as b\n"
+        + "on a.name = b.name";
+    sql(sql)
+        .withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
+        .checkUnchanged();
+  }
+
+  /** As {@link #testJoinProjectTranspose2()};
+   * should not transpose since the left project of right join has literal. */
+  @Test public void testJoinProjectTranspose3() {
+    final String sql = "select *\n"
+        + "from (select name, 1 from dept) as a\n"
+        + "right join dept b\n"
+        + "on a.name = b.name";
+    sql(sql)
+        .withRule(JoinProjectTransposeRule.LEFT_PROJECT_INCLUDE_OUTER)
+        .checkUnchanged();
+  }
+
+  /** As {@link #testJoinProjectTranspose2()};
+   * should not transpose since the right project of left join has not-strong
+   * expression {@code y is not null}. */
+  @Test public void testJoinProjectTranspose4() {
+    final String sql = "select *\n"
+        + "from dept a\n"
+        + "left join (select x name, y is not null from\n"
+        + "(values (2, cast(null as integer)), (2, 1)) as t(x, y)) b\n"
+        + "on a.name = b.name";
+    sql(sql)
+        .withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
+        .checkUnchanged();
+  }
+
+  /** As {@link #testJoinProjectTranspose2()};
+   * should not transpose since the right project of left join has not-strong
+   * expression {@code 1 + 1}. */
+  @Test public void testJoinProjectTranspose5() {
+    final String sql = "select *\n"
+        + "from dept a\n"
+        + "left join (select name, 1 + 1 from dept) as b\n"
+        + "on a.name = b.name";
+    sql(sql)
+        .withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
+        .checkUnchanged();
+  }
+
+  /** As {@link #testJoinProjectTranspose2()};
+   * should not transpose since both the left project and right project have
+   * literal. */
+  @Test public void testJoinProjectTranspose6() {
+    final String sql = "select *\n"
+        + "from (select name, 1 from dept) a\n"
+        + "full join (select name, 1 from dept) as b\n"
+        + "on a.name = b.name";
+    sql(sql)
+        .withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
+        .checkUnchanged();
+  }
+
+  /** As {@link #testJoinProjectTranspose2()};
+   * Should transpose since all expressions in the right project of left join
+   * are strong. */
+  @Test public void testJoinProjectTranspose7() {
+    final String sql = "select *\n"
+        + "from dept a\n"
+        + "left join (select name from dept) as b\n"
+        + " on a.name = b.name";
+    sql(sql)
+        .withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
+        .check();
+  }
+
+  /** As {@link #testJoinProjectTranspose2()};
+   * should transpose since all expressions including
+   * {@code deptno > 10 and cast(null as boolean)} in the right project of left
+   * join are strong. */
+  @Test public void testJoinProjectTranspose8() {
+    final String sql = "select *\n"
+        + "from dept a\n"
+        + "left join (\n"
+        + "  select name, deptno > 10 and cast(null as boolean)\n"
+        + "  from dept) as b\n"
+        + "on a.name = b.name";
+    sql(sql)
+        .withRule(JoinProjectTransposeRule.RIGHT_PROJECT_INCLUDE_OUTER)
+        .check();
+  }
+
+  /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-889">[CALCITE-889]
    * Implement SortUnionTransposeRule</a>. */
   @Test public void testSortUnionTranspose() {
@@ -4159,10 +4254,10 @@ public class RelOptRulesTest extends RelOptTestBase {
   }
 
   @Test public void testSelectNotInCorrelated() {
-    final String sql = "select sal, \n"
+    final String sql = "select sal,\n"
         + " empno NOT IN (\n"
-        + " select deptno from dept \n"
-        + "   where emp.job=dept.name) \n"
+        + " select deptno from dept\n"
+        + "   where emp.job=dept.name)\n"
         + " from emp";
     checkSubQuery(sql).withLateDecorrelation(true).check();
   }
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 ef28033..392c496 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -5778,7 +5778,7 @@ LogicalProject(NAME=[$1])
 ]]>
         </Resource>
     </TestCase>
-    <TestCase name="testJoinProjectTranspose">
+    <TestCase name="testJoinProjectTranspose1">
         <Resource name="sql">
             <![CDATA[select a.name
 from dept a
@@ -5811,6 +5811,205 @@ LogicalProject(NAME=[$1])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testJoinProjectTranspose2">
+        <Resource name="sql">
+            <![CDATA[select *
+from dept a
+left join (select name, 1 from dept) as b
+on a.name = b.name
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2], EXPR$1=[$3])
+  LogicalJoin(condition=[=($1, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$1], EXPR$1=[1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2], EXPR$1=[$3])
+  LogicalJoin(condition=[=($1, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$1], EXPR$1=[1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testJoinProjectTranspose3">
+        <Resource name="sql">
+            <![CDATA[select *
+from (select name, 1 from dept) as a
+right join dept b
+on a.name = b.name
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(NAME=[$0], EXPR$1=[$1], DEPTNO=[$2], NAME0=[$3])
+  LogicalJoin(condition=[=($0, $3)], joinType=[right])
+    LogicalProject(NAME=[$1], EXPR$1=[1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(NAME=[$0], EXPR$1=[$1], DEPTNO=[$2], NAME0=[$3])
+  LogicalJoin(condition=[=($0, $3)], joinType=[right])
+    LogicalProject(NAME=[$1], EXPR$1=[1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testJoinProjectTranspose4">
+        <Resource name="sql">
+            <![CDATA[select *
+left join (select x name, y is not null from
+(values (2, cast(null as integer)), (2, 1)) as t(x, y)) b
+on a.name = b.name
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2], EXPR$1=[$3])
+  LogicalJoin(condition=[=($1, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$0], EXPR$1=[IS NOT NULL($1)])
+      LogicalUnion(all=[true])
+        LogicalProject(EXPR$0=[2], EXPR$1=[null:INTEGER])
+          LogicalValues(tuples=[[{ 0 }]])
+        LogicalProject(EXPR$0=[2], EXPR$1=[1])
+          LogicalValues(tuples=[[{ 0 }]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2], EXPR$1=[$3])
+  LogicalJoin(condition=[=($1, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$0], EXPR$1=[IS NOT NULL($1)])
+      LogicalUnion(all=[true])
+        LogicalProject(EXPR$0=[2], EXPR$1=[null:INTEGER])
+          LogicalValues(tuples=[[{ 0 }]])
+        LogicalProject(EXPR$0=[2], EXPR$1=[1])
+          LogicalValues(tuples=[[{ 0 }]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testJoinProjectTranspose5">
+        <Resource name="sql">
+            <![CDATA[select *
+left join (select x name, y is not null from
+(values (2, cast(null as integer)), (2, 1)) as t(x, y)) b
+on a.name = b.name
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2], EXPR$1=[$3])
+  LogicalJoin(condition=[=($1, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$1], EXPR$1=[+(1, 1)])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2], EXPR$1=[$3])
+  LogicalJoin(condition=[=($1, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$1], EXPR$1=[+(1, 1)])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testJoinProjectTranspose6">
+        <Resource name="sql">
+            <![CDATA[select *
+from (select name, 1 from dept) a
+full join (select name, 1 from dept) as b
+on a.name = b.name
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(NAME=[$0], EXPR$1=[$1], NAME0=[$2], EXPR$10=[$3])
+  LogicalJoin(condition=[=($0, $2)], joinType=[full])
+    LogicalProject(NAME=[$1], EXPR$1=[1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$1], EXPR$1=[1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(NAME=[$0], EXPR$1=[$1], NAME0=[$2], EXPR$10=[$3])
+  LogicalJoin(condition=[=($0, $2)], joinType=[full])
+    LogicalProject(NAME=[$1], EXPR$1=[1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$1], EXPR$1=[1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testJoinProjectTranspose7">
+        <Resource name="sql">
+            <![CDATA[select *
+from dept a
+left join (select name from dept) as b
+on a.name = b.name
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2])
+  LogicalJoin(condition=[=($1, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$1])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2])
+  LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$3])
+    LogicalJoin(condition=[=($1, $3)], joinType=[left])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testJoinProjectTranspose8">
+        <Resource name="sql">
+            <![CDATA[select *
+from dept a
+left join (select name, deptno > 10 and cast(null as boolean) from dept b) as b
+on a.name = b.name
+]]>
+        </Resource>
+        <Resource name="planBefore">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2], EXPR$1=[$3])
+  LogicalJoin(condition=[=($1, $2)], joinType=[left])
+    LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+    LogicalProject(NAME=[$1], EXPR$1=[AND(>($0, 10), null)])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+        <Resource name="planAfter">
+            <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$2], EXPR$1=[$3])
+  LogicalProject(DEPTNO=[$0], NAME=[$1], NAME0=[$3], EXPR$1=[AND(>($2, 10), null)])
+    LogicalJoin(condition=[=($1, $3)], joinType=[left])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
     <TestCase name="testMergeFilter">
         <Resource name="sql">
             <![CDATA[select name from (


[calcite] 02/02: [CALCITE-2991] getMaxRowCount should return 1 for an Aggregate with constant keys (Vineet Garg)

Posted by jh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9538374e8fae5cec7d6f7b270850f5dfb4c1fc06
Author: Vineet Garg <vg...@apache.org>
AuthorDate: Thu Apr 11 16:47:02 2019 -0700

    [CALCITE-2991] getMaxRowCount should return 1 for an Aggregate with constant keys (Vineet Garg)
    
    Close apache/calcite#1155
---
 .../calcite/rel/metadata/RelMdMaxRowCount.java     | 24 ++++++++++++++++++++++
 .../org/apache/calcite/test/RelMetadataTest.java   | 11 ++++++++++
 2 files changed, 35 insertions(+)

diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
index 967960d..7058a6c 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.rel.metadata;
 
 import org.apache.calcite.adapter.enumerable.EnumerableLimit;
+import org.apache.calcite.plan.RelOptPredicateList;
 import org.apache.calcite.plan.volcano.RelSubset;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
@@ -29,6 +30,7 @@ import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.util.Bug;
 import org.apache.calcite.util.BuiltInMethod;
@@ -129,6 +131,16 @@ public class RelMdMaxRowCount
       // Aggregate with no GROUP BY always returns 1 row (even on empty table).
       return 1D;
     }
+
+    // Aggregate with constant GROUP BY always returns 1 row
+    if (rel.getGroupType() == Aggregate.Group.SIMPLE) {
+      final RelOptPredicateList predicateList =
+          mq.getPulledUpPredicates(rel.getInput());
+      if (predicateList != null
+          && allGroupKeysAreConstant(rel, predicateList)) {
+        return 1D;
+      }
+    }
     final Double rowCount = mq.getMaxRowCount(rel.getInput());
     if (rowCount == null) {
       return null;
@@ -136,6 +148,18 @@ public class RelMdMaxRowCount
     return rowCount * rel.getGroupSets().size();
   }
 
+  private static boolean allGroupKeysAreConstant(Aggregate aggregate,
+      RelOptPredicateList predicateList) {
+    final RexBuilder rexBuilder = aggregate.getCluster().getRexBuilder();
+    for (int key : aggregate.getGroupSet()) {
+      if (!predicateList.constantMap.containsKey(
+          rexBuilder.makeInputRef(aggregate.getInput(), key))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   public Double getMaxRowCount(Join rel, RelMetadataQuery mq) {
     Double left = mq.getMaxRowCount(rel.getLeft());
     Double right = mq.getMaxRowCount(rel.getRight());
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index 0d04bd2..85b4ba2 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -678,6 +678,17 @@ public class RelMetadataTest extends SqlToRelTestBase {
     checkRowCount(sql, 1D, 1D, 1D);
   }
 
+  @Test public void testRowCountAggregateConstantKey() {
+    final String sql = "select count(*) from emp where deptno=2 and ename='emp1' "
+        + "group by deptno, ename";
+    checkRowCount(sql, 1D, 0D, 1D);
+  }
+
+  @Test public void testRowCountAggregateConstantKeys() {
+    final String sql = "select distinct deptno from emp where deptno=4";
+    checkRowCount(sql, 1D, 0D, 1D);
+  }
+
   @Test public void testRowCountFilterAggregateEmptyKey() {
     final String sql = "select count(*) from emp where 1 = 0";
     checkRowCount(sql, 1D, 1D, 1D);