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/02 00:55:32 UTC

calcite git commit: Following [CALCITE-987], improve rowCount and maxRowCount formulae

Repository: calcite
Updated Branches:
  refs/heads/master d3c5acd3a -> 24b074715


Following [CALCITE-987], improve rowCount and maxRowCount formulae

Enable tests for rowCount and add tests for maxRowCount.

Move 2 methods into RelMdUtil.

Add Util.first methods, for unboxing to primitive values.


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

Branch: refs/heads/master
Commit: 24b074715c2663d78c0b2a2d42e0711c733b1f40
Parents: d3c5acd
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Nov 30 16:22:27 2015 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Tue Dec 1 14:43:45 2015 -0800

----------------------------------------------------------------------
 .../java/org/apache/calcite/rel/core/Join.java  |  33 ++-
 .../java/org/apache/calcite/rel/core/Minus.java |  12 +-
 .../org/apache/calcite/rel/core/SemiJoin.java   |  10 +-
 .../rel/metadata/RelMdDistinctRowCount.java     |  35 +++
 .../calcite/rel/metadata/RelMdMaxRowCount.java  |  71 ++++--
 .../calcite/rel/metadata/RelMdRowCount.java     |  60 +++--
 .../apache/calcite/rel/metadata/RelMdUtil.java  |  61 ++++-
 .../calcite/rel/metadata/RelMetadataQuery.java  |   4 +-
 .../calcite/sql/validate/SqlValidator.java      |   4 +-
 .../main/java/org/apache/calcite/util/Util.java |  63 ++++-
 .../org/apache/calcite/test/CalciteAssert.java  |   2 +-
 .../apache/calcite/test/MockCatalogReader.java  |  33 +--
 .../apache/calcite/test/RelMetadataTest.java    | 242 ++++++++++++++++---
 .../apache/calcite/test/RelOptRulesTest.java    |  18 +-
 14 files changed, 510 insertions(+), 138 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/rel/core/Join.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Join.java b/core/src/main/java/org/apache/calcite/rel/core/Join.java
index 1a6b5c1..5a0990d 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Join.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Join.java
@@ -23,6 +23,7 @@ import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.BiRel;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -31,6 +32,7 @@ import org.apache.calcite.rex.RexChecker;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -98,7 +100,7 @@ public abstract class Join extends BiRel {
     return ImmutableList.of(condition);
   }
 
-  public RelNode accept(RexShuttle shuttle) {
+  @Override public RelNode accept(RexShuttle shuttle) {
     RexNode condition = shuttle.apply(this.condition);
     if (this.condition == condition) {
       return this;
@@ -155,34 +157,29 @@ public abstract class Join extends BiRel {
     return true;
   }
 
-  // implement RelNode
-  public RelOptCost computeSelfCost(RelOptPlanner planner) {
+  @Override public RelOptCost computeSelfCost(RelOptPlanner planner) {
     // REVIEW jvs 9-Apr-2006:  Just for now...
     double rowCount = RelMetadataQuery.getRowCount(this);
     return planner.getCostFactory().makeCost(rowCount, 0, 0);
   }
 
+  /** @deprecated Use {@link RelMdUtil#getJoinRowCount(Join, RexNode)}. */
+  @Deprecated // to be removed before 2.0
   public static double estimateJoinedRows(
       Join joinRel,
       RexNode condition) {
-    double product =
-        RelMetadataQuery.getRowCount(joinRel.getLeft())
-            * RelMetadataQuery.getRowCount(joinRel.getRight());
-
-    // TODO:  correlation factor
-    return product * RelMetadataQuery.getSelectivity(joinRel, condition);
+    return Util.first(RelMdUtil.getJoinRowCount(joinRel, condition), 1D);
   }
 
-  // implement RelNode
-  public double getRows() {
-    return estimateJoinedRows(this, condition);
+  @Override public double getRows() {
+    return Util.first(RelMdUtil.getJoinRowCount(this, condition), 1D);
   }
 
-  public Set<String> getVariablesStopped() {
+  @Override public Set<String> getVariablesStopped() {
     return variablesStopped;
   }
 
-  public RelWriter explainTerms(RelWriter pw) {
+  @Override public RelWriter explainTerms(RelWriter pw) {
     return super.explainTerms(pw)
         .item("condition", condition)
         .item("joinType", joinType.name().toLowerCase())
@@ -192,7 +189,7 @@ public abstract class Join extends BiRel {
             !getSystemFieldList().isEmpty());
   }
 
-  protected RelDataType deriveRowType() {
+  @Override protected RelDataType deriveRowType() {
     return deriveJoinRowType(
         left.getRowType(),
         right.getRowType(),
@@ -295,14 +292,14 @@ public abstract class Join extends BiRel {
         == (systemFieldList.size()
         + leftType.getFieldCount()
         + rightType.getFieldCount()));
-    List<String> nameList = new ArrayList<String>();
-    List<RelDataType> typeList = new ArrayList<RelDataType>();
+    List<String> nameList = new ArrayList<>();
+    final List<RelDataType> typeList = new ArrayList<>();
 
     // use a hashset to keep track of the field names; this is needed
     // to ensure that the contains() call to check for name uniqueness
     // runs in constant time; otherwise, if the number of fields is large,
     // doing a contains() on a list can be expensive
-    HashSet<String> uniqueNameList = new HashSet<String>();
+    final HashSet<String> uniqueNameList = new HashSet<>();
     addFields(systemFieldList, typeList, nameList, uniqueNameList);
     addFields(leftType.getFieldList(), typeList, nameList, uniqueNameList);
     if (rightType != null) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/rel/core/Minus.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Minus.java b/core/src/main/java/org/apache/calcite/rel/core/Minus.java
index 23c1ee8..18c6f60 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Minus.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Minus.java
@@ -20,7 +20,7 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.sql.SqlKind;
 
 import java.util.List;
@@ -49,15 +49,7 @@ public abstract class Minus extends SetOp {
   }
 
   @Override public double getRows() {
-    // REVIEW jvs 30-May-2005:  I just pulled this out of a hat.
-    double dRows = RelMetadataQuery.getRowCount(inputs.get(0));
-    for (int i = 1; i < inputs.size(); i++) {
-      dRows -= 0.5 * RelMetadataQuery.getRowCount(inputs.get(i));
-    }
-    if (dRows < 0) {
-      dRows = 0;
-    }
-    return dRows;
+    return RelMdUtil.getMinusRowCount(this);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/rel/core/SemiJoin.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/SemiJoin.java b/core/src/main/java/org/apache/calcite/rel/core/SemiJoin.java
index 77129b1..4570919 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/SemiJoin.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/SemiJoin.java
@@ -22,12 +22,12 @@ import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.metadata.RelMdUtil;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.util.ImmutableIntList;
+import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -100,9 +100,9 @@ public class SemiJoin extends EquiJoin {
   }
 
   @Override public double getRows() {
-    // TODO:  correlation factor
-    return RelMetadataQuery.getRowCount(left)
-        * RexUtil.getSelectivity(condition);
+    return Util.first(RelMdUtil.getSemiJoinRowCount(left, right, joinType, condition),
+        1D);
+
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
index 784e131..2fb2b25 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
@@ -112,6 +112,11 @@ public class RelMdDistinctRowCount {
       Filter rel,
       ImmutableBitSet groupKey,
       RexNode predicate) {
+    if (predicate == null || predicate.isAlwaysTrue()) {
+      if (groupKey.isEmpty()) {
+        return 1D;
+      }
+    }
     // REVIEW zfong 4/18/06 - In the Broadbase code, duplicates are not
     // removed from the two filter lists.  However, the code below is
     // doing so.
@@ -131,6 +136,11 @@ public class RelMdDistinctRowCount {
       Join rel,
       ImmutableBitSet groupKey,
       RexNode predicate) {
+    if (predicate == null || predicate.isAlwaysTrue()) {
+      if (groupKey.isEmpty()) {
+        return 1D;
+      }
+    }
     return RelMdUtil.getJoinDistinctRowCount(
         rel,
         rel.getJoinType(),
@@ -143,6 +153,11 @@ public class RelMdDistinctRowCount {
       SemiJoin rel,
       ImmutableBitSet groupKey,
       RexNode predicate) {
+    if (predicate == null || predicate.isAlwaysTrue()) {
+      if (groupKey.isEmpty()) {
+        return 1D;
+      }
+    }
     // create a RexNode representing the selectivity of the
     // semijoin filter and pass it to getDistinctRowCount
     RexNode newPred = RelMdUtil.makeSemiJoinSelectivityRexNode(rel);
@@ -165,6 +180,11 @@ public class RelMdDistinctRowCount {
       Aggregate rel,
       ImmutableBitSet groupKey,
       RexNode predicate) {
+    if (predicate == null || predicate.isAlwaysTrue()) {
+      if (groupKey.isEmpty()) {
+        return 1D;
+      }
+    }
     // determine which predicates can be applied on the child of the
     // aggregate
     List<RexNode> notPushable = new ArrayList<RexNode>();
@@ -202,6 +222,11 @@ public class RelMdDistinctRowCount {
       Values rel,
       ImmutableBitSet groupKey,
       RexNode predicate) {
+    if (predicate == null || predicate.isAlwaysTrue()) {
+      if (groupKey.isEmpty()) {
+        return 1D;
+      }
+    }
     Double selectivity = RelMdUtil.guessSelectivity(predicate);
 
     // assume half the rows are duplicates
@@ -213,6 +238,11 @@ public class RelMdDistinctRowCount {
       Project rel,
       ImmutableBitSet groupKey,
       RexNode predicate) {
+    if (predicate == null || predicate.isAlwaysTrue()) {
+      if (groupKey.isEmpty()) {
+        return 1D;
+      }
+    }
     ImmutableBitSet.Builder baseCols = ImmutableBitSet.builder();
     ImmutableBitSet.Builder projCols = ImmutableBitSet.builder();
     List<RexNode> projExprs = rel.getProjects();
@@ -278,6 +308,11 @@ public class RelMdDistinctRowCount {
       RelNode rel,
       ImmutableBitSet groupKey,
       RexNode predicate) {
+    if (predicate == null || predicate.isAlwaysTrue()) {
+      if (groupKey.isEmpty()) {
+        return 1D;
+      }
+    }
     // REVIEW zfong 4/19/06 - Broadbase code does not take into
     // consideration selectivity of predicates passed in.  Also, they
     // assume the rows are unique even if the table is not

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
----------------------------------------------------------------------
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 85ff11a..c8e84cc 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
@@ -19,11 +19,14 @@ package org.apache.calcite.rel.metadata;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Intersect;
 import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.Minus;
 import org.apache.calcite.rel.core.Project;
 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.RexLiteral;
 import org.apache.calcite.util.BuiltInMethod;
 
@@ -39,16 +42,32 @@ public class RelMdMaxRowCount {
   //~ Methods ----------------------------------------------------------------
 
   public Double getMaxRowCount(Union rel) {
-    double nRows = 0.0;
-
+    double rowCount = 0.0;
     for (RelNode input : rel.getInputs()) {
       Double partialRowCount = RelMetadataQuery.getMaxRowCount(input);
       if (partialRowCount == null) {
         return null;
       }
-      nRows += partialRowCount;
+      rowCount += partialRowCount;
+    }
+    return rowCount;
+  }
+
+  public Double getMaxRowCount(Intersect rel) {
+    // max row count is the smallest of the inputs
+    Double rowCount = null;
+    for (RelNode input : rel.getInputs()) {
+      Double partialRowCount = RelMetadataQuery.getMaxRowCount(input);
+      if (rowCount == null
+          || partialRowCount != null && partialRowCount < rowCount) {
+        rowCount = partialRowCount;
+      }
     }
-    return nRows;
+    return rowCount;
+  }
+
+  public Double getMaxRowCount(Minus rel) {
+    return RelMetadataQuery.getMaxRowCount(rel.getInput(0));
   }
 
   public Double getMaxRowCount(Filter rel) {
@@ -60,36 +79,54 @@ public class RelMdMaxRowCount {
   }
 
   public Double getMaxRowCount(Sort rel) {
-    final Double rowCount = RelMetadataQuery.getMaxRowCount(rel.getInput());
-    if (rowCount != null && rel.fetch != null) {
-      final int offset = rel.offset == null ? 0 : RexLiteral.intValue(rel.offset);
+    Double rowCount = RelMetadataQuery.getMaxRowCount(rel.getInput());
+    if (rowCount == null) {
+      return null;
+    }
+    final int offset = rel.offset == null ? 0 : RexLiteral.intValue(rel.offset);
+    rowCount = Math.max(rowCount - offset, 0D);
+
+    if (rel.fetch != null) {
       final int limit = RexLiteral.intValue(rel.fetch);
-      final Double offsetLimit = new Double(offset + limit);
-      // offsetLimit is smaller than rowCount of the input operator
-      // thus, we return the offsetLimit
-      if (offsetLimit < rowCount) {
-        return offsetLimit;
+      if (limit < rowCount) {
+        return (double) limit;
       }
     }
     return rowCount;
   }
 
   public Double getMaxRowCount(Aggregate rel) {
-    return RelMetadataQuery.getMaxRowCount(rel.getInput());
+    if (rel.getGroupSet().isEmpty()) {
+      return 1D;
+    }
+    return RelMetadataQuery.getMaxRowCount(rel.getInput())
+        * rel.getGroupSets().size();
   }
 
   public Double getMaxRowCount(Join rel) {
     Double left = RelMetadataQuery.getMaxRowCount(rel.getLeft());
-    Double right = RelMetadataQuery.getMaxRowCount(rel.getLeft());
+    Double right = RelMetadataQuery.getMaxRowCount(rel.getRight());
     if (left == null || right == null) {
       return null;
-    } else {
-      return left * right;
     }
+    if (left < 1D && rel.getJoinType().generatesNullsOnLeft()) {
+      left = 1D;
+    }
+    if (right < 1D && rel.getJoinType().generatesNullsOnRight()) {
+      right = 1D;
+    }
+    return left * right;
   }
 
   public Double getMaxRowCount(TableScan rel) {
-    return rel.getRows();
+    // For typical tables, there is no upper bound to the number of rows.
+    return Double.POSITIVE_INFINITY;
+  }
+
+  public Double getMaxRowCount(Values values) {
+    // For Values, the maximum row count is the actual row count.
+    // This is especially useful if Values is empty.
+    return (double) values.getTuples().size();
   }
 
   // Catch-all rule when none of the others apply.

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
index 2d7beaf..aac3aae 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
@@ -19,6 +19,8 @@ package org.apache.calcite.rel.metadata;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Intersect;
+import org.apache.calcite.rel.core.Minus;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.SemiJoin;
 import org.apache.calcite.rel.core.Sort;
@@ -41,16 +43,39 @@ public class RelMdRowCount {
   //~ Methods ----------------------------------------------------------------
 
   public Double getRowCount(Union rel) {
-    double nRows = 0.0;
-
+    double rowCount = 0D;
     for (RelNode input : rel.getInputs()) {
       Double partialRowCount = RelMetadataQuery.getRowCount(input);
       if (partialRowCount == null) {
         return null;
       }
-      nRows += partialRowCount;
+      rowCount += partialRowCount;
+    }
+    return rowCount;
+  }
+
+  public Double getRowCount(Intersect rel) {
+    Double rowCount = null;
+    for (RelNode input : rel.getInputs()) {
+      Double partialRowCount = RelMetadataQuery.getRowCount(input);
+      if (rowCount == null
+          || partialRowCount != null && partialRowCount < rowCount) {
+        rowCount = partialRowCount;
+      }
+    }
+    return rowCount;
+  }
+
+  public Double getRowCount(Minus rel) {
+    Double rowCount = null;
+    for (RelNode input : rel.getInputs()) {
+      Double partialRowCount = RelMetadataQuery.getRowCount(input);
+      if (rowCount == null
+          || partialRowCount != null && partialRowCount < rowCount) {
+        rowCount = partialRowCount;
+      }
     }
-    return nRows;
+    return rowCount;
   }
 
   public Double getRowCount(Filter rel) {
@@ -66,15 +91,17 @@ public class RelMdRowCount {
   }
 
   public Double getRowCount(Sort rel) {
-    final Double rowCount = RelMetadataQuery.getRowCount(rel.getInput());
-    if (rowCount != null && rel.fetch != null) {
-      final int offset = rel.offset == null ? 0 : RexLiteral.intValue(rel.offset);
+    Double rowCount = RelMetadataQuery.getRowCount(rel.getInput());
+    if (rowCount == null) {
+      return null;
+    }
+    final int offset = rel.offset == null ? 0 : RexLiteral.intValue(rel.offset);
+    rowCount = Math.max(rowCount - offset, 0D);
+
+    if (rel.fetch != null) {
       final int limit = RexLiteral.intValue(rel.fetch);
-      final Double offsetLimit = new Double(offset + limit);
-      // offsetLimit is smaller than rowCount of the input operator
-      // thus, we return the offsetLimit
-      if (offsetLimit < rowCount) {
-        return offsetLimit;
+      if (limit < rowCount) {
+        return (double) limit;
       }
     }
     return rowCount;
@@ -103,10 +130,13 @@ public class RelMdRowCount {
             groupKey,
             null);
     if (distinctRowCount == null) {
-      return RelMetadataQuery.getRowCount(rel.getInput()) / 10;
-    } else {
-      return distinctRowCount;
+      distinctRowCount = RelMetadataQuery.getRowCount(rel.getInput()) / 10D;
     }
+
+    // Grouping sets multiply
+    distinctRowCount *= rel.getGroupSets().size();
+
+    return distinctRowCount;
   }
 
   // Catch-all rule when none of the others apply.

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
index 24bef2f..d9ea2a2 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUtil.java
@@ -22,7 +22,9 @@ import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Minus;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.SemiJoin;
 import org.apache.calcite.rex.RexBuilder;
@@ -445,8 +447,8 @@ public class RelMdUtil {
       RexBuilder rexBuilder,
       RexNode pred1,
       RexNode pred2) {
-    final List<RexNode> unionList = new ArrayList<RexNode>();
-    final Set<String> strings = new HashSet<String>();
+    final List<RexNode> unionList = new ArrayList<>();
+    final Set<String> strings = new HashSet<>();
 
     for (RexNode rex : RelOptUtil.conjunctions(pred1)) {
       if (strings.add(rex.toString())) {
@@ -477,7 +479,7 @@ public class RelMdUtil {
       RexNode pred2) {
     List<RexNode> list1 = RelOptUtil.conjunctions(pred1);
     List<RexNode> list2 = RelOptUtil.conjunctions(pred2);
-    List<RexNode> minusList = new ArrayList<RexNode>();
+    List<RexNode> minusList = new ArrayList<>();
 
     for (RexNode rex1 : list1) {
       boolean add = true;
@@ -629,9 +631,9 @@ public class RelMdUtil {
     RexNode leftPred = null;
     RexNode rightPred = null;
     if (predicate != null) {
-      List<RexNode> leftFilters = new ArrayList<RexNode>();
-      List<RexNode> rightFilters = new ArrayList<RexNode>();
-      List<RexNode> joinFilters = new ArrayList<RexNode>();
+      List<RexNode> leftFilters = new ArrayList<>();
+      List<RexNode> rightFilters = new ArrayList<>();
+      List<RexNode> joinFilters = new ArrayList<>();
       List<RexNode> predList = RelOptUtil.conjunctions(predicate);
 
       RelOptUtil.classifyFilters(
@@ -676,6 +678,53 @@ public class RelMdUtil {
         RelMetadataQuery.getRowCount(joinRel));
   }
 
+  /** Returns an estimate of the number of rows returned by a {@link Minus}. */
+  public static double getMinusRowCount(Minus minus) {
+    // REVIEW jvs 30-May-2005:  I just pulled this out of a hat.
+    final List<RelNode> inputs = minus.getInputs();
+    double dRows = RelMetadataQuery.getRowCount(inputs.get(0));
+    for (int i = 1; i < inputs.size(); i++) {
+      dRows -= 0.5 * RelMetadataQuery.getRowCount(inputs.get(i));
+    }
+    if (dRows < 0) {
+      dRows = 0;
+    }
+    return dRows;
+  }
+
+  /** Returns an estimate of the number of rows returned by a {@link Join}. */
+  public static Double getJoinRowCount(Join join, RexNode condition) {
+    // Row count estimates of 0 will be rounded up to 1.
+    // So, use maxRowCount where the product is very small.
+    final Double left = RelMetadataQuery.getRowCount(join.getLeft());
+    final Double right = RelMetadataQuery.getRowCount(join.getRight());
+    if (left == null || right == null) {
+      return null;
+    }
+    if (left <= 1D || right <= 1D) {
+      Double max = RelMetadataQuery.getMaxRowCount(join);
+      if (max != null && max <= 1D) {
+        return max;
+      }
+    }
+    double product = left * right;
+
+    // TODO:  correlation factor
+    return product * RelMetadataQuery.getSelectivity(join, condition);
+  }
+
+  /** Returns an estimate of the number of rows returned by a
+   * {@link SemiJoin}. */
+  public static Double getSemiJoinRowCount(RelNode left, RelNode right,
+      JoinRelType joinType, RexNode condition) {
+    // TODO:  correlation factor
+    final Double leftCount = RelMetadataQuery.getRowCount(left);
+    if (leftCount == null) {
+      return null;
+    }
+    return leftCount * RexUtil.getSelectivity(condition);
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /** Visitor that walks over a scalar expression and computes the

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
index 43bbefe..44d0724 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
@@ -89,7 +89,7 @@ public abstract class RelMetadataQuery {
 
   /**
    * Returns the
-   * {@link BuiltInMetadata.RowCount#getRowCount()}
+   * {@link BuiltInMetadata.MaxRowCount#getMaxRowCount()}
    * statistic.
    *
    * @param rel the relational expression
@@ -97,7 +97,7 @@ public abstract class RelMetadataQuery {
    */
   public static Double getMaxRowCount(RelNode rel) {
     final BuiltInMetadata.MaxRowCount metadata =
-            rel.metadata(BuiltInMetadata.MaxRowCount.class);
+        rel.metadata(BuiltInMetadata.MaxRowCount.class);
     return metadata.getMaxRowCount();
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
index 962668f..7878917 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
@@ -103,7 +103,7 @@ import java.util.Map;
  */
 public interface SqlValidator {
   /** Whether to follow the SQL standard strictly. */
-  boolean STRICT = Util.first(Boolean.getBoolean("calcite.strict.sql"), false);
+  boolean STRICT = Util.getBooleanProperty("calcite.strict.sql");
 
   //~ Methods ----------------------------------------------------------------
 
@@ -478,7 +478,7 @@ public interface SqlValidator {
    * <li>In FROM ({@link #getFromScope} , you can only see 'foo'.
    *
    * <li>In WHERE ({@link #getWhereScope}), GROUP BY ({@link #getGroupScope}),
-   * SELECT ({@link #getSelectScope}), and the ON clause of the JOIN
+   * SELECT ({@code getSelectScope}), and the ON clause of the JOIN
    * ({@link #getJoinScope}) you can see 'emp', 'dept', and 'foo'.
    *
    * <li>In ORDER BY ({@link #getOrderScope}), you can see the column alias 'x';

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/main/java/org/apache/calcite/util/Util.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Util.java b/core/src/main/java/org/apache/calcite/util/Util.java
index 7091855..eea3e47 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -1913,12 +1913,67 @@ public class Util {
     };
   }
 
-  public static <T> T first(T t0, T t1) {
-    return t0 != null ? t0 : t1;
+  /** Returns the first value if it is not null,
+   * otherwise the second value.
+   *
+   * <p>The result may be null.
+   *
+   * <p>Equivalent to the Elvis operator ({@code ?:}) of languages such as
+   * Groovy or PHP. */
+  public static <T> T first(T v0, T v1) {
+    return v0 != null ? v0 : v1;
+  }
+
+  /** Unboxes a {@link Double} value,
+   * using a given default value if it is null. */
+  public static double first(Double v0, double v1) {
+    return v0 != null ? v0 : v1;
+  }
+
+  /** Unboxes a {@link Float} value,
+   * using a given default value if it is null. */
+  public static float first(Float v0, float v1) {
+    return v0 != null ? v0 : v1;
+  }
+
+  /** Unboxes a {@link Integer} value,
+   * using a given default value if it is null. */
+  public static int first(Integer v0, int v1) {
+    return v0 != null ? v0 : v1;
+  }
+
+  /** Unboxes a {@link Long} value,
+   * using a given default value if it is null. */
+  public static long first(Long v0, long v1) {
+    return v0 != null ? v0 : v1;
+  }
+
+  /** Unboxes a {@link Boolean} value,
+   * using a given default value if it is null. */
+  public static boolean first(Boolean v0, boolean v1) {
+    return v0 != null ? v0 : v1;
+  }
+
+  /** Unboxes a {@link Short} value,
+   * using a given default value if it is null. */
+  public static short first(Short v0, short v1) {
+    return v0 != null ? v0 : v1;
+  }
+
+  /** Unboxes a {@link Character} value,
+   * using a given default value if it is null. */
+  public static char first(Character v0, char v1) {
+    return v0 != null ? v0 : v1;
+  }
+
+  /** Unboxes a {@link Byte} value,
+   * using a given default value if it is null. */
+  public static byte first(Byte v0, byte v1) {
+    return v0 != null ? v0 : v1;
   }
 
-  public static <T> Iterable<T> orEmpty(Iterable<T> t0) {
-    return t0 != null ? t0 : ImmutableList.<T>of();
+  public static <T> Iterable<T> orEmpty(Iterable<T> v0) {
+    return v0 != null ? v0 : ImmutableList.<T>of();
   }
 
   /** Returns the last element of a list.

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index aebf192..f9e2bea 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -112,7 +112,7 @@ public class CalciteAssert {
 
   /** Whether to enable slow tests. Default is false. */
   public static final boolean ENABLE_SLOW =
-      Util.first(Boolean.getBoolean("calcite.test.slow"), false);
+      Util.getBooleanProperty("calcite.test.slow");
 
   private static final DateFormat UTC_DATE_FORMAT;
   private static final DateFormat UTC_TIME_FORMAT;

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
index eccedb2..71b3115 100644
--- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
@@ -170,7 +170,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
     // Register "EMP" table.
     final MockTable empTable =
-        MockTable.create(this, salesSchema, "EMP", false);
+        MockTable.create(this, salesSchema, "EMP", false, 14);
     empTable.addColumn("EMPNO", intType);
     empTable.addColumn("ENAME", varchar20Type);
     empTable.addColumn("JOB", varchar10Type);
@@ -183,13 +183,13 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     registerTable(empTable);
 
     // Register "DEPT" table.
-    MockTable deptTable = MockTable.create(this, salesSchema, "DEPT", false);
+    MockTable deptTable = MockTable.create(this, salesSchema, "DEPT", false, 4);
     deptTable.addColumn("DEPTNO", intType);
     deptTable.addColumn("NAME", varchar10Type);
     registerTable(deptTable);
 
     // Register "BONUS" table.
-    MockTable bonusTable = MockTable.create(this, salesSchema, "BONUS", false);
+    MockTable bonusTable = MockTable.create(this, salesSchema, "BONUS", false, 0);
     bonusTable.addColumn("ENAME", varchar20Type);
     bonusTable.addColumn("JOB", varchar10Type);
     bonusTable.addColumn("SAL", intType);
@@ -197,8 +197,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     registerTable(bonusTable);
 
     // Register "SALGRADE" table.
-    MockTable salgradeTable = MockTable.create(this, salesSchema, "SALGRADE",
-        false);
+    MockTable salgradeTable = MockTable.create(this, salesSchema, "SALGRADE", false, 5);
     salgradeTable.addColumn("GRADE", intType);
     salgradeTable.addColumn("LOSAL", intType);
     salgradeTable.addColumn("HISAL", intType);
@@ -206,7 +205,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
     // Register "EMP_ADDRESS" table
     MockTable contactAddressTable =
-        MockTable.create(this, salesSchema, "EMP_ADDRESS", false);
+        MockTable.create(this, salesSchema, "EMP_ADDRESS", false, 26);
     contactAddressTable.addColumn("EMPNO", intType);
     contactAddressTable.addColumn("HOME_ADDRESS", addressType);
     contactAddressTable.addColumn("MAILING_ADDRESS", addressType);
@@ -218,7 +217,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
     // Register "CONTACT" table.
     MockTable contactTable = MockTable.create(this, customerSchema, "CONTACT",
-        false);
+        false, 1000);
     contactTable.addColumn("CONTACTNO", intType);
     contactTable.addColumn("FNAME", varchar10Type);
     contactTable.addColumn("LNAME", varchar10Type);
@@ -228,7 +227,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
     // Register "ACCOUNT" table.
     MockTable accountTable = MockTable.create(this, customerSchema, "ACCOUNT",
-        false);
+        false, 457);
     accountTable.addColumn("ACCTNO", intType);
     accountTable.addColumn("TYPE", varchar20Type);
     accountTable.addColumn("BALANCE", intType);
@@ -236,7 +235,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
     // Register "ORDERS" stream.
     MockTable ordersStream = MockTable.create(this, salesSchema, "ORDERS",
-        true);
+        true, Double.POSITIVE_INFINITY);
     ordersStream.addColumn("ROWTIME", timestampType);
     ordersStream.addMonotonic("ROWTIME");
     ordersStream.addColumn("PRODUCTID", intType);
@@ -245,7 +244,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
     // Register "SHIPMENTS" stream.
     MockTable shipmentsStream = MockTable.create(this, salesSchema, "SHIPMENTS",
-        true);
+        true, Double.POSITIVE_INFINITY);
     shipmentsStream.addColumn("ROWTIME", timestampType);
     shipmentsStream.addMonotonic("ROWTIME");
     shipmentsStream.addColumn("ORDERID", intType);
@@ -256,7 +255,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     // but "DEPTNO" not visible and set to 20 by default
     // and "SAL" is visible but must be greater than 1000
     MockTable emp20View = new MockTable(this, salesSchema.getCatalogName(),
-        salesSchema.name, "EMP_20", false) {
+        salesSchema.name, "EMP_20", false, 600) {
       private final Table table = empTable.unwrap(Table.class);
       private final ImmutableIntList mapping =
           ImmutableIntList.of(0, 1, 2, 3, 4, 5, 6, 8);
@@ -552,6 +551,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
   public static class MockTable implements Prepare.PreparingTable {
     protected final MockCatalogReader catalogReader;
     private final boolean stream;
+    private final double rowCount;
     private final List<Map.Entry<String, RelDataType>> columnList =
         Lists.newArrayList();
     private RelDataType rowType;
@@ -560,17 +560,18 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     private final Set<String> monotonicColumnSet = Sets.newHashSet();
 
     public MockTable(MockCatalogReader catalogReader, String catalogName,
-        String schemaName, String name, boolean stream) {
+        String schemaName, String name, boolean stream, double rowCount) {
       this.catalogReader = catalogReader;
       this.stream = stream;
+      this.rowCount = rowCount;
       this.names = ImmutableList.of(catalogName, schemaName, name);
     }
 
     public static MockTable create(MockCatalogReader catalogReader,
-        MockSchema schema, String name, boolean stream) {
+        MockSchema schema, String name, boolean stream, double rowCount) {
       MockTable table =
           new MockTable(catalogReader, schema.getCatalogName(), schema.name,
-              name, stream);
+              name, stream, rowCount);
       schema.addTable(name);
       return table;
     }
@@ -611,7 +612,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
     }
 
     public double getRowCount() {
-      return 0;
+      return rowCount;
     }
 
     public RelOptSchema getRelOptSchema() {
@@ -676,7 +677,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
 
     public RelOptTable extend(List<RelDataTypeField> extendedFields) {
       final MockTable table = new MockTable(catalogReader, names.get(0),
-          names.get(1), names.get(2), stream);
+          names.get(1), names.get(2), stream, rowCount);
       table.columnList.addAll(columnList);
       table.columnList.addAll(extendedFields);
       table.onRegister(catalogReader.typeFactory);

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
----------------------------------------------------------------------
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 6b0451e..98b3a42 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -85,6 +85,7 @@ import java.util.Set;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
@@ -113,9 +114,9 @@ public class RelMetadataTest extends SqlToRelTestBase {
 
   private static final double DEFAULT_SELECTIVITY = 0.25;
 
-  private static final double EMP_SIZE = 1000.0;
+  private static final double EMP_SIZE = 14d;
 
-  private static final double DEPT_SIZE = 100.0;
+  private static final double DEPT_SIZE = 4d;
 
   //~ Methods ----------------------------------------------------------------
 
@@ -431,57 +432,222 @@ public class RelMetadataTest extends SqlToRelTestBase {
       double expected) {
     RelNode rel = convertSql(sql);
     Double result = RelMetadataQuery.getRowCount(rel);
-    assertTrue(result != null);
+    assertThat(result, notNullValue());
+    assertEquals(expected, result, 0d);
+  }
+
+  private void checkMaxRowCount(
+      String sql,
+      double expected) {
+    RelNode rel = convertSql(sql);
+    Double result = RelMetadataQuery.getMaxRowCount(rel);
+    assertThat(result, notNullValue());
     assertEquals(expected, result, 0d);
   }
 
-  @Ignore
   @Test public void testRowCountEmp() {
-    checkRowCount(
-        "select * from emp",
-        EMP_SIZE);
+    final String sql = "select * from emp";
+    checkRowCount(sql, EMP_SIZE);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
   }
 
-  @Ignore
   @Test public void testRowCountDept() {
-    checkRowCount(
-        "select * from dept",
-        DEPT_SIZE);
+    final String sql = "select * from dept";
+    checkRowCount(sql, DEPT_SIZE);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountValues() {
+    final String sql = "select * from (values (1), (2)) as t(c)";
+    checkRowCount(sql, 2);
+    checkMaxRowCount(sql, 2);
   }
 
-  @Ignore
   @Test public void testRowCountCartesian() {
-    checkRowCount(
-        "select * from emp,dept",
-        EMP_SIZE * DEPT_SIZE);
+    final String sql = "select * from emp,dept";
+    checkRowCount(sql, EMP_SIZE * DEPT_SIZE);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
   }
 
-  @Ignore
   @Test public void testRowCountJoin() {
-    checkRowCount(
-        "select * from emp inner join dept on emp.deptno = dept.deptno",
-        EMP_SIZE * DEPT_SIZE * DEFAULT_EQUAL_SELECTIVITY);
+    final String sql = "select * from emp\n"
+        + "inner join dept on emp.deptno = dept.deptno";
+    checkRowCount(sql, EMP_SIZE * DEPT_SIZE * DEFAULT_EQUAL_SELECTIVITY);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountJoinFinite() {
+    final String sql = "select * from (select * from emp limit 14) as emp\n"
+        + "inner join (select * from dept limit 4) as dept\n"
+        + "on emp.deptno = dept.deptno";
+    checkRowCount(sql, EMP_SIZE * DEPT_SIZE * DEFAULT_EQUAL_SELECTIVITY);
+    checkMaxRowCount(sql, 56D); // 4 * 14
+  }
+
+  @Test public void testRowCountJoinEmptyFinite() {
+    final String sql = "select * from (select * from emp limit 0) as emp\n"
+        + "inner join (select * from dept limit 4) as dept\n"
+        + "on emp.deptno = dept.deptno";
+    checkRowCount(sql, 1D); // 0, rounded up to row count's minimum 1
+    checkMaxRowCount(sql, 0D); // 0 * 4
+  }
+
+  @Test public void testRowCountLeftJoinEmptyFinite() {
+    final String sql = "select * from (select * from emp limit 0) as emp\n"
+        + "left join (select * from dept limit 4) as dept\n"
+        + "on emp.deptno = dept.deptno";
+    checkRowCount(sql, 1D); // 0, rounded up to row count's minimum 1
+    checkMaxRowCount(sql, 0D); // 0 * 4
+  }
+
+  @Test public void testRowCountRightJoinEmptyFinite() {
+    final String sql = "select * from (select * from emp limit 0) as emp\n"
+        + "right join (select * from dept limit 4) as dept\n"
+        + "on emp.deptno = dept.deptno";
+    checkRowCount(sql, 1D); // 0, rounded up to row count's minimum 1
+    checkMaxRowCount(sql, 4D); // 1 * 4
+  }
+
+  @Test public void testRowCountJoinFiniteEmpty() {
+    final String sql = "select * from (select * from emp limit 7) as emp\n"
+        + "inner join (select * from dept limit 0) as dept\n"
+        + "on emp.deptno = dept.deptno";
+    checkRowCount(sql, 1D); // 0, rounded up to row count's minimum 1
+    checkMaxRowCount(sql, 0D); // 7 * 0
+  }
+
+  @Test public void testRowCountJoinEmptyEmpty() {
+    final String sql = "select * from (select * from emp limit 0) as emp\n"
+        + "inner join (select * from dept limit 0) as dept\n"
+        + "on emp.deptno = dept.deptno";
+    checkRowCount(sql, 1D); // 0, rounded up to row count's minimum 1
+    checkMaxRowCount(sql, 0D); // 0 * 0
   }
 
-  @Ignore
   @Test public void testRowCountUnion() {
-    checkRowCount(
-        "select ename from emp union all select name from dept",
-        EMP_SIZE + DEPT_SIZE);
+    final String sql = "select ename from emp\n"
+        + "union all\n"
+        + "select name from dept";
+    checkRowCount(sql, EMP_SIZE + DEPT_SIZE);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountUnionOnFinite() {
+    final String sql = "select ename from (select * from emp limit 100)\n"
+        + "union all\n"
+        + "select name from (select * from dept limit 40)";
+    checkRowCount(sql, EMP_SIZE + DEPT_SIZE);
+    checkMaxRowCount(sql, 140D);
+  }
+
+  @Test public void testRowCountIntersectOnFinite() {
+    final String sql = "select ename from (select * from emp limit 100)\n"
+        + "intersect\n"
+        + "select name from (select * from dept limit 40)";
+    checkRowCount(sql, Math.min(EMP_SIZE, DEPT_SIZE));
+    checkMaxRowCount(sql, 40D);
+  }
+
+  @Test public void testRowCountMinusOnFinite() {
+    final String sql = "select ename from (select * from emp limit 100)\n"
+        + "except\n"
+        + "select name from (select * from dept limit 40)";
+    checkRowCount(sql, 4D);
+    checkMaxRowCount(sql, 100D);
   }
 
-  @Ignore
   @Test public void testRowCountFilter() {
-    checkRowCount(
-        "select * from emp where ename='Mathilda'",
-        EMP_SIZE * DEFAULT_EQUAL_SELECTIVITY);
+    final String sql = "select * from emp where ename='Mathilda'";
+    checkRowCount(sql, EMP_SIZE * DEFAULT_EQUAL_SELECTIVITY);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountFilterOnFinite() {
+    final String sql = "select * from (select * from emp limit 10)\n"
+        + "where ename='Mathilda'";
+    checkRowCount(sql, 10D * DEFAULT_EQUAL_SELECTIVITY);
+    checkMaxRowCount(sql, 10D);
   }
 
-  @Ignore
   @Test public void testRowCountSort() {
-    checkRowCount(
-        "select * from emp order by ename",
-        EMP_SIZE);
+    final String sql = "select * from emp order by ename";
+    checkRowCount(sql, EMP_SIZE);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountSortHighLimit() {
+    final String sql = "select * from emp order by ename limit 123456";
+    checkRowCount(sql, EMP_SIZE);
+    checkMaxRowCount(sql, 123456D);
+  }
+
+  @Test public void testRowCountSortHighOffset() {
+    final String sql = "select * from emp order by ename offset 123456";
+    checkRowCount(sql, 1D);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountSortHighOffsetLimit() {
+    final String sql = "select * from emp order by ename limit 5 offset 123456";
+    checkRowCount(sql, 1D);
+    checkMaxRowCount(sql, 5D);
+  }
+
+  @Test public void testRowCountSortLimit() {
+    final String sql = "select * from emp order by ename limit 10";
+    checkRowCount(sql, 10d);
+    checkMaxRowCount(sql, 10d);
+  }
+
+  @Test public void testRowCountSortLimit0() {
+    final String sql = "select * from emp order by ename limit 10";
+    checkRowCount(sql, 10d);
+    checkMaxRowCount(sql, 10d);
+  }
+
+  @Test public void testRowCountSortLimitOffset() {
+    final String sql = "select * from emp order by ename limit 10 offset 5";
+    checkRowCount(sql, 9D); // 14 - 5
+    checkMaxRowCount(sql, 10d);
+  }
+
+  @Test public void testRowCountSortLimitOffsetOnFinite() {
+    final String sql = "select * from (select * from emp limit 12)\n"
+        + "order by ename limit 20 offset 5";
+    checkRowCount(sql, 7d);
+    checkMaxRowCount(sql, 7d);
+  }
+
+  @Test public void testRowCountAggregate() {
+    final String sql = "select deptno from emp group by deptno";
+    checkRowCount(sql, 1.4D);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountAggregateGroupingSets() {
+    final String sql = "select deptno from emp\n"
+        + "group by grouping sets ((deptno), (empno, deptno))";
+    checkRowCount(sql, 2.8D); // EMP_SIZE / 10 * 2
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountAggregateGroupingSetsOneEmpty() {
+    final String sql = "select deptno from emp\n"
+        + "group by grouping sets ((deptno), ())";
+    checkRowCount(sql, 2.8D);
+    checkMaxRowCount(sql, Double.POSITIVE_INFINITY);
+  }
+
+  @Test public void testRowCountAggregateEmptyKey() {
+    final String sql = "select count(*) from emp";
+    checkRowCount(sql, 1D);
+    checkMaxRowCount(sql, 1D);
+  }
+
+  @Test public void testRowCountAggregateEmptyKeyOnEmptyTable() {
+    final String sql = "select count(*) from (select * from emp limit 0)";
+    checkRowCount(sql, 1D);
+    checkMaxRowCount(sql, 1D);
   }
 
   private void checkFilterSelectivity(
@@ -584,11 +750,21 @@ public class RelMetadataTest extends SqlToRelTestBase {
   @Test public void testDistinctRowCountTable() {
     // no unique key information is available so return null
     RelNode rel = convertSql("select * from emp where deptno = 10");
-    ImmutableBitSet groupKey = ImmutableBitSet.of();
+    ImmutableBitSet groupKey =
+        ImmutableBitSet.of(rel.getRowType().getFieldNames().indexOf("DEPTNO"));
+    Double result =
+        RelMetadataQuery.getDistinctRowCount(
+            rel, groupKey, null);
+    assertThat(result, nullValue());
+  }
+
+  @Test public void testDistinctRowCountTableEmptyKey() {
+    RelNode rel = convertSql("select * from emp where deptno = 10");
+    ImmutableBitSet groupKey = ImmutableBitSet.of(); // empty key
     Double result =
         RelMetadataQuery.getDistinctRowCount(
             rel, groupKey, null);
-    assertTrue(result == null);
+    assertThat(result, is(1D));
   }
 
   /** Asserts that {@link RelMetadataQuery#getUniqueKeys(RelNode)}

http://git-wip-us.apache.org/repos/asf/calcite/blob/24b07471/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 ab21c8c..4c72ba5 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -413,17 +413,17 @@ public class RelOptRulesTest extends RelOptTestBase {
 
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-987">[CALCITE-987]
-   * Implement SortUnionTransposeRule</a>. */
+   * Push limit 0 will result in an infinite loop</a>. */
   @Test public void testSortUnionTranspose3() {
     final HepProgram program =
-            HepProgram.builder()
-                    .addRuleInstance(ProjectSetOpTransposeRule.INSTANCE)
-                    .addRuleInstance(SortUnionTransposeRule.MATCH_NULL_FETCH)
-                    .build();
+        HepProgram.builder()
+            .addRuleInstance(ProjectSetOpTransposeRule.INSTANCE)
+            .addRuleInstance(SortUnionTransposeRule.MATCH_NULL_FETCH)
+            .build();
     final String sql = "select a.name from dept a\n"
-            + "union all\n"
-            + "select b.name from dept b\n"
-            + "order by name limit 0";
+        + "union all\n"
+        + "select b.name from dept b\n"
+        + "order by name limit 0";
     checkPlanning(program, sql);
   }
 
@@ -1098,7 +1098,7 @@ public class RelOptRulesTest extends RelOptTestBase {
                     typeFactory.createSqlType(SqlTypeName.INTEGER);
                 for (int i = 0; i < 10; i++) {
                   String t = String.valueOf((char) ('A' + i));
-                  MockTable table = MockTable.create(this, schema, t, false);
+                  MockTable table = MockTable.create(this, schema, t, false, 100);
                   table.addColumn(t, intType);
                   registerTable(table);
                 }