You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jc...@apache.org on 2017/04/26 19:18:38 UTC

[04/11] calcite git commit: [CALCITE-1682] New metadata providers for expression column origin and all predicates in plan

http://git-wip-us.apache.org/repos/asf/calcite/blob/41b05d78/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 234b666..dbc230a 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -36,11 +36,16 @@ import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.core.Correlate;
 import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.Filter;
 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.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.rel.logical.LogicalAggregate;
 import org.apache.calcite.rel.logical.LogicalExchange;
 import org.apache.calcite.rel.logical.LogicalFilter;
@@ -65,9 +70,13 @@ import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexTableInputRef;
 import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.tools.Frameworks;
@@ -80,6 +89,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
 
 import org.hamcrest.CoreMatchers;
 import org.hamcrest.CustomTypeSafeMatcher;
@@ -91,17 +101,27 @@ import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
+import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.isA;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -133,6 +153,8 @@ public class RelMetadataTest extends SqlToRelTestBase {
 
   private static final double DEPT_SIZE = 4d;
 
+  private static final String EMP_QNAME = "[CATALOG, SALES, EMP]";
+
   //~ Methods ----------------------------------------------------------------
 
   private static Matcher<? super Number> nearTo(Number v, Number epsilon) {
@@ -1481,6 +1503,631 @@ public class RelMetadataTest extends SqlToRelTestBase {
     assertThat(RelMdUtil.linear(12, 0, 10, 100, 200), is(200d));
   }
 
+  @Test public void testExpressionLineageStar() {
+    // All columns in output
+    final RelNode tableRel = convertSql("select * from emp");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(4, tableRel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(tableRel, ref);
+    final String inputRef = RexInputRef.of(4, tableRel.getRowType().getFieldList()).toString();
+    assertThat(r.size(), is(1));
+    final String resultString = r.iterator().next().toString();
+    assertThat(resultString, startsWith(EMP_QNAME));
+    assertThat(resultString, endsWith(inputRef));
+  }
+
+  @Test public void testExpressionLineageTwoColumns() {
+    // mgr is column 3 in catalog.sales.emp
+    // deptno is column 7 in catalog.sales.emp
+    final RelNode rel = convertSql("select mgr, deptno from emp");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref1 = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r1 = mq.getExpressionLineage(rel, ref1);
+    assertThat(r1.size(), is(1));
+    final RexTableInputRef result1 = (RexTableInputRef) r1.iterator().next();
+    assertThat(result1.getQualifiedName(), is(EMP_QNAME));
+    assertThat(result1.getIndex(), is(3));
+
+    final RexNode ref2 = RexInputRef.of(1, rel.getRowType().getFieldList());
+    final Set<RexNode> r2 = mq.getExpressionLineage(rel, ref2);
+    assertThat(r2.size(), is(1));
+    final RexTableInputRef result2 = (RexTableInputRef) r2.iterator().next();
+    assertThat(result2.getQualifiedName(), is(EMP_QNAME));
+    assertThat(result2.getIndex(), is(7));
+
+    assertThat(result1.getIdentifier(), is(result2.getIdentifier()));
+  }
+
+  @Test public void testExpressionLineageTwoColumnsSwapped() {
+    // deptno is column 7 in catalog.sales.emp
+    // mgr is column 3 in catalog.sales.emp
+    final RelNode rel = convertSql("select deptno, mgr from emp");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref1 = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r1 = mq.getExpressionLineage(rel, ref1);
+    assertThat(r1.size(), is(1));
+    final RexTableInputRef result1 = (RexTableInputRef) r1.iterator().next();
+    assertThat(result1.getQualifiedName(), is(EMP_QNAME));
+    assertThat(result1.getIndex(), is(7));
+
+    final RexNode ref2 = RexInputRef.of(1, rel.getRowType().getFieldList());
+    final Set<RexNode> r2 = mq.getExpressionLineage(rel, ref2);
+    assertThat(r2.size(), is(1));
+    final RexTableInputRef result2 = (RexTableInputRef) r2.iterator().next();
+    assertThat(result2.getQualifiedName(), is(EMP_QNAME));
+    assertThat(result2.getIndex(), is(3));
+
+    assertThat(result1.getIdentifier(), is(result2.getIdentifier()));
+  }
+
+  @Test public void testExpressionLineageCombineTwoColumns() {
+    // empno is column 0 in catalog.sales.emp
+    // deptno is column 7 in catalog.sales.emp
+    final RelNode rel = convertSql("select empno + deptno from emp");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+
+    assertThat(r.size(), is(1));
+    final RexNode result = r.iterator().next();
+    assertThat(result.getKind(), is(SqlKind.PLUS));
+    final RexCall call = (RexCall) result;
+    assertThat(call.getOperands().size(), is(2));
+    final RexTableInputRef inputRef1 = (RexTableInputRef) call.getOperands().get(0);
+    assertThat(inputRef1.getQualifiedName(), is(EMP_QNAME));
+    assertThat(inputRef1.getIndex(), is(0));
+    final RexTableInputRef inputRef2 = (RexTableInputRef) call.getOperands().get(1);
+    assertThat(inputRef2.getQualifiedName(), is(EMP_QNAME));
+    assertThat(inputRef2.getIndex(), is(7));
+    assertThat(inputRef1.getIdentifier(), is(inputRef2.getIdentifier()));
+  }
+
+  @Test public void testExpressionLineageInnerJoinLeft() {
+    // ename is column 1 in catalog.sales.emp
+    final RelNode rel = convertSql("select ename from emp,dept");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+    assertThat(r.size(), is(1));
+    final RexTableInputRef result = (RexTableInputRef) r.iterator().next();
+    assertThat(result.getQualifiedName(), is(EMP_QNAME));
+    assertThat(result.getIndex(), is(1));
+  }
+
+  @Test public void testExpressionLineageInnerJoinRight() {
+    // ename is column 0 in catalog.sales.bonus
+    final RelNode rel = convertSql("select bonus.ename from emp join bonus using (ename)");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+    assertThat(r.size(), is(1));
+    final RexTableInputRef result = (RexTableInputRef) r.iterator().next();
+    assertThat(result.getQualifiedName(), is("[CATALOG, SALES, BONUS]"));
+    assertThat(result.getIndex(), is(0));
+  }
+
+  @Test public void testExpressionLineageSelfJoin() {
+    // deptno is column 7 in catalog.sales.emp
+    // sal is column 5 in catalog.sales.emp
+    final RelNode rel = convertSql("select a.deptno, b.sal from (select * from emp limit 7) as a\n"
+        + "inner join (select * from emp limit 2) as b\n"
+        + "on a.deptno = b.deptno");
+    final RelNode tableRel = convertSql("select * from emp");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref1 = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r1 = mq.getExpressionLineage(rel, ref1);
+    final String inputRef1 = RexInputRef.of(7, tableRel.getRowType().getFieldList()).toString();
+    assertThat(r1.size(), is(1));
+    final String resultString1 = r1.iterator().next().toString();
+    assertThat(resultString1, startsWith(EMP_QNAME));
+    assertThat(resultString1, endsWith(inputRef1));
+
+    final RexNode ref2 = RexInputRef.of(1, rel.getRowType().getFieldList());
+    final Set<RexNode> r2 = mq.getExpressionLineage(rel, ref2);
+    final String inputRef2 = RexInputRef.of(5, tableRel.getRowType().getFieldList()).toString();
+    assertThat(r2.size(), is(1));
+    final String resultString2 = r2.iterator().next().toString();
+    assertThat(resultString2, startsWith(EMP_QNAME));
+    assertThat(resultString2, endsWith(inputRef2));
+
+    assertThat(((RexTableInputRef) r1.iterator().next()).getIdentifier(),
+        not(((RexTableInputRef) r2.iterator().next()).getIdentifier()));
+  }
+
+  @Test public void testExpressionLineageOuterJoin() {
+    // lineage cannot be determined
+    final RelNode rel = convertSql("select name as dname from emp left outer join dept"
+        + " on emp.deptno = dept.deptno");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+    assertNull(r);
+  }
+
+  @Test public void testExpressionLineageFilter() {
+    // ename is column 1 in catalog.sales.emp
+    final RelNode rel = convertSql("select ename from emp where deptno = 10");
+    final RelNode tableRel = convertSql("select * from emp");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+    final String inputRef = RexInputRef.of(1, tableRel.getRowType().getFieldList()).toString();
+    assertThat(r.size(), is(1));
+    final String resultString = r.iterator().next().toString();
+    assertThat(resultString, startsWith(EMP_QNAME));
+    assertThat(resultString, endsWith(inputRef));
+  }
+
+  @Test public void testExpressionLineageAggregateGroupColumn() {
+    // deptno is column 7 in catalog.sales.emp
+    final RelNode rel = convertSql("select deptno, count(*) from emp where deptno > 10 "
+        + "group by deptno having count(*) = 0");
+    final RelNode tableRel = convertSql("select * from emp");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+    final String inputRef = RexInputRef.of(7, tableRel.getRowType().getFieldList()).toString();
+    assertThat(r.size(), is(1));
+    final String resultString = r.iterator().next().toString();
+    assertThat(resultString, startsWith(EMP_QNAME));
+    assertThat(resultString, endsWith(inputRef));
+  }
+
+  @Test public void testExpressionLineageAggregateAggColumn() {
+    // lineage cannot be determined
+    final RelNode rel = convertSql("select deptno, count(*) from emp where deptno > 10 "
+        + "group by deptno having count(*) = 0");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(1, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+    assertNull(r);
+  }
+
+  @Test public void testExpressionLineageUnion() {
+    // sal is column 5 in catalog.sales.emp
+    final RelNode rel = convertSql("select sal from (\n"
+        + "  select * from emp union all select * from emp) "
+        + "where deptno = 10");
+    final RelNode tableRel = convertSql("select * from emp");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+    final String inputRef = RexInputRef.of(5, tableRel.getRowType().getFieldList()).toString();
+    assertThat(r.size(), is(2));
+    for (RexNode result : r) {
+      final String resultString = result.toString();
+      assertThat(resultString, startsWith(EMP_QNAME));
+      assertThat(resultString, endsWith(inputRef));
+    }
+
+    Iterator<RexNode> it = r.iterator();
+    assertThat(((RexTableInputRef) it.next()).getIdentifier(),
+        not(((RexTableInputRef) it.next()).getIdentifier()));
+  }
+
+  @Test public void testExpressionLineageMultiUnion() {
+    // empno is column 0 in catalog.sales.emp
+    // sal is column 5 in catalog.sales.emp
+    final RelNode rel = convertSql("select a.empno + b.sal from \n"
+        + " (select empno, ename from emp,dept) a join "
+        + " (select * from emp union all select * from emp) b \n"
+        + " on a.empno = b.empno \n"
+        + " where b.deptno = 10");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+
+    // With the union, we should get two origins
+    // The first one should be the same one: join
+    // The second should come from each union input
+    final Set<String> set = new HashSet<>();
+    assertThat(r.size(), is(2));
+    for (RexNode result : r) {
+      assertThat(result.getKind(), is(SqlKind.PLUS));
+      final RexCall call = (RexCall) result;
+      assertThat(call.getOperands().size(), is(2));
+      final RexTableInputRef inputRef1 = (RexTableInputRef) call.getOperands().get(0);
+      assertThat(inputRef1.getQualifiedName(), is(EMP_QNAME));
+      // Add join alpha to set
+      set.add(inputRef1.getQualifiedName());
+      assertThat(inputRef1.getIndex(), is(0));
+      final RexTableInputRef inputRef2 = (RexTableInputRef) call.getOperands().get(1);
+      assertThat(inputRef2.getQualifiedName(), is(EMP_QNAME));
+      assertThat(inputRef2.getIndex(), is(5));
+      assertThat(inputRef1.getIdentifier(), not(inputRef2.getIdentifier()));
+    }
+    assertThat(set.size(), is(1));
+  }
+
+  @Test public void testExpressionLineageValues() {
+    // lineage cannot be determined
+    final RelNode rel = convertSql("select * from (values (1), (2)) as t(c)");
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final RexNode ref = RexInputRef.of(0, rel.getRowType().getFieldList());
+    final Set<RexNode> r = mq.getExpressionLineage(rel, ref);
+    assertNull(r);
+  }
+
+  @Test public void testAllPredicates() {
+    final Project rel = (Project) convertSql("select * from emp, dept");
+    final Join join = (Join) rel.getInput();
+    final RelOptTable empTable = join.getInput(0).getTable();
+    final RelOptTable deptTable = join.getInput(1).getTable();
+    Frameworks.withPlanner(
+        new Frameworks.PlannerAction<Void>() {
+          public Void apply(RelOptCluster cluster,
+              RelOptSchema relOptSchema,
+              SchemaPlus rootSchema) {
+            checkAllPredicates(cluster, empTable, deptTable);
+            return null;
+          }
+        });
+  }
+
+  private void checkAllPredicates(RelOptCluster cluster, RelOptTable empTable,
+      RelOptTable deptTable) {
+    final RelBuilder relBuilder = RelBuilder.proto().create(cluster, null);
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+
+    final LogicalTableScan empScan = LogicalTableScan.create(cluster, empTable);
+    relBuilder.push(empScan);
+
+    RelOptPredicateList predicates =
+        mq.getAllPredicates(empScan);
+    assertThat(predicates.pulledUpPredicates.isEmpty(), is(true));
+
+    relBuilder.filter(
+        relBuilder.equals(relBuilder.field("EMPNO"),
+            relBuilder.literal(BigDecimal.ONE)));
+
+    final RelNode filter = relBuilder.peek();
+    predicates = mq.getAllPredicates(filter);
+    assertThat(predicates.pulledUpPredicates.size(), is(1));
+    RexCall call = (RexCall) predicates.pulledUpPredicates.get(0);
+    assertThat(call.getOperands().size(), is(2));
+    RexTableInputRef inputRef1 = (RexTableInputRef) call.getOperands().get(0);
+    assertThat(inputRef1.getQualifiedName(), is(EMP_QNAME));
+    assertThat(inputRef1.getIndex(), is(0));
+
+    final LogicalTableScan deptScan =
+        LogicalTableScan.create(cluster, deptTable);
+    relBuilder.push(deptScan);
+
+    relBuilder.join(JoinRelType.INNER,
+        relBuilder.equals(relBuilder.field(2, 0, "DEPTNO"),
+            relBuilder.field(2, 1, "DEPTNO")));
+
+    relBuilder.project(relBuilder.field("DEPTNO"));
+    final RelNode project = relBuilder.peek();
+    predicates = mq.getAllPredicates(project);
+    assertThat(predicates.pulledUpPredicates.size(), is(2));
+    // From Filter
+    call = (RexCall) predicates.pulledUpPredicates.get(0);
+    assertThat(call.getOperands().size(), is(2));
+    inputRef1 = (RexTableInputRef) call.getOperands().get(0);
+    assertThat(inputRef1.getQualifiedName(), is(EMP_QNAME));
+    assertThat(inputRef1.getIndex(), is(0));
+    // From Join
+    call = (RexCall) predicates.pulledUpPredicates.get(1);
+    assertThat(call.getOperands().size(), is(2));
+    inputRef1 = (RexTableInputRef) call.getOperands().get(0);
+    assertThat(inputRef1.getQualifiedName(), is(EMP_QNAME));
+    assertThat(inputRef1.getIndex(), is(7));
+    RexTableInputRef inputRef2 = (RexTableInputRef) call.getOperands().get(1);
+    assertThat(inputRef2.getQualifiedName(), is("[CATALOG, SALES, DEPT]"));
+    assertThat(inputRef2.getIndex(), is(0));
+  }
+
+  @Test public void testAllPredicatesAggregate1() {
+    final String sql = "select a, max(b) from (\n"
+        + "  select empno as a, sal as b from emp where empno = 5)subq\n"
+        + "group by a";
+    final RelNode rel = convertSql(sql);
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+    RelOptPredicateList inputSet = mq.getAllPredicates(rel);
+    ImmutableList<RexNode> pulledUpPredicates = inputSet.pulledUpPredicates;
+    assertThat(pulledUpPredicates.size(), is(1));
+    RexCall call = (RexCall) pulledUpPredicates.get(0);
+    assertThat(call.getOperands().size(), is(2));
+    final RexTableInputRef inputRef1 = (RexTableInputRef) call.getOperands().get(0);
+    assertThat(inputRef1.getQualifiedName(), is(EMP_QNAME));
+    assertThat(inputRef1.getIndex(), is(0));
+    final RexLiteral constant = (RexLiteral) call.getOperands().get(1);
+    assertThat(constant.toString(), is("5"));
+  }
+
+  @Test public void testAllPredicatesAggregate2() {
+    final String sql = "select * from (select a, max(b) from (\n"
+        + "  select empno as a, sal as b from emp)subq\n"
+        + "group by a) \n"
+        + "where a = 5";
+    final RelNode rel = convertSql(sql);
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+    RelOptPredicateList inputSet = mq.getAllPredicates(rel);
+    ImmutableList<RexNode> pulledUpPredicates = inputSet.pulledUpPredicates;
+    assertThat(pulledUpPredicates.size(), is(1));
+    RexCall call = (RexCall) pulledUpPredicates.get(0);
+    assertThat(call.getOperands().size(), is(2));
+    final RexTableInputRef inputRef1 = (RexTableInputRef) call.getOperands().get(0);
+    assertThat(inputRef1.getQualifiedName(), is(EMP_QNAME));
+    assertThat(inputRef1.getIndex(), is(0));
+    final RexLiteral constant = (RexLiteral) call.getOperands().get(1);
+    assertThat(constant.toString(), is("5"));
+  }
+
+  @Test public void testAllPredicatesAggregate3() {
+    final String sql = "select * from (select a, max(b) as b from (\n"
+        + "  select empno as a, sal as b from emp)subq\n"
+        + "group by a) \n"
+        + "where b = 5";
+    final RelNode rel = convertSql(sql);
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+    RelOptPredicateList inputSet = mq.getAllPredicates(rel);
+    // Filter on aggregate, we cannot infer lineage
+    assertNull(inputSet);
+  }
+
+  private void checkNodeTypeCount(String sql, Map<Class<? extends RelNode>, Integer> expected) {
+    final RelNode rel = convertSql(sql);
+    final RelMetadataQuery mq = RelMetadataQuery.instance();
+    final Multimap<Class<? extends RelNode>, RelNode> result = mq.getNodeTypes(rel);
+    assertThat(result, notNullValue());
+    final Map<Class<? extends RelNode>, Integer> resultCount = new HashMap<>();
+    for (Entry<Class<? extends RelNode>, Collection<RelNode>> e : result.asMap().entrySet()) {
+      resultCount.put(e.getKey(), e.getValue().size());
+    }
+    assertEquals(expected, resultCount);
+  }
+
+  @Test public void testNodeTypeCountEmp() {
+    final String sql = "select * from emp";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountDept() {
+    final String sql = "select * from dept";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountValues() {
+    final String sql = "select * from (values (1), (2)) as t(c)";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(Values.class, 1);
+    expected.put(Project.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountCartesian() {
+    final String sql = "select * from emp,dept";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Join.class, 1);
+    expected.put(Project.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountJoin() {
+    final String sql = "select * from emp\n"
+        + "inner join dept on emp.deptno = dept.deptno";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Join.class, 1);
+    expected.put(Project.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountJoinFinite() {
+    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";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Join.class, 1);
+    expected.put(Project.class, 3);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountJoinEmptyFinite() {
+    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";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Join.class, 1);
+    expected.put(Project.class, 3);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountLeftJoinEmptyFinite() {
+    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";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Join.class, 1);
+    expected.put(Project.class, 3);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountRightJoinEmptyFinite() {
+    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";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Join.class, 1);
+    expected.put(Project.class, 3);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountJoinFiniteEmpty() {
+    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";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Join.class, 1);
+    expected.put(Project.class, 3);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountJoinEmptyEmpty() {
+    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";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Join.class, 1);
+    expected.put(Project.class, 3);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountUnion() {
+    final String sql = "select ename from emp\n"
+        + "union all\n"
+        + "select name from dept";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Project.class, 2);
+    expected.put(Union.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountUnionOnFinite() {
+    final String sql = "select ename from (select * from emp limit 100)\n"
+        + "union all\n"
+        + "select name from (select * from dept limit 40)";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Union.class, 1);
+    expected.put(Project.class, 4);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountMinusOnFinite() {
+    final String sql = "select ename from (select * from emp limit 100)\n"
+        + "except\n"
+        + "select name from (select * from dept limit 40)";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 2);
+    expected.put(Minus.class, 1);
+    expected.put(Project.class, 4);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountFilter() {
+    final String sql = "select * from emp where ename='Mathilda'";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 1);
+    expected.put(Filter.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountSort() {
+    final String sql = "select * from emp order by ename";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 1);
+    expected.put(Sort.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountSortLimit() {
+    final String sql = "select * from emp order by ename limit 10";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 1);
+    expected.put(Sort.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountSortLimitOffset() {
+    final String sql = "select * from emp order by ename limit 10 offset 5";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 1);
+    expected.put(Sort.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountSortLimitOffsetOnFinite() {
+    final String sql = "select * from (select * from emp limit 12)\n"
+        + "order by ename limit 20 offset 5";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 2);
+    expected.put(Sort.class, 2);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountAggregate() {
+    final String sql = "select deptno from emp group by deptno";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 1);
+    expected.put(Aggregate.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountAggregateGroupingSets() {
+    final String sql = "select deptno from emp\n"
+        + "group by grouping sets ((deptno), (ename, deptno))";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 3);
+    expected.put(Aggregate.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountAggregateEmptyKeyOnEmptyTable() {
+    final String sql = "select count(*) from (select * from emp limit 0)";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 2);
+    expected.put(Aggregate.class, 1);
+    expected.put(Sort.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
+  @Test public void testNodeTypeCountFilterAggregateEmptyKey() {
+    final String sql = "select count(*) from emp where 1 = 0";
+    final Map<Class<? extends RelNode>, Integer> expected = new HashMap<>();
+    expected.put(TableScan.class, 1);
+    expected.put(Project.class, 1);
+    expected.put(Filter.class, 1);
+    expected.put(Aggregate.class, 1);
+    checkNodeTypeCount(sql, expected);
+  }
+
   /**
    * Matcher that succeeds for any collection that, when converted to strings
    * and sorted on those strings, matches the given reference string.

http://git-wip-us.apache.org/repos/asf/calcite/blob/41b05d78/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 2314612..dd3861b 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -6593,7 +6593,7 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$
         <Resource name="planAfter">
             <![CDATA[
 LogicalProject(EMPNO=[10], ENAME=[$1], JOB=[$2], MGR=[null], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[7], SLACKER=[$8])
-  LogicalFilter(condition=[AND(=($7, 7), =($0, 10), IS NULL($3))])
+  LogicalFilter(condition=[AND(=($7, 7), IS NULL($3), =($0, 10))])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
         </Resource>