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/05/01 22:41:58 UTC

[1/5] incubator-calcite git commit: [CALCITE-702] Add validator test for monotonic expressions

Repository: incubator-calcite
Updated Branches:
  refs/heads/master e218cb153 -> f076a9bac


[CALCITE-702] Add validator test for monotonic expressions

Recognize that "EXTRACT(YEAR FROM <montonic_expr>)" is monotonic.


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

Branch: refs/heads/master
Commit: 91f0fca610c0ec8964e95a87547e3a941b834aab
Parents: e218cb1
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Apr 27 13:02:53 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Apr 27 13:02:53 2015 -0700

----------------------------------------------------------------------
 .../calcite/sql/fun/SqlExtractFunction.java     |  14 ++
 .../org/apache/calcite/sql/test/SqlTester.java  |  11 +-
 .../apache/calcite/sql/test/SqlTesterImpl.java  |  22 ++-
 .../apache/calcite/test/SqlValidatorTest.java   |  86 ++++++++++
 .../calcite/test/SqlValidatorTestCase.java      | 169 +++++++++----------
 5 files changed, 210 insertions(+), 92 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/91f0fca6/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java
index 1e870c3..86c6893 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlExtractFunction.java
@@ -19,10 +19,13 @@ package org.apache.calcite.sql.fun;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlFunction;
 import org.apache.calcite.sql.SqlFunctionCategory;
+import org.apache.calcite.sql.SqlIntervalQualifier;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.validate.SqlMonotonicity;
+import org.apache.calcite.sql.validate.SqlValidatorScope;
 import org.apache.calcite.util.Util;
 
 /**
@@ -64,6 +67,17 @@ public class SqlExtractFunction extends SqlFunction {
     call.operand(1).unparse(writer, 0, 0);
     writer.endFunCall(frame);
   }
+
+  @Override public SqlMonotonicity getMonotonicity(SqlCall call,
+      SqlValidatorScope scope) {
+    final SqlIntervalQualifier o = call.operand(0);
+    switch (o.timeUnitRange) {
+    case YEAR:
+      return scope.getMonotonicity(call.operand(1)).unstrict();
+    default:
+      return SqlMonotonicity.NOT_MONOTONIC;
+    }
+  }
 }
 
 // End SqlExtractFunction.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/91f0fca6/core/src/test/java/org/apache/calcite/sql/test/SqlTester.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlTester.java b/core/src/test/java/org/apache/calcite/sql/test/SqlTester.java
index 3a919fc..5c14704 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlTester.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlTester.java
@@ -22,6 +22,7 @@ import org.apache.calcite.config.Lex;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.sql.validate.SqlMonotonicity;
 import org.apache.calcite.test.SqlValidatorTestCase;
 
 import java.io.Closeable;
@@ -46,7 +47,7 @@ public interface SqlTester extends Closeable, SqlValidatorTestCase.Tester {
   /**
    * Name of a virtual machine that can potentially implement an operator.
    */
-  public enum VmName {
+  enum VmName {
     FENNEL, JAVA, EXPAND
   }
 
@@ -269,6 +270,14 @@ public interface SqlTester extends Closeable, SqlValidatorTestCase.Tester {
       ResultChecker resultChecker);
 
   /**
+   * Tests that the first column of a SQL query has a given monotonicity.
+   *
+   * @param expectedMonotonicity Expected monotonicity
+   * @param query                SQL query
+   */
+  void checkMonotonic(String query, SqlMonotonicity expectedMonotonicity);
+
+  /**
    * Declares that this test is for a given operator. So we can check that all
    * operators are tested.
    *

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/91f0fca6/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java b/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
index 36c1228..98741f4 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
@@ -40,6 +40,7 @@ import org.apache.calcite.sql.util.SqlShuttle;
 import org.apache.calcite.sql.validate.SqlConformance;
 import org.apache.calcite.sql.validate.SqlMonotonicity;
 import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorNamespace;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
 import org.apache.calcite.test.SqlValidatorTestCase;
 import org.apache.calcite.util.Pair;
@@ -60,8 +61,10 @@ import java.util.NoSuchElementException;
 
 import static org.apache.calcite.sql.SqlUtil.stripAs;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
 /**
@@ -447,6 +450,18 @@ public class SqlTesterImpl implements SqlTester {
     typeChecker.checkType(actualType);
   }
 
+  public void checkMonotonic(String query,
+      SqlMonotonicity expectedMonotonicity) {
+    SqlValidator validator = getValidator();
+    SqlNode n = parseAndValidate(validator, query);
+    final RelDataType rowType = validator.getValidatedNodeType(n);
+    final SqlValidatorNamespace selectNamespace = validator.getNamespace(n);
+    final String field0 = rowType.getFieldList().get(0).getName();
+    final SqlMonotonicity monotonicity =
+        selectNamespace.getMonotonicity(field0);
+    assertThat(monotonicity, equalTo(expectedMonotonicity));
+  }
+
   public void checkRewrite(
       SqlValidator validator,
       String query,
@@ -529,7 +544,7 @@ public class SqlTesterImpl implements SqlTester {
     } catch (SqlParseException e) {
       throw new RuntimeException(e);
     }
-    final Collection<SqlNode> literalSet = new LinkedHashSet<SqlNode>();
+    final Collection<SqlNode> literalSet = new LinkedHashSet<>();
     x.accept(
         new SqlShuttle() {
           private final List<SqlOperator> ops =
@@ -569,7 +584,7 @@ public class SqlTesterImpl implements SqlTester {
                 == SqlTypeName.NULL;
           }
         });
-    final List<SqlNode> nodes = new ArrayList<SqlNode>(literalSet);
+    final List<SqlNode> nodes = new ArrayList<>(literalSet);
     Collections.sort(
         nodes,
         new Comparator<SqlNode>() {
@@ -586,8 +601,7 @@ public class SqlTesterImpl implements SqlTester {
           }
         });
     String sql2 = sql;
-    final List<Pair<String, String>> values =
-        new ArrayList<Pair<String, String>>();
+    final List<Pair<String, String>> values = new ArrayList<>();
     int p = 0;
     for (SqlNode literal : nodes) {
       final SqlParserPos pos = literal.getParserPosition();

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/91f0fca6/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index a24e16a..aa097d9 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -27,6 +27,7 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.test.SqlTester;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.sql.validate.SqlMonotonicity;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.Bug;
@@ -7118,6 +7119,91 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         .fails(STR_AGG_REQUIRES_MONO);
   }
 
+  /** Tests that various expressions are monotonic. */
+  @Test public void testMonotonic() {
+    sql("select stream floor(rowtime to hour) from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream ceil(rowtime to minute) from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream extract(minute from rowtime) from orders")
+        .monotonic(SqlMonotonicity.NOT_MONOTONIC);
+    sql("select stream (rowtime - timestamp '1970-01-01 00:00:00') hour from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream\n"
+        + "cast((rowtime - timestamp '1970-01-01 00:00:00') hour as integer)\n"
+        + "from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream\n"
+        + "cast((rowtime - timestamp '1970-01-01 00:00:00') hour as integer) / 15\n"
+        + "from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream\n"
+        + "mod(cast((rowtime - timestamp '1970-01-01 00:00:00') hour as integer), 15)\n"
+        + "from orders")
+        .monotonic(SqlMonotonicity.NOT_MONOTONIC);
+
+    // constant
+    sql("select stream 1 - 2 from orders")
+        .monotonic(SqlMonotonicity.CONSTANT);
+    sql("select stream 1 + 2 from orders")
+        .monotonic(SqlMonotonicity.CONSTANT);
+
+    // extract(YEAR) is monotonic, extract(other time unit) is not
+    sql("select stream extract(year from rowtime) from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream extract(month from rowtime) from orders")
+        .monotonic(SqlMonotonicity.NOT_MONOTONIC);
+
+    // <monotonic> - constant
+    sql("select stream extract(year from rowtime) - 3 from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream extract(year from rowtime) * 5 from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream extract(year from rowtime) * -5 from orders")
+        .monotonic(SqlMonotonicity.DECREASING);
+
+    // <monotonic> / constant
+    sql("select stream extract(year from rowtime) / -5 from orders")
+        .monotonic(SqlMonotonicity.DECREASING);
+    sql("select stream extract(year from rowtime) / 5 from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream extract(year from rowtime) / 0 from orders")
+        .monotonic(SqlMonotonicity.CONSTANT); // +inf is constant!
+
+    // constant / <monotonic> is not monotonic (we don't know whether sign of
+    // expression ever changes)
+    sql("select stream 5 / extract(year from rowtime) from orders")
+        .monotonic(SqlMonotonicity.NOT_MONOTONIC);
+
+    // <monotonic> * constant
+    sql("select stream extract(year from rowtime) * -5 from orders")
+        .monotonic(SqlMonotonicity.DECREASING);
+    sql("select stream extract(year from rowtime) * 5 from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream extract(year from rowtime) * 0 from orders")
+        .monotonic(SqlMonotonicity.CONSTANT); // 0 is constant!
+
+    // constant * <monotonic>
+    sql("select stream -5 * extract(year from rowtime) from orders")
+        .monotonic(SqlMonotonicity.DECREASING);
+    sql("select stream 5 * extract(year from rowtime) from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+    sql("select stream 0 * extract(year from rowtime) from orders")
+        .monotonic(SqlMonotonicity.CONSTANT);
+
+    // <monotonic> - <monotonic>
+    sql("select stream\n"
+        + "extract(year from rowtime) - extract(year from rowtime)\n"
+        + "from orders")
+        .monotonic(SqlMonotonicity.NOT_MONOTONIC);
+
+    // <monotonic> + <monotonic>
+    sql("select stream\n"
+        + "extract(year from rowtime) + extract(year from rowtime)\n"
+        + "from orders")
+        .monotonic(SqlMonotonicity.INCREASING);
+  }
+
   @Test public void testStreamUnionAll() {
     sql("select orderId\n"
         + "from ^orders^\n"

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/91f0fca6/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java
index 039cfe8..ff87245 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTestCase.java
@@ -314,97 +314,87 @@ public class SqlValidatorTestCase {
     }
 
     if (null == expectedMsgPattern) {
-      if (null != actualException) {
-        actualException.printStackTrace();
-        fail(
-            "Validator threw unexpected exception"
-            + "; query [" + sap.sql
-            + "]; exception [" + actualMessage
-            + "]; class [" + actualException.getClass()
-            + "]; pos [line " + actualLine
-            + " col " + actualColumn
-            + " thru line " + actualLine
-            + " col " + actualColumn + "]");
+      actualException.printStackTrace();
+      fail("Validator threw unexpected exception"
+          + "; query [" + sap.sql
+          + "]; exception [" + actualMessage
+          + "]; class [" + actualException.getClass()
+          + "]; pos [line " + actualLine
+          + " col " + actualColumn
+          + " thru line " + actualLine
+          + " col " + actualColumn + "]");
+    }
+
+    String sqlWithCarets;
+    if (actualColumn <= 0
+        || actualLine <= 0
+        || actualEndColumn <= 0
+        || actualEndLine <= 0) {
+      if (sap.pos != null) {
+        AssertionError e =
+            new AssertionError("Expected error to have position,"
+                + " but actual error did not: "
+                + " actual pos [line " + actualLine
+                + " col " + actualColumn
+                + " thru line " + actualEndLine + " col "
+                + actualEndColumn + "]");
+        e.initCause(actualException);
+        throw e;
       }
+      sqlWithCarets = sap.sql;
     } else {
-      if (null == actualException) {
-        fail(
-            "Expected validator to throw "
-            + "exception, but it did not; query [" + sap.sql
-            + "]; expected [" + expectedMsgPattern + "]");
-      } else {
-        String sqlWithCarets;
-        if ((actualColumn <= 0)
-            || (actualLine <= 0)
-            || (actualEndColumn <= 0)
-            || (actualEndLine <= 0)) {
-          if (sap.pos != null) {
-            AssertionError e =
-                new AssertionError("Expected error to have position,"
-                    + " but actual error did not: "
-                    + " actual pos [line " + actualLine
-                    + " col " + actualColumn
-                    + " thru line " + actualEndLine + " col "
-                    + actualEndColumn + "]");
-            e.initCause(actualException);
-            throw e;
-          }
-          sqlWithCarets = sap.sql;
-        } else {
-          sqlWithCarets =
-              SqlParserUtil.addCarets(
-                  sap.sql,
-                  actualLine,
-                  actualColumn,
-                  actualEndLine,
-                  actualEndColumn + 1);
-          if (sap.pos == null) {
-            throw new AssertionError(
-                "Actual error had a position, but expected error"
-                + " did not. Add error position carets to sql:\n"
-                + sqlWithCarets);
-          }
-        }
-        if (actualMessage != null) {
-          actualMessage = Util.toLinux(actualMessage);
-        }
-        if ((actualMessage == null)
-            || !actualMessage.matches(expectedMsgPattern)) {
-          actualException.printStackTrace();
-          final String actualJavaRegexp =
-              (actualMessage == null) ? "null"
-                  : TestUtil.quoteForJava(
-                      TestUtil.quotePattern(actualMessage));
-          fail("Validator threw different "
-              + "exception than expected; query [" + sap.sql
-              + "];\n"
-              + " expected pattern [" + expectedMsgPattern
-              + "];\n"
-              + " actual [" + actualMessage
-              + "];\n"
-              + " actual as java regexp [" + actualJavaRegexp
-              + "]; pos [" + actualLine
-              + " col " + actualColumn
-              + " thru line " + actualEndLine
-              + " col " + actualEndColumn
-              + "]; sql [" + sqlWithCarets + "]");
-        } else if (
-            (sap.pos != null)
-                && ((actualLine != sap.pos.getLineNum())
-                || (actualColumn != sap.pos.getColumnNum())
-                || (actualEndLine != sap.pos.getEndLineNum())
-                || (actualEndColumn != sap.pos.getEndColumnNum()))) {
-          fail(
-              "Validator threw expected "
-              + "exception [" + actualMessage
-              + "];\nbut at pos [line " + actualLine
-              + " col " + actualColumn
-              + " thru line " + actualEndLine
-              + " col " + actualEndColumn
-              + "];\nsql [" + sqlWithCarets + "]");
-        }
+      sqlWithCarets =
+          SqlParserUtil.addCarets(
+              sap.sql,
+              actualLine,
+              actualColumn,
+              actualEndLine,
+              actualEndColumn + 1);
+      if (sap.pos == null) {
+        throw new AssertionError("Actual error had a position, but expected "
+            + "error did not. Add error position carets to sql:\n"
+            + sqlWithCarets);
       }
     }
+
+    if (actualMessage != null) {
+      actualMessage = Util.toLinux(actualMessage);
+    }
+
+    if (actualMessage == null
+        || !actualMessage.matches(expectedMsgPattern)) {
+      actualException.printStackTrace();
+      final String actualJavaRegexp =
+          (actualMessage == null)
+              ? "null"
+              : TestUtil.quoteForJava(
+                  TestUtil.quotePattern(actualMessage));
+      fail("Validator threw different "
+          + "exception than expected; query [" + sap.sql
+          + "];\n"
+          + " expected pattern [" + expectedMsgPattern
+          + "];\n"
+          + " actual [" + actualMessage
+          + "];\n"
+          + " actual as java regexp [" + actualJavaRegexp
+          + "]; pos [" + actualLine
+          + " col " + actualColumn
+          + " thru line " + actualEndLine
+          + " col " + actualEndColumn
+          + "]; sql [" + sqlWithCarets + "]");
+    } else if (sap.pos != null
+        && (actualLine != sap.pos.getLineNum()
+            || actualColumn != sap.pos.getColumnNum()
+            || actualEndLine != sap.pos.getEndLineNum()
+            || actualEndColumn != sap.pos.getEndColumnNum())) {
+      fail("Validator threw expected "
+          + "exception [" + actualMessage
+          + "];\nbut at pos [line " + actualLine
+          + " col " + actualColumn
+          + " thru line " + actualEndLine
+          + " col " + actualEndColumn
+          + "];\nsql [" + sqlWithCarets + "]");
+    }
   }
 
   //~ Inner Interfaces -------------------------------------------------------
@@ -565,6 +555,11 @@ public class SqlValidatorTestCase {
       tester.checkResultType(sql, expectedType);
       return this;
     }
+
+    public Sql monotonic(SqlMonotonicity expectedMonotonicity) {
+      tester.checkMonotonic(sql, expectedMonotonicity);
+      return this;
+    }
   }
 }
 


[2/5] incubator-calcite git commit: [CALCITE-704] FILTER clause for aggregate functions

Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 1b2139b..7bc58be 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -198,9 +198,8 @@ public class SqlToRelConverter {
   protected final RelOptCluster cluster;
   private DefaultValueFactory defaultValueFactory;
   private SubqueryConverter subqueryConverter;
-  protected final List<RelNode> leaves = new ArrayList<RelNode>();
-  private final List<SqlDynamicParam> dynamicParamSqlNodes =
-      new ArrayList<SqlDynamicParam>();
+  protected final List<RelNode> leaves = new ArrayList<>();
+  private final List<SqlDynamicParam> dynamicParamSqlNodes = new ArrayList<>();
   private final SqlOperatorTable opTab;
   private boolean shouldConvertTableAccess;
   protected final RelDataTypeFactory typeFactory;
@@ -215,7 +214,7 @@ public class SqlToRelConverter {
    * Fields used in name resolution for correlated subqueries.
    */
   private final Map<String, DeferredLookup> mapCorrelToDeferred =
-      new HashMap<String, DeferredLookup>();
+      new HashMap<>();
   private int nextCorrel = 0;
 
   private static final String CORREL_PREFIX = "$cor";
@@ -224,7 +223,7 @@ public class SqlToRelConverter {
    * Stack of names of datasets requested by the <code>
    * TABLE(SAMPLE(&lt;datasetName&gt;, &lt;query&gt;))</code> construct.
    */
-  private final Stack<String> datasetStack = new Stack<String>();
+  private final Stack<String> datasetStack = new Stack<>();
 
   /**
    * Mapping of non-correlated subqueries that have been converted to their
@@ -232,7 +231,7 @@ public class SqlToRelConverter {
    * already been evaluated.
    */
   private final Map<SqlNode, RexNode> mapConvertedNonCorrSubqs =
-      new HashMap<SqlNode, RexNode>();
+      new HashMap<>();
 
   public final RelOptTable.ViewExpander viewExpander;
 
@@ -612,9 +611,8 @@ public class SqlToRelConverter {
         bb,
         select.getWhere());
 
-    List<SqlNode> orderExprList = new ArrayList<SqlNode>();
-    List<RelFieldCollation> collationList =
-        new ArrayList<RelFieldCollation>();
+    final List<SqlNode> orderExprList = new ArrayList<>();
+    final List<RelFieldCollation> collationList = new ArrayList<>();
     gatherOrderExprs(
         bb,
         select,
@@ -668,7 +666,7 @@ public class SqlToRelConverter {
     if (checkForDupExprs && (rel instanceof LogicalProject)) {
       LogicalProject project = (LogicalProject) rel;
       final List<RexNode> projectExprs = project.getProjects();
-      List<Integer> origins = new ArrayList<Integer>();
+      final List<Integer> origins = new ArrayList<>();
       int dupCount = 0;
       for (int i = 0; i < projectExprs.size(); i++) {
         int x = findExpr(projectExprs.get(i), projectExprs, i);
@@ -782,7 +780,7 @@ public class SqlToRelConverter {
     // If extra expressions were added to the project list for sorting,
     // add another project to remove them.
     if (orderExprList.size() > 0) {
-      List<RexNode> exprs = new ArrayList<RexNode>();
+      final List<RexNode> exprs = new ArrayList<>();
       final RelDataType rowType = bb.root.getRowType();
       final int fieldCount =
           rowType.getFieldCount() - orderExprList.size();
@@ -1044,10 +1042,10 @@ public class SqlToRelConverter {
         LogicalAggregate aggregate =
             LogicalAggregate.create(seek, false, ImmutableBitSet.of(), null,
                 ImmutableList.of(
-                    new AggregateCall(SqlStdOperatorTable.COUNT, false,
-                        ImmutableList.<Integer>of(), longType, null),
-                    new AggregateCall(SqlStdOperatorTable.COUNT, false,
-                        args, longType, null)));
+                    AggregateCall.create(SqlStdOperatorTable.COUNT, false,
+                        ImmutableList.<Integer>of(), -1, longType, null),
+                    AggregateCall.create(SqlStdOperatorTable.COUNT, false,
+                        args, -1, longType, null)));
         LogicalJoin join =
             LogicalJoin.create(bb.root,
                 aggregate,
@@ -1311,7 +1309,7 @@ public class SqlToRelConverter {
       final List<RexNode> leftKeys,
       SqlNodeList valuesList,
       boolean isNotIn) {
-    List<RexNode> comparisons = new ArrayList<RexNode>();
+    final List<RexNode> comparisons = new ArrayList<>();
     for (SqlNode rightVals : valuesList) {
       RexNode rexComparison;
       if (leftKeys.size() == 1) {
@@ -1451,7 +1449,7 @@ public class SqlToRelConverter {
               null);
     }
 
-    List<RelNode> unionInputs = new ArrayList<RelNode>();
+    final List<RelNode> unionInputs = new ArrayList<>();
     for (SqlNode node : rows) {
       SqlBasicCall call;
       if (isRowConstructor(node)) {
@@ -1702,8 +1700,7 @@ public class SqlToRelConverter {
   public RexNode convertExpression(
       SqlNode node,
       Map<String, RexNode> nameToNodeMap) {
-    final Map<String, RelDataType> nameToTypeMap =
-        new HashMap<String, RelDataType>();
+    final Map<String, RelDataType> nameToTypeMap = new HashMap<>();
     for (Map.Entry<String, RexNode> entry : nameToNodeMap.entrySet()) {
       nameToTypeMap.put(entry.getKey(), entry.getValue().getType());
     }
@@ -2162,8 +2159,8 @@ public class SqlToRelConverter {
       }
     }
 
-    final List<RexNode> extraLeftExprs = new ArrayList<RexNode>();
-    final List<RexNode> extraRightExprs = new ArrayList<RexNode>();
+    final List<RexNode> extraLeftExprs = new ArrayList<>();
+    final List<RexNode> extraRightExprs = new ArrayList<>();
     final int leftCount = leftRel.getRowType().getFieldCount();
     final int rightCount = rightRel.getRowType().getFieldCount();
     if (!containsGet(joinCond)) {
@@ -2187,8 +2184,7 @@ public class SqlToRelConverter {
                     new RexInputRef(index, field.getType()),
                     field.getName());
               } else {
-                return Pair.<RexNode, String>of(
-                    extraLeftExprs.get(index - leftCount), null);
+                return Pair.of(extraLeftExprs.get(index - leftCount), null);
               }
             }
           },
@@ -2274,8 +2270,8 @@ public class SqlToRelConverter {
     case AND:
     case OR:
     case EQUALS:
-      RexCall call = (RexCall) node;
-      List<RexNode> list = new ArrayList<RexNode>();
+      final RexCall call = (RexCall) node;
+      final List<RexNode> list = new ArrayList<>();
       List<RexNode> operands = Lists.newArrayList(call.getOperands());
       for (int i = 0; i < operands.size(); i++) {
         RexNode operand = operands.get(i);
@@ -2426,8 +2422,8 @@ public class SqlToRelConverter {
       bb.setRoot(ImmutableList.of(leftRel, rightRel));
       return bb.convertExpression(condition);
     case USING:
-      SqlNodeList list = (SqlNodeList) condition;
-      List<String> nameList = new ArrayList<String>();
+      final SqlNodeList list = (SqlNodeList) condition;
+      final List<String> nameList = new ArrayList<>();
       for (SqlNode columnName : list) {
         final SqlIdentifier id = (SqlIdentifier) columnName;
         String name = id.getSimple();
@@ -2691,6 +2687,7 @@ public class SqlToRelConverter {
       // (yet) appear in the validator type.
       final SelectScope selectScope =
           SqlValidatorUtil.getEnclosingSelectScope(bb.scope);
+      assert selectScope != null;
       final SqlValidatorNamespace selectNamespace =
           validator.getNamespace(selectScope.getNode());
       final List<String> names =
@@ -3054,8 +3051,8 @@ public class SqlToRelConverter {
     RelDataType sourceRowType = sourceRel.getRowType();
     final RexNode sourceRef =
         rexBuilder.makeRangeReference(sourceRowType, 0, false);
-    final List<String> targetColumnNames = new ArrayList<String>();
-    final List<RexNode> columnExprs = new ArrayList<RexNode>();
+    final List<String> targetColumnNames = new ArrayList<>();
+    final List<RexNode> columnExprs = new ArrayList<>();
     collectInsertTargets(call, sourceRef, targetColumnNames, columnExprs);
 
     final RelOptTable targetTable = getTargetTable(call);
@@ -3063,10 +3060,10 @@ public class SqlToRelConverter {
     final List<RelDataTypeField> targetFields =
         targetRowType.getFieldList();
     final List<RexNode> sourceExps =
-        new ArrayList<RexNode>(
+        new ArrayList<>(
             Collections.<RexNode>nCopies(targetFields.size(), null));
     final List<String> fieldNames =
-        new ArrayList<String>(
+        new ArrayList<>(
             Collections.<String>nCopies(targetFields.size(), null));
 
     // Walk the name list and place the associated value in the
@@ -3155,7 +3152,7 @@ public class SqlToRelConverter {
     RelOptTable targetTable = getTargetTable(call);
 
     // convert update column list from SqlIdentifier to String
-    List<String> targetColumnNameList = new ArrayList<String>();
+    final List<String> targetColumnNameList = new ArrayList<>();
     for (SqlNode node : call.getTargetColumnList()) {
       SqlIdentifier id = (SqlIdentifier) node;
       String name = id.getSimple();
@@ -3172,7 +3169,7 @@ public class SqlToRelConverter {
     RelOptTable targetTable = getTargetTable(call);
 
     // convert update column list from SqlIdentifier to String
-    List<String> targetColumnNameList = new ArrayList<String>();
+    final List<String> targetColumnNameList = new ArrayList<>();
     SqlUpdate updateCall = call.getUpdateCall();
     if (updateCall != null) {
       for (SqlNode targetColumn : updateCall.getTargetColumnList()) {
@@ -3221,7 +3218,7 @@ public class SqlToRelConverter {
 
     LogicalJoin join = (LogicalJoin) mergeSourceRel.getInput(0);
     int nSourceFields = join.getLeft().getRowType().getFieldCount();
-    List<RexNode> projects = new ArrayList<RexNode>();
+    final List<RexNode> projects = new ArrayList<>();
     for (int level1Idx = 0; level1Idx < nLevel1Exprs; level1Idx++) {
       if ((level2InsertExprs != null)
           && (level1InsertExprs.get(level1Idx) instanceof RexInputRef)) {
@@ -3347,8 +3344,8 @@ public class SqlToRelConverter {
       Blackboard bb) {
     // NOTE: Wael 2/04/05: this implementation is not the most efficient in
     // terms of planning since it generates XOs that can be reduced.
-    List<Object> joinList = new ArrayList<Object>();
-    List<SqlNode> lastList = new ArrayList<SqlNode>();
+    final List<Object> joinList = new ArrayList<>();
+    List<SqlNode> lastList = new ArrayList<>();
     for (int i = 0; i < operands.size(); i++) {
       SqlNode operand = operands.get(i);
       if (!(operand instanceof SqlCall)) {
@@ -3395,7 +3392,7 @@ public class SqlToRelConverter {
       if (lastList.size() > 0) {
         joinList.add(lastList);
       }
-      lastList = new ArrayList<SqlNode>();
+      lastList = new ArrayList<>();
       Collect collect =
           new Collect(
               cluster,
@@ -3413,8 +3410,8 @@ public class SqlToRelConverter {
       Object o = joinList.get(i);
       if (o instanceof List) {
         List<SqlNode> projectList = (List<SqlNode>) o;
-        final List<RexNode> selectList = new ArrayList<RexNode>();
-        final List<String> fieldNameList = new ArrayList<String>();
+        final List<RexNode> selectList = new ArrayList<>();
+        final List<String> fieldNameList = new ArrayList<>();
         for (int j = 0; j < projectList.size(); j++) {
           SqlNode operand = projectList.get(j);
           selectList.add(bb.convertExpression(operand));
@@ -3485,14 +3482,13 @@ public class SqlToRelConverter {
 
     replaceSubqueries(bb, selectList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
 
-    List<String> fieldNames = new ArrayList<String>();
-    List<RexNode> exprs = new ArrayList<RexNode>();
-    Collection<String> aliases = new TreeSet<String>();
+    List<String> fieldNames = new ArrayList<>();
+    final List<RexNode> exprs = new ArrayList<>();
+    final Collection<String> aliases = new TreeSet<>();
 
     // Project any system fields. (Must be done before regular select items,
     // because offsets may be affected.)
-    final List<SqlMonotonicity> columnMonotonicityList =
-        new ArrayList<SqlMonotonicity>();
+    final List<SqlMonotonicity> columnMonotonicityList = new ArrayList<>();
     extraSelectItems(
         bb,
         select,
@@ -3618,14 +3614,13 @@ public class SqlToRelConverter {
       return;
     }
 
-    List<RelNode> unionRels = new ArrayList<RelNode>();
+    final List<RelNode> unionRels = new ArrayList<>();
     for (SqlNode rowConstructor1 : values.getOperandList()) {
       SqlCall rowConstructor = (SqlCall) rowConstructor1;
       Blackboard tmpBb = createBlackboard(bb.scope, null);
       replaceSubqueries(tmpBb, rowConstructor,
           RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
-      List<Pair<RexNode, String>> exps =
-          new ArrayList<Pair<RexNode, String>>();
+      final List<Pair<RexNode, String>> exps = new ArrayList<>();
       for (Ord<SqlNode> operand : Ord.zip(rowConstructor.getOperandList())) {
         exps.add(
             Pair.of(
@@ -3686,7 +3681,7 @@ public class SqlToRelConverter {
     public RelNode root;
     private List<RelNode> inputs;
     private final Map<String, RexNode> mapCorrelateVariableToRexNode =
-        new HashMap<String, RexNode>();
+        new HashMap<>();
 
     List<RelNode> cursors;
 
@@ -3718,14 +3713,12 @@ public class SqlToRelConverter {
      * "right" to the subquery.
      */
     private final Map<RelNode, Map<Integer, Integer>>
-    mapRootRelToFieldProjection =
-        new HashMap<RelNode, Map<Integer, Integer>>();
+    mapRootRelToFieldProjection = new HashMap<>();
 
     private final List<SqlMonotonicity> columnMonotonicities =
-        new ArrayList<SqlMonotonicity>();
+        new ArrayList<>();
 
-    private final List<RelDataTypeField> systemFieldList =
-        new ArrayList<RelDataTypeField>();
+    private final List<RelDataTypeField> systemFieldList = new ArrayList<>();
 
     /**
      * Creates a Blackboard.
@@ -3742,7 +3735,7 @@ public class SqlToRelConverter {
         Map<String, RexNode> nameToNodeMap) {
       this.scope = scope;
       this.nameToNodeMap = nameToNodeMap;
-      this.cursors = new ArrayList<RelNode>();
+      this.cursors = new ArrayList<>();
       subqueryNeedsOuterJoin = false;
     }
 
@@ -4216,7 +4209,7 @@ public class SqlToRelConverter {
     public RexNode visit(SqlCall call) {
       if (agg != null) {
         final SqlOperator op = call.getOperator();
-        if (op.isAggregator()) {
+        if (op.isAggregator() || op.getKind() == SqlKind.FILTER) {
           return agg.lookupAggregates(call);
         }
       }
@@ -4469,78 +4462,94 @@ public class SqlToRelConverter {
     }
 
     public Void visit(SqlCall call) {
-      if (call.getOperator().isAggregator()) {
-        assert bb.agg == this;
-        List<Integer> args = new ArrayList<Integer>();
-        List<RelDataType> argTypes =
-            call.getOperator() instanceof SqlCountAggFunction
-            ? new ArrayList<RelDataType>(call.getOperandList().size())
-            : null;
-        try {
-          // switch out of agg mode
-          bb.agg = null;
-          for (SqlNode operand : call.getOperandList()) {
-            RexNode convertedExpr;
-
-            // special case for COUNT(*):  delete the *
-            if (operand instanceof SqlIdentifier) {
-              SqlIdentifier id = (SqlIdentifier) operand;
-              if (id.isStar()) {
-                assert call.operandCount() == 1;
-                assert args.isEmpty();
-                break;
-              }
-            }
-            convertedExpr = bb.convertExpression(operand);
-            assert convertedExpr != null;
-            if (argTypes != null) {
-              argTypes.add(convertedExpr.getType());
-            }
-            args.add(lookupOrCreateGroupExpr(convertedExpr));
-          }
-        } finally {
-          // switch back into agg mode
-          bb.agg = this;
-        }
-
-        final SqlAggFunction aggFunction =
-            (SqlAggFunction) call.getOperator();
-        RelDataType type = validator.deriveType(bb.scope, call);
-        boolean distinct = false;
-        SqlLiteral quantifier = call.getFunctionQuantifier();
-        if ((null != quantifier)
-            && (quantifier.getValue() == SqlSelectKeyword.DISTINCT)) {
-          distinct = true;
-        }
-        final AggregateCall aggCall =
-            new AggregateCall(
-                aggFunction,
-                distinct,
-                args,
-                type,
-                nameMap.get(call.toString()));
-        RexNode rex =
-            rexBuilder.addAggCall(
-                aggCall,
-                groupExprs.size(),
-                aggregatingSelectScope.indicator,
-                aggCalls,
-                aggCallMapping,
-                argTypes);
-        aggMapping.put(call, rex);
-      } else if (call instanceof SqlSelect) {
+      switch (call.getKind()) {
+      case FILTER:
+        translateAgg((SqlCall) call.operand(0), call.operand(1), call);
+        return null;
+      case SELECT:
         // rchen 2006-10-17:
         // for now do not detect aggregates in subqueries.
         return null;
-      } else {
+      }
+      if (call.getOperator().isAggregator()) {
+        translateAgg(call, null, call);
+        return null;
+      }
+      for (SqlNode operand : call.getOperandList()) {
+        // Operands are occasionally null, e.g. switched CASE arg 0.
+        if (operand != null) {
+          operand.accept(this);
+        }
+      }
+      return null;
+    }
+
+    private void translateAgg(SqlCall call, SqlNode filter, SqlCall outerCall) {
+      assert bb.agg == this;
+      final List<Integer> args = new ArrayList<>();
+      int filterArg = -1;
+      final List<RelDataType> argTypes =
+          call.getOperator() instanceof SqlCountAggFunction
+              ? new ArrayList<RelDataType>(call.getOperandList().size())
+              : null;
+      try {
+        // switch out of agg mode
+        bb.agg = null;
         for (SqlNode operand : call.getOperandList()) {
-          // Operands are occasionally null, e.g. switched CASE arg 0.
-          if (operand != null) {
-            operand.accept(this);
+
+          // special case for COUNT(*):  delete the *
+          if (operand instanceof SqlIdentifier) {
+            SqlIdentifier id = (SqlIdentifier) operand;
+            if (id.isStar()) {
+              assert call.operandCount() == 1;
+              assert args.isEmpty();
+              break;
+            }
+          }
+          RexNode convertedExpr = bb.convertExpression(operand);
+          assert convertedExpr != null;
+          if (argTypes != null) {
+            argTypes.add(convertedExpr.getType());
           }
+          args.add(lookupOrCreateGroupExpr(convertedExpr));
         }
+
+        if (filter != null) {
+          RexNode convertedExpr = bb.convertExpression(filter);
+          assert convertedExpr != null;
+          filterArg = lookupOrCreateGroupExpr(convertedExpr);
+        }
+      } finally {
+        // switch back into agg mode
+        bb.agg = this;
       }
-      return null;
+
+      final SqlAggFunction aggFunction =
+          (SqlAggFunction) call.getOperator();
+      RelDataType type = validator.deriveType(bb.scope, call);
+      boolean distinct = false;
+      SqlLiteral quantifier = call.getFunctionQuantifier();
+      if ((null != quantifier)
+          && (quantifier.getValue() == SqlSelectKeyword.DISTINCT)) {
+        distinct = true;
+      }
+      final AggregateCall aggCall =
+          AggregateCall.create(
+              aggFunction,
+              distinct,
+              args,
+              filterArg,
+              type,
+              nameMap.get(outerCall.toString()));
+      RexNode rex =
+          rexBuilder.addAggCall(
+              aggCall,
+              groupExprs.size(),
+              aggregatingSelectScope.indicator,
+              aggCalls,
+              aggCallMapping,
+              argTypes);
+      aggMapping.put(outerCall, rex);
     }
 
     private int lookupOrCreateGroupExpr(RexNode expr) {
@@ -4661,7 +4670,7 @@ public class SqlToRelConverter {
    */
   private static class LookupContext {
     private final List<Pair<RelNode, Integer>> relOffsetList =
-        new ArrayList<Pair<RelNode, Integer>>();
+        new ArrayList<>();
 
     /**
      * Creates a LookupContext with multiple input relational expressions.
@@ -4768,7 +4777,7 @@ public class SqlToRelConverter {
         // Replace original expression with CAST of not one
         // of the supported types
         if (histogramType != type) {
-          exprs = new ArrayList<RexNode>(exprs);
+          exprs = new ArrayList<>(exprs);
           exprs.set(
               0,
               reinterpretCast

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/util/Bug.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Bug.java b/core/src/main/java/org/apache/calcite/util/Bug.java
index a0ced22..a1309ab 100644
--- a/core/src/main/java/org/apache/calcite/util/Bug.java
+++ b/core/src/main/java/org/apache/calcite/util/Bug.java
@@ -156,9 +156,13 @@ public abstract class Bug {
    * Table aliases should follow case-sensitivity policy</a> is fixed. */
   public static final boolean CALCITE_319_FIXED = false;
 
-  /** Whether
+  /** Whether the remaining issues raised in
    * <a href="https://issues.apache.org/jira/browse/CALCITE-461">[CALCITE-461]
-   * Convert more planner rules to handle grouping sets</a> is fixed. */
+   * Convert more planner rules to handle grouping sets</a> are fixed.
+   *
+   * <p>Now that [CALCITE-461] is fixed, the tracking bug is
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-574">[CALCITE-574]
+   * Remove org.apache.calcite.util.Bug.CALCITE_461_FIXED</a>. */
   public static final boolean CALCITE_461_FIXED = false;
 
   /** Whether

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java b/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java
index 385df5b..556f090 100644
--- a/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java
+++ b/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java
@@ -626,9 +626,7 @@ public abstract class Mappings {
             int target = mapping.getTargetOpt(source);
             return target < 0 ? null : target + offset;
           }
-        },
-        mapping.getSourceCount(),
-        targetCount);
+        }, mapping.getSourceCount(), targetCount);
   }
 
   /**
@@ -713,6 +711,12 @@ public abstract class Mappings {
     };
   }
 
+  /** Applies a mapping to an optional integer, returning an optional
+   * result. */
+  public static int apply(TargetMapping mapping, int i) {
+    return i < 0 ? i : mapping.getTarget(i);
+  }
+
   //~ Inner Interfaces -------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
----------------------------------------------------------------------
diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 60f14ba..e2321bb 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -94,10 +94,12 @@ AggregateIllegalInClause=Aggregate expression is illegal in {0} clause
 WindowedAggregateIllegalInClause=Windowed aggregate expression is illegal in {0} clause
 AggregateIllegalInGroupBy=Aggregate expression is illegal in GROUP BY clause
 NestedAggIllegal=Aggregate expressions cannot be nested
+AggregateInFilterIllegal=FILTER must not contain aggregate expression
 AggregateIllegalInOrderBy=Aggregate expression is illegal in ORDER BY clause of non-aggregating SELECT
 CondMustBeBoolean={0} clause must be a condition
 HavingMustBeBoolean=HAVING clause must be a condition
 OverNonAggregate=OVER must be applied to aggregate function
+FilterNonAggregate=FILTER must be applied to aggregate function
 CannotOverrideWindowAttribute=Cannot override window attribute
 ColumnCountMismatchInSetop=Column count mismatch in {0}
 ColumnTypeMismatchInSetop=Type mismatch in column {0,number} of {1}

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
index f5dd8a2..cff82e9 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -139,11 +139,12 @@ public class RelWriterTest {
                     LogicalAggregate.create(filter, false,
                         ImmutableBitSet.of(0), null,
                         ImmutableList.of(
-                            new AggregateCall(SqlStdOperatorTable.COUNT,
-                                true, ImmutableList.of(1), bigIntType, "c"),
-                            new AggregateCall(SqlStdOperatorTable.COUNT,
-                                false, ImmutableList.<Integer>of(), bigIntType,
-                                "d")));
+                            AggregateCall.create(SqlStdOperatorTable.COUNT,
+                                true, ImmutableList.of(1), -1, bigIntType,
+                                "c"),
+                            AggregateCall.create(SqlStdOperatorTable.COUNT,
+                                false, ImmutableList.<Integer>of(), -1,
+                                bigIntType, "d")));
                 aggregate.explain(writer);
                 return writer.asString();
               }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java b/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
index e5731f4..8acd917 100644
--- a/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
@@ -163,9 +163,8 @@ public class TraitPropagationTest {
           .build());
 
       // aggregate on s, count
-      AggregateCall aggCall = new AggregateCall(SqlStdOperatorTable.COUNT,
-          false, Collections.singletonList(1),
-          sqlBigInt, "cnt");
+      AggregateCall aggCall = AggregateCall.create(SqlStdOperatorTable.COUNT,
+          false, Collections.singletonList(1), -1, sqlBigInt, "cnt");
       RelNode agg = new LogicalAggregate(cluster,
           cluster.traitSetOf(Convention.NONE), project, false,
           ImmutableBitSet.of(0), null, Collections.singletonList(aggCall));

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 11eaf7c..a04b5dd 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -719,6 +719,17 @@ public class SqlParserTest {
     checkExp("ln(power(2,2))", "LN(POWER(2, 2))");
   }
 
+  @Test public void testAggregateFilter() {
+    sql("select sum(sal) filter (where gender = 'F') as femaleSal,\n"
+        + " sum(sal) filter (where true) allSal,\n"
+        + " count(distinct deptno) filter (where (deptno < 40))\n"
+        + "from emp")
+        .ok("SELECT (SUM(`SAL`) FILTER (WHERE (`GENDER` = 'F'))) AS `FEMALESAL`,"
+                + " (SUM(`SAL`) FILTER (WHERE TRUE)) AS `ALLSAL`,"
+                + " (COUNT(DISTINCT `DEPTNO`) FILTER (WHERE (`DEPTNO` < 40)))\n"
+                + "FROM `EMP`");
+  }
+
   @Test public void testGroup() {
     check(
         "select deptno, min(foo) as x from emp group by deptno, gender",

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index c5a75ca..678f053 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -2590,6 +2590,31 @@ public class JdbcTest {
         .returns("c0=1997; m0=85452\n");
   }
 
+  @Test public void testAggregateFilter() {
+    final String s = "select \"the_month\",\n"
+        + " count(*) as \"c\",\n"
+        + " count(*) filter (where \"day_of_month\" > 20) as \"c2\"\n"
+        + "from \"time_by_day\" as \"time_by_day\"\n"
+        + "where \"time_by_day\".\"the_year\" = 1997\n"
+        + "group by \"time_by_day\".\"the_month\"\n"
+        + "order by \"time_by_day\".\"the_month\"";
+    CalciteAssert.that()
+        .with(CalciteAssert.Config.FOODMART_CLONE)
+        .query(s)
+        .returns("the_month=April; c=30; c2=10\n"
+                + "the_month=August; c=31; c2=11\n"
+                + "the_month=December; c=31; c2=11\n"
+                + "the_month=February; c=28; c2=8\n"
+                + "the_month=January; c=31; c2=11\n"
+                + "the_month=July; c=31; c2=11\n"
+                + "the_month=June; c=30; c2=10\n"
+                + "the_month=March; c=31; c2=11\n"
+                + "the_month=May; c=31; c2=11\n"
+                + "the_month=November; c=30; c2=10\n"
+                + "the_month=October; c=31; c2=11\n"
+                + "the_month=September; c=30; c2=10\n");
+  }
+
   /** Tests a simple IN query implemented as a semi-join. */
   @Test public void testSimpleIn() {
     CalciteAssert.hr()
@@ -5227,6 +5252,52 @@ public class JdbcTest {
         .withDefaultSchema("adhoc");
   }
 
+  /** Tests user-defined aggregate function with FILTER.
+   *
+   * <p>Also tests that we do not try to push ADAF to JDBC source. */
+  @Test public void testUserDefinedAggregateFunctionWithFilter() throws Exception {
+    final String sum = MyStaticSumFunction.class.getName();
+    final String sum2 = MySumFunction.class.getName();
+    final CalciteAssert.AssertThat with = CalciteAssert.model("{\n"
+        + "  version: '1.0',\n"
+        + "   schemas: [\n"
+        + SCOTT_SCHEMA
+        + ",\n"
+        + "     {\n"
+        + "       name: 'adhoc',\n"
+        + "       functions: [\n"
+        + "         {\n"
+        + "           name: 'MY_SUM',\n"
+        + "           className: '" + sum + "'\n"
+        + "         },\n"
+        + "         {\n"
+        + "           name: 'MY_SUM2',\n"
+        + "           className: '" + sum2 + "'\n"
+        + "         }\n"
+        + "       ]\n"
+        + "     }\n"
+        + "   ]\n"
+        + "}")
+        .withDefaultSchema("adhoc");
+    with.query("select deptno, \"adhoc\".my_sum(deptno) as p\n"
+            + "from scott.emp\n"
+            + "group by deptno\n")
+        .returns(
+            "DEPTNO=20; P=100\n"
+                + "DEPTNO=10; P=30\n"
+                + "DEPTNO=30; P=180\n");
+
+    with.query("select deptno,\n"
+            + "  \"adhoc\".my_sum(deptno) filter (where job = 'CLERK') as c,\n"
+            + "  \"adhoc\".my_sum(deptno) filter (where job = 'XXX') as x\n"
+            + "from scott.emp\n"
+            + "group by deptno\n")
+        .returns(
+            "DEPTNO=20; C=40; X=0\n"
+                + "DEPTNO=10; C=10; X=0\n"
+                + "DEPTNO=30; C=30; X=0\n");
+  }
+
   /** Tests resolution of functions using schema paths. */
   @Test public void testPath() throws Exception {
     final String name = MyPlusFunction.class.getName();

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index c19ec03..c3a1f40 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -350,6 +350,12 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
         + "group by deptno").ok();
   }
 
+  @Test public void testAggFilter() {
+    sql("select deptno, sum(sal * 2) filter (where empno < 10), count(*) "
+        + "from emp "
+        + "group by deptno").ok();
+  }
+
   @Test public void testSelectDistinct() {
     sql("select distinct sal + 5 from emp").ok();
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index aa097d9..a2e6c20 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -6151,6 +6151,28 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
     check("select sum(empno) from emp order by sum(empno)");
   }
 
+  @Test public void testAggregateFilter() {
+    sql("select sum(empno) filter (where deptno < 10) as s from emp")
+        .type("RecordType(INTEGER S) NOT NULL");
+  }
+
+  @Test public void testAggregateFilterNotBoolean() {
+    sql("select sum(empno) filter (where ^deptno + 10^) from emp")
+        .fails("FILTER clause must be a condition");
+  }
+
+  @Test public void testAggregateFilterInHaving() {
+    sql("select sum(empno) as s from emp\n"
+        + "group by deptno\n"
+        + "having sum(empno) filter (where deptno < 20) > 10")
+        .ok();
+  }
+
+  @Test public void testAggregateFilterContainsAggregate() {
+    sql("select sum(empno) filter (where ^count(*) < 10^) from emp")
+        .fails("FILTER must not contain aggregate expression");
+  }
+
   @Test public void testCorrelatingVariables() {
     // reference to unqualified correlating column
     check("select * from emp where exists (\n"

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 2d85361..7ad5b41 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -2409,4 +2409,16 @@ LogicalAggregate(group=[{0}], EXPR$1=[MIN($1)])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testAggFilter">
+        <Resource name="sql">
+            <![CDATA[select deptno, sum(sal * 2) filter (where empno < 10), count(*) from emp group by deptno]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalAggregate(group=[{0}], EXPR$1=[SUM($1) FILTER $2], EXPR$2=[COUNT()])
+  LogicalProject(DEPTNO=[$7], $f1=[*($5, 2)], $f2=[<($0, 10)])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/test/resources/sql/agg.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/agg.oq b/core/src/test/resources/sql/agg.oq
index c61e10e..c54655e 100644
--- a/core/src/test/resources/sql/agg.oq
+++ b/core/src/test/resources/sql/agg.oq
@@ -553,4 +553,126 @@ from emp group by cube(deptno, gender);
 
 !ok
 
+!use scott
+
+# Aggregate FILTER
+select deptno,
+  sum(sal) filter (where job = 'CLERK') c_sal,
+  sum(sal) filter (where job = 'CLERK' and deptno > 10) c10_sal,
+  max(sal) filter (where job = 'CLERK') as max_c,
+  min(sal) filter (where job = 'CLERK') as min_c,
+  max(sal) filter (where job = 'CLERK')
+    - min(sal) filter (where job = 'CLERK') as range_c,
+  max(sal) filter (where job = 'SALESMAN')
+    - min(sal) filter (where job = 'SALESMAN') as range_m
+from "scott".emp
+group by deptno;
++--------+---------+---------+---------+---------+---------+---------+
+| DEPTNO | C_SAL   | C10_SAL | MAX_C   | MIN_C   | RANGE_C | RANGE_M |
++--------+---------+---------+---------+---------+---------+---------+
+|     10 | 1300.00 |         | 1300.00 | 1300.00 |    0.00 |         |
+|     20 | 1900.00 | 1900.00 | 1100.00 |  800.00 |  300.00 |         |
+|     30 |  950.00 |  950.00 |  950.00 |  950.00 |    0.00 |  350.00 |
++--------+---------+---------+---------+---------+---------+---------+
+(3 rows)
+
+!ok
+
+# Aggregate FILTER on condition in GROUP BY
+select deptno,
+  sum(sal) filter (where deptno = 10) sal_10
+from "scott".emp
+group by deptno;
++--------+---------+
+| DEPTNO | SAL_10  |
++--------+---------+
+|     10 | 8750.00 |
+|     20 |         |
+|     30 |         |
++--------+---------+
+(3 rows)
+
+!ok
+
+# Aggregate FILTER with HAVING
+select deptno
+from "scott".emp
+group by deptno
+having sum(sal) filter (where job = 'CLERK') > 1000;
++--------+
+| DEPTNO |
++--------+
+|     10 |
+|     20 |
++--------+
+(2 rows)
+
+!ok
+
+# Aggregate FILTER with ORDER BY
+select deptno
+from "scott".emp
+group by deptno
+order by sum(sal) filter (where job = 'CLERK');
++--------+
+| DEPTNO |
++--------+
+|     30 |
+|     10 |
+|     20 |
++--------+
+(3 rows)
+
+!ok
+
+# Aggregate FILTER with JOIN
+select dept.deptno,
+  sum(sal) filter (where 1 < 2) as s,
+  sum(sal) as s1,
+  count(*) filter (where emp.ename < dept.dname) as c
+from "scott".emp
+join "scott".dept using (deptno)
+group by dept.deptno;
++--------+----------+----------+---+
+| DEPTNO | S        | S1       | C |
++--------+----------+----------+---+
+|     10 |  8750.00 |  8750.00 | 0 |
+|     20 | 10875.00 | 10875.00 | 3 |
+|     30 |  9400.00 |  9400.00 | 4 |
++--------+----------+----------+---+
+(3 rows)
+
+!ok
+
+# Aggregate FILTER with DISTINCT
+select deptno,
+ count(distinct job) as cdj
+from "scott".emp
+group by deptno;
++--------+-----+
+| DEPTNO | CDJ |
++--------+-----+
+|     10 |   3 |
+|     20 |   3 |
+|     30 |   3 |
++--------+-----+
+(3 rows)
+
+!ok
+
+select deptno,
+ count(distinct job) filter (where job <> 'SALESMAN') as cdj
+from "scott".emp
+group by deptno;
++--------+-----+
+| DEPTNO | CDJ |
++--------+-----+
+|     10 |   3 |
+|     20 |   3 |
+|     30 |   2 |
++--------+-----+
+(3 rows)
+
+!ok
+
 # End agg.oq

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/doc/REFERENCE.md
----------------------------------------------------------------------
diff --git a/doc/REFERENCE.md b/doc/REFERENCE.md
index e0c6278..b67ad57 100644
--- a/doc/REFERENCE.md
+++ b/doc/REFERENCE.md
@@ -477,6 +477,20 @@ Not implemented:
 
 ### Aggregate functions
 
+Syntax:
+
+```SQL
+aggregateCall:
+        agg( [ DISTINCT ] value [, value]* ) [ FILTER ( WHERE condition ) ]
+    |   agg(*) [ FILTER ( WHERE condition ) ]
+```
+
+If `FILTER` is present, the aggregate function only considers rows for which
+*condition* evaluates to TRUE.
+
+If `DISTINCT` is present, duplicate argument values are eliminated before being
+passed to the aggregate function.
+
 | Operator syntax                    | Description
 | ---------------------------------- | -----------
 | COUNT( [ DISTINCT ] value [, value]* ) | Returns the number of input rows for which *value* is not null (wholly not null if *value* is composite)


[4/5] incubator-calcite git commit: Add tests for scalar sub-queries, including test cases for [CALCITE-709] Errors with LIMIT inside scalar sub-query

Posted by jh...@apache.org.
Add tests for scalar sub-queries, including test cases for [CALCITE-709] Errors with LIMIT inside scalar sub-query


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

Branch: refs/heads/master
Commit: f107218b956a1b25d11c8d06c6378bdd22c75378
Parents: f5434a4
Author: Julian Hyde <jh...@apache.org>
Authored: Fri May 1 11:19:06 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri May 1 11:19:06 2015 -0700

----------------------------------------------------------------------
 .../java/org/apache/calcite/test/JdbcTest.java  |   4 +
 core/src/test/resources/sql/scalar.oq           | 216 +++++++++++++++++++
 2 files changed, 220 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f107218b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 678f053..27eb0e5 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -4391,6 +4391,10 @@ public class JdbcTest {
     checkRun("sql/sort.oq");
   }
 
+  @Test public void testRunScalar() throws Exception {
+    checkRun("sql/scalar.oq");
+  }
+
   @Test public void testRunSubquery() throws Exception {
     checkRun("sql/subquery.oq");
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f107218b/core/src/test/resources/sql/scalar.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/scalar.oq b/core/src/test/resources/sql/scalar.oq
new file mode 100644
index 0000000..103df29
--- /dev/null
+++ b/core/src/test/resources/sql/scalar.oq
@@ -0,0 +1,216 @@
+# scalar.oq - Scalar sub-queries
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+!set outputformat mysql
+!use scott
+
+select deptno, (select min(empno) from "scott".emp where deptno = dept.deptno) as x from "scott".dept;
++--------+------+
+| DEPTNO | X    |
++--------+------+
+|     10 | 7782 |
+|     20 | 7369 |
+|     30 | 7499 |
+|     40 |      |
++--------+------+
+(4 rows)
+
+!ok
+
+select deptno, (select count(*) from "scott".emp where deptno = dept.deptno) as x from "scott".dept;
++--------+---+
+| DEPTNO | X |
++--------+---+
+|     10 | 3 |
+|     20 | 5 |
+|     30 | 6 |
+|     40 | 0 |
++--------+---+
+(4 rows)
+
+!ok
+
+select deptno, (select count(*) from "scott".emp where deptno = dept.deptno group by deptno) as x from "scott".dept;
++--------+---+
+| DEPTNO | X |
++--------+---+
+|     10 | 3 |
+|     20 | 5 |
+|     30 | 6 |
+|     40 |   |
++--------+---+
+(4 rows)
+
+!ok
+
+# cast necessary to prevent overflow
+select deptno, (select sum(cast(empno as int)) from "scott".emp where deptno = dept.deptno group by deptno) as x from "scott".dept;
++--------+-------+
+| DEPTNO | X     |
++--------+-------+
+|     10 | 23555 |
+|     20 | 38501 |
+|     30 | 46116 |
+|     40 |       |
++--------+-------+
+(4 rows)
+
+!ok
+
+select deptno, (select count(*) from "scott".emp where 1 = 0) as x from "scott".dept;
++--------+---+
+| DEPTNO | X |
++--------+---+
+|     10 | 0 |
+|     20 | 0 |
+|     30 | 0 |
+|     40 | 0 |
++--------+---+
+(4 rows)
+
+!ok
+
+select deptno, (select count(*) from "scott".emp where 1 = 0 group by ()) as x from "scott".dept;
++--------+---+
+| DEPTNO | X |
++--------+---+
+|     10 | 0 |
+|     20 | 0 |
+|     30 | 0 |
+|     40 | 0 |
++--------+---+
+(4 rows)
+
+!ok
+
+select deptno, (select sum(empno) from "scott".emp where 1 = 0) as x from "scott".dept;
++--------+---+
+| DEPTNO | X |
++--------+---+
+|     10 |   |
+|     20 |   |
+|     30 |   |
+|     40 |   |
++--------+---+
+(4 rows)
+
+!ok
+
+select deptno, (select sum(empno) from "scott".emp where 1 = 0 group by ()) as x from "scott".dept;
++--------+---+
+| DEPTNO | X |
++--------+---+
+|     10 |   |
+|     20 |   |
+|     30 |   |
+|     40 |   |
++--------+---+
+(4 rows)
+
+!ok
+
+# [CALCITE-709] Errors with LIMIT inside scalar sub-query
+!if (false) {
+select deptno, (select sum(empno) from "scott".emp where deptno = dept.deptno limit 1) as x from "scott".dept;
++--------+----------------------+
+| DEPTNO |          X           |
++--------+----------------------+
+| 10     | 23555                |
+| 20     | 38501                |
+| 30     | 46116                |
+| 40     | null                 |
++--------+----------------------+
+(4 rows)
+
+!ok
+!}
+
+# [CALCITE-709] Errors with LIMIT inside scalar sub-query
+!if (false) {
+select deptno, (select sum(empno) from "scott".emp where deptno = dept.deptno limit 0) as x from "scott".dept;
++--------+----------------------+
+| DEPTNO |          X           |
++--------+----------------------+
+| 10     | 23555                |
+| 20     | 38501                |
+| 30     | 46116                |
+| 40     | null                 |
++--------+----------------------+
+(4 rows)
+
+!ok
+!}
+
+# [CALCITE-709] Errors with LIMIT inside scalar sub-query
+!if (false) {
+select deptno, (select deptno from "scott".emp where deptno = dept.deptno limit 1) as x from "scott".dept;
++--------+------+
+| DEPTNO |  X   |
++--------+------+
+| 10     | 10   |
+| 20     | 20   |
+| 30     | 30   |
+| 40     | null |
++--------+------+
+(4 rows)
+
+!ok
+!}
+
+select deptno, (select deptno from "scott".emp where deptno = dept.deptno limit 0) as x from "scott".dept;
++--------+---+
+| DEPTNO | X |
++--------+---+
+|     10 |   |
+|     20 |   |
+|     30 |   |
+|     40 |   |
++--------+---+
+(4 rows)
+
+!ok
+
+# [CALCITE-709] Errors with LIMIT inside scalar sub-query
+!if (false) {
+select deptno, (select empno from "scott".emp where deptno = dept.deptno order by empno limit 1) as x from "scott".dept;
++--------+--------+
+| DEPTNO |   X    |
++--------+--------+
+| 10     | 7369   |
+| 20     | 7369   |
+| 30     | 7369   |
+| 40     | 7369   |
++--------+--------+
+(4 rows)
+
+!ok
+!}
+
+select deptno, (select empno from "scott".emp order by empno limit 1) as x from "scott".dept;
++--------+------+
+| DEPTNO | X    |
++--------+------+
+|     10 | 7369 |
+|     20 | 7369 |
+|     30 | 7369 |
+|     40 | 7369 |
++--------+------+
+(4 rows)
+
+!ok
+
+# End scalar.oq


[5/5] incubator-calcite git commit: [CALCITE-695] Do not add SINGLE_VALUE aggregate function to a sub-query that will never return more than one row (Hsuan-Yi Chu)

Posted by jh...@apache.org.
[CALCITE-695] Do not add SINGLE_VALUE aggregate function to a sub-query that will never return more than one row (Hsuan-Yi Chu)

Close apache/incubator-calcite#81


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

Branch: refs/heads/master
Commit: f076a9bacc25dc5671b5557e48d9a654a6aa6854
Parents: f107218
Author: Hsuan-Yi Chu <hs...@usc.edu>
Authored: Thu Apr 30 11:16:03 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri May 1 11:25:21 2015 -0700

----------------------------------------------------------------------
 .../calcite/sql2rel/SqlToRelConverter.java      | 25 ++++++++-
 .../main/java/org/apache/calcite/util/Util.java | 47 +++++++++++++++++
 .../calcite/test/SqlToRelConverterTest.java     | 37 ++++++++++++++
 .../calcite/test/SqlToRelConverterTest.xml      | 54 ++++++++++++++++++++
 4 files changed, 161 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f076a9ba/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 7bc58be..905578d 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -94,6 +94,7 @@ import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlMerge;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlNumericLiteral;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.SqlSampleSpec;
@@ -102,6 +103,7 @@ import org.apache.calcite.sql.SqlSelectKeyword;
 import org.apache.calcite.sql.SqlSetOperator;
 import org.apache.calcite.sql.SqlUpdate;
 import org.apache.calcite.sql.SqlUtil;
+import org.apache.calcite.sql.SqlValuesOperator;
 import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.sql.SqlWith;
 import org.apache.calcite.sql.SqlWithItem;
@@ -1282,11 +1284,30 @@ public class SqlToRelConverter {
         SqlNode selectExpr = selectList.get(0);
         if (selectExpr instanceof SqlCall) {
           SqlCall selectExprCall = (SqlCall) selectExpr;
-          if (selectExprCall.getOperator()
-              instanceof SqlAggFunction) {
+          if (Util.isSingleValue(selectExprCall)) {
             return plan;
           }
         }
+
+        // If there is a limit with 0 or 1,
+        // it is ensured to produce a single value
+        if (select.getFetch() != null
+            && select.getFetch() instanceof SqlNumericLiteral) {
+          SqlNumericLiteral limitNum = (SqlNumericLiteral) select.getFetch();
+          if (((BigDecimal) limitNum.getValue()).intValue() < 2) {
+            return plan;
+          }
+        }
+      }
+    } else if (query instanceof SqlCall) {
+      // If the query is (values ...),
+      // it is necessary to look into the operands to determine
+      // whether SingleValueAgg is necessary
+      SqlCall exprCall = (SqlCall) query;
+      if (exprCall.getOperator()
+          instanceof SqlValuesOperator
+              && Util.isSingleValue(exprCall)) {
+        return plan;
       }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f076a9ba/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 5aa8246..f739fbd 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -19,6 +19,12 @@ package org.apache.calcite.util;
 import org.apache.calcite.avatica.util.Spaces;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.runtime.CalciteException;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlValuesOperator;
+import org.apache.calcite.sql.fun.SqlRowOperator;
 
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
@@ -133,6 +139,47 @@ public class Util {
   //~ Methods ----------------------------------------------------------------
 
   /**
+   * Does nothing with its argument. Returns whether it is ensured that
+   * the call produces a single value
+   *
+   * @param call      the expression to evaluate
+   * @return Whether it is ensured that the call produces a single value
+   */
+  public static boolean isSingleValue(SqlCall call) {
+    if (call.getOperator() instanceof SqlAggFunction) {
+      return true;
+    } else if (call.getOperator() instanceof SqlValuesOperator
+        || call.getOperator() instanceof SqlRowOperator) {
+      List<SqlNode> operands = call.getOperandList();
+      if (operands.size() == 1) {
+        SqlNode operand = operands.get(0);
+        if (operand instanceof SqlLiteral) {
+          return true;
+        } else if (operand instanceof SqlCall) {
+          return isSingleValue((SqlCall) operand);
+        }
+      }
+
+      return false;
+    } else {
+      boolean isScalar = true;
+      for (SqlNode operand : call.getOperandList()) {
+        if (operand instanceof SqlLiteral) {
+          continue;
+        }
+
+        if (!(operand instanceof SqlCall)
+            || !Util.isSingleValue((SqlCall) operand)) {
+          isScalar = false;
+          break;
+        }
+      }
+
+      return isScalar;
+    }
+  }
+
+  /**
    * Does nothing with its argument. Call this method when you have a value
    * you are not interested in, but you don't want the compiler to warn that
    * you are not using it.

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f076a9ba/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index c3a1f40..fd09a21 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -1143,6 +1143,43 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
   }
 
   /**
+   * Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-695">[CALCITE-695]
+   * SqlSingleValueAggFunction is created when it may not be needed</a>.
+   */
+  @Test public void testSubqueryAggreFunctionFollowedBySimpleOperation() {
+    sql("select deptno\n"
+        + "from EMP\n"
+        + "where deptno > (select min(deptno) * 2 + 10 from EMP)")
+        .convertsTo("${plan}");
+  }
+
+  /**
+   * Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-695">[CALCITE-695]
+   * SqlSingleValueAggFunction is created when it may not be needed</a>.
+   */
+  @Test public void testSubqueryValues() {
+    sql("select deptno\n"
+        + "from EMP\n"
+        + "where deptno > (values 10)")
+        .convertsTo("${plan}");
+  }
+
+  /**
+   * Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-695">[CALCITE-695]
+   * SqlSingleValueAggFunction is created when it may not be needed</a>.
+   */
+  @Test public void testSubqueryLimitOne() {
+    sql("select deptno\n"
+        + "from EMP\n"
+        + "where deptno > (select deptno \n"
+        + "from EMP order by deptno limit 1)")
+        .convertsTo("${plan}");
+  }
+
+  /**
    * Visitor that checks that every {@link RelNode} in a tree is valid.
    *
    * @see RelNode#isValid(boolean)

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f076a9ba/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 7ad5b41..76070dc 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -2421,4 +2421,58 @@ LogicalAggregate(group=[{0}], EXPR$1=[SUM($1) FILTER $2], EXPR$2=[COUNT()])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testSubqueryAggreFunctionFollowedBySimpleOperation">
+        <Resource name="sql">
+            <![CDATA[select deptno
+from EMP
+where deptno > (select min(deptno) * 2 + 10 from EMP]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalProject(DEPTNO=[$7])
+  LogicalFilter(condition=[>($7, $9)])
+    LogicalJoin(condition=[true], joinType=[left])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalProject(EXPR$0=[+(*($0, 2), 10)])
+        LogicalAggregate(group=[{}], agg#0=[MIN($0)])
+          LogicalProject(DEPTNO=[$7])
+            LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testSubqueryValues">
+        <Resource name="sql">
+            <![CDATA[select deptno
+from EMP
+where deptno > (values 10)]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalProject(DEPTNO=[$7])
+  LogicalFilter(condition=[>($7, $9)])
+    LogicalJoin(condition=[true], joinType=[left])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalValues(tuples=[[{ 10 }]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testSubqueryLimitOne">
+        <Resource name="sql">
+            <![CDATA[select deptno
+from EMP
+where deptno > (select deptno
+from EMP order by deptno limit 1)]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalProject(DEPTNO=[$7])
+  LogicalFilter(condition=[>($7, $9)])
+    LogicalJoin(condition=[true], joinType=[left])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalSort(sort0=[$0], dir0=[ASC], fetch=[1])
+        LogicalProject(DEPTNO=[$7])
+          LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>


[3/5] incubator-calcite git commit: [CALCITE-704] FILTER clause for aggregate functions

Posted by jh...@apache.org.
[CALCITE-704] FILTER clause for aggregate functions

Implemented in enumerable convention for all aggregate functions, including user-defined.

Implemented "agg(DISTINCT arg [, ...]) FILTER (WHERE condition)" for all strict aggregate functions (i.e. aggregate functions that do not wish to see nulls).

Not implemented in JDBC, MongoDB and other adapters.

Not implemented in window functions.

Deprecate calling AggregateCall constructor; use create method instead.


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

Branch: refs/heads/master
Commit: f5434a495271a6887f7964423630405cf3d8c877
Parents: 91f0fca
Author: Julian Hyde <jh...@apache.org>
Authored: Wed Apr 29 13:47:26 2015 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Wed Apr 29 14:40:21 2015 -0700

----------------------------------------------------------------------
 core/src/main/codegen/templates/Parser.jj       |  31 ++-
 .../adapter/enumerable/AggAddContext.java       |   6 +
 .../adapter/enumerable/EnumerableAggregate.java |  37 +--
 .../adapter/enumerable/EnumerableWindow.java    |   4 +
 .../enumerable/StrictAggImplementor.java        |  18 +-
 .../apache/calcite/adapter/jdbc/JdbcRules.java  |  18 ++
 .../calcite/interpreter/AggregateNode.java      |  15 +-
 .../org/apache/calcite/plan/RelOptUtil.java     |  15 +-
 .../calcite/plan/SubstitutionVisitor.java       |   7 +-
 .../org/apache/calcite/rel/core/Aggregate.java  |   3 +
 .../apache/calcite/rel/core/AggregateCall.java  | 101 ++++++--
 .../org/apache/calcite/rel/core/Window.java     |   9 +-
 .../calcite/rel/externalize/RelJsonReader.java  |   4 +-
 .../AggregateExpandDistinctAggregatesRule.java  | 113 +++++----
 .../rel/rules/AggregateFilterTransposeRule.java |   5 +-
 .../rel/rules/AggregateProjectMergeRule.java    |  12 +-
 .../AggregateProjectPullUpConstantsRule.java    |   9 +-
 .../rel/rules/AggregateReduceFunctionsRule.java |  21 +-
 .../java/org/apache/calcite/rex/RexBuilder.java |   2 +-
 .../apache/calcite/runtime/CalciteResource.java |   6 +
 .../org/apache/calcite/sql/SqlAggFunction.java  |   2 +-
 .../apache/calcite/sql/SqlFilterOperator.java   | 121 +++++++++
 .../java/org/apache/calcite/sql/SqlKind.java    |   5 +
 .../org/apache/calcite/sql/SqlOverOperator.java |   2 +-
 .../calcite/sql/fun/SqlStdOperatorTable.java    |   5 +
 .../apache/calcite/sql/validate/AggChecker.java |   4 +
 .../calcite/sql/validate/SqlValidator.java      |   6 +-
 .../calcite/sql/validate/SqlValidatorImpl.java  |  13 +-
 .../apache/calcite/sql2rel/RelDecorrelator.java |  19 +-
 .../apache/calcite/sql2rel/RelFieldTrimmer.java |  17 +-
 .../calcite/sql2rel/SqlToRelConverter.java      | 253 ++++++++++---------
 .../main/java/org/apache/calcite/util/Bug.java  |   8 +-
 .../apache/calcite/util/mapping/Mappings.java   |  10 +-
 .../calcite/runtime/CalciteResource.properties  |   2 +
 .../org/apache/calcite/plan/RelWriterTest.java  |  11 +-
 .../plan/volcano/TraitPropagationTest.java      |   5 +-
 .../calcite/sql/parser/SqlParserTest.java       |  11 +
 .../java/org/apache/calcite/test/JdbcTest.java  |  71 ++++++
 .../calcite/test/SqlToRelConverterTest.java     |   6 +
 .../apache/calcite/test/SqlValidatorTest.java   |  22 ++
 .../calcite/test/SqlToRelConverterTest.xml      |  12 +
 core/src/test/resources/sql/agg.oq              | 122 +++++++++
 doc/REFERENCE.md                                |  14 +
 43 files changed, 861 insertions(+), 316 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/codegen/templates/Parser.jj
----------------------------------------------------------------------
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 6b62ce3..36f9e05 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -4101,8 +4101,9 @@ SqlNode NamedFunctionCall() :
     List<SqlNode> args;
     SqlParserPos pos;
     SqlParserPos starPos;
+    SqlParserPos filterPos = null;
+    SqlNode filter = null;
     SqlParserPos overPos = null;
-    boolean over = false;
     SqlCall function = null;
     SqlFunctionCategory funcType = SqlFunctionCategory.USER_DEFINED_FUNCTION;
     SqlNode e = null;
@@ -4138,10 +4139,17 @@ SqlNode NamedFunctionCall() :
                 args.remove(0);
             }
         )
-        [ <OVER>
+        [
+            <FILTER> { filterPos = getPos(); }
+            <LPAREN>
+            <WHERE>
+            filter = Expression(ExprContext.ACCEPT_SUBQUERY)
+            <RPAREN>  { filterPos = filterPos.plus(getPos()); }
+        ]
+        [
+            <OVER>
             {
                 overPos = getPos();
-                over = true;
                 pos = pos.plus(overPos);
             }
             (
@@ -4154,17 +4162,20 @@ SqlNode NamedFunctionCall() :
                 qualifiedName, pos, funcType, quantifier,
                 SqlParserUtil.toNodeArray(args));
 
-            if (over) {
+            if (filter != null) {
+                function = SqlStdOperatorTable.FILTER.createCall(filterPos,
+                    function, filter);
+            }
+            if (overPos != null) {
                 if (id != null) {
-                    return SqlStdOperatorTable.OVER.createCall(
-                        overPos, new SqlNode[] {function, id});
+                    function = SqlStdOperatorTable.OVER.createCall(overPos,
+                        function, id);
                 } else {
-                    return SqlStdOperatorTable.OVER.createCall(
-                        overPos, new SqlNode[] { function, e });
+                    function = SqlStdOperatorTable.OVER.createCall(overPos,
+                        function, e);
                 }
-            } else {
-                return function;
             }
+            return function;
         }
     )
 }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/adapter/enumerable/AggAddContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/AggAddContext.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/AggAddContext.java
index 9f8f595..4da0447 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/AggAddContext.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/AggAddContext.java
@@ -38,6 +38,12 @@ public interface AggAddContext extends AggResultContext {
   List<RexNode> rexArguments();
 
   /**
+   * Returns {@link org.apache.calcite.rex.RexNode} representation of the
+   * filter, or null.
+   */
+  RexNode rexFilterArgument();
+
+  /**
    * Returns Linq4j form of arguments.
    * The resulting value is equivalent to
    * {@code rowTranslator().translateList(rexArguments())}.

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java
index 38f3ee9..60f9b11 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java
@@ -19,6 +19,7 @@ package org.apache.calcite.adapter.enumerable;
 import org.apache.calcite.adapter.enumerable.impl.AggAddContextImpl;
 import org.apache.calcite.adapter.enumerable.impl.AggResultContextImpl;
 import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.linq4j.function.Function0;
 import org.apache.calcite.linq4j.function.Function1;
 import org.apache.calcite.linq4j.function.Function2;
@@ -177,12 +178,9 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
     final int groupCount = getGroupCount();
     final int indicatorCount = getIndicatorCount();
 
-    final List<AggImpState> aggs =
-        new ArrayList<AggImpState>(aggCalls.size());
-
-    for (int i = 0; i < aggCalls.size(); i++) {
-      AggregateCall call = aggCalls.get(i);
-      aggs.add(new AggImpState(i, call, false));
+    final List<AggImpState> aggs = new ArrayList<>(aggCalls.size());
+    for (Ord<AggregateCall> call : Ord.zip(aggCalls)) {
+      aggs.add(new AggImpState(call.i, call.e, false));
     }
 
     // Function0<Object[]> accumulatorInitializer =
@@ -191,11 +189,10 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
     //             return new Object[] {0, 0};
     //         }
     //     };
-    final List<Expression> initExpressions =
-        new ArrayList<Expression>();
+    final List<Expression> initExpressions = new ArrayList<>();
     final BlockBuilder initBlock = new BlockBuilder();
 
-    final List<Type> aggStateTypes = new ArrayList<Type>();
+    final List<Type> aggStateTypes = new ArrayList<>();
     for (final AggImpState agg : aggs) {
       agg.context =
           new AggContext() {
@@ -230,8 +227,7 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
 
       aggStateTypes.addAll(state);
 
-      final List<Expression> decls =
-          new ArrayList<Expression>(state.size());
+      final List<Expression> decls = new ArrayList<>(state.size());
       for (int i = 0; i < state.size(); i++) {
         String aggName = "a" + agg.aggIdx;
         if (CalcitePrepareImpl.DEBUG) {
@@ -281,9 +277,8 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
     for (int i = 0, stateOffset = 0; i < aggs.size(); i++) {
       final AggImpState agg = aggs.get(i);
 
-      int stateSize = agg.state.size();
-      List<Expression> accumulator =
-          new ArrayList<Expression>(stateSize);
+      final int stateSize = agg.state.size();
+      final List<Expression> accumulator = new ArrayList<>(stateSize);
       for (int j = 0; j < stateSize; j++) {
         accumulator.add(accPhysType.fieldReference(acc_, j + stateOffset));
       }
@@ -296,14 +291,20 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
             public List<RexNode> rexArguments() {
               List<RelDataTypeField> inputTypes =
                   inputPhysType.getRowType().getFieldList();
-              List<RexNode> args = new ArrayList<RexNode>();
-              for (Integer index : agg.call.getArgList()) {
-                args.add(
-                    new RexInputRef(index, inputTypes.get(index).getType()));
+              List<RexNode> args = new ArrayList<>();
+              for (int index : agg.call.getArgList()) {
+                args.add(RexInputRef.of(index, inputTypes));
               }
               return args;
             }
 
+            public RexNode rexFilterArgument() {
+              return agg.call.filterArg < 0
+                  ? null
+                  : RexInputRef.of(agg.call.filterArg,
+                      inputPhysType.getRowType());
+            }
+
             public RexToLixTranslator rowTranslator() {
               return RexToLixTranslator.forAggregation(typeFactory,
                   currentBlock(),

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java
index 7650c62..15f77f5 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableWindow.java
@@ -828,6 +828,10 @@ public class EnumerableWindow extends Window implements EnumerableRel {
             public List<RexNode> rexArguments() {
               return rexArguments.apply(agg);
             }
+
+            public RexNode rexFilterArgument() {
+              return null; // REVIEW
+            }
           };
       agg.implementor.implementAdd(agg.context, addContext);
     }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/adapter/enumerable/StrictAggImplementor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/StrictAggImplementor.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/StrictAggImplementor.java
index 783dd43..8b463e8 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/StrictAggImplementor.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/StrictAggImplementor.java
@@ -74,7 +74,7 @@ public abstract class StrictAggImplementor implements AggImplementor {
     }
     trackNullsPerRow = !(info instanceof WinAggContext) || hasNullableArgs;
 
-    List<Type> res = new ArrayList<Type>(subState.size() + 1);
+    List<Type> res = new ArrayList<>(subState.size() + 1);
     res.addAll(subState);
     res.add(boolean.class); // has not nulls
     return res;
@@ -111,10 +111,16 @@ public abstract class StrictAggImplementor implements AggImplementor {
   }
 
   public final void implementAdd(AggContext info, final AggAddContext add) {
-    List<RexNode> args = add.rexArguments();
-    RexToLixTranslator translator = add.rowTranslator();
-    List<Expression> conditions =
-      translator.translateList(args, RexImpTable.NullAs.IS_NOT_NULL);
+    final List<RexNode> args = add.rexArguments();
+    final RexToLixTranslator translator = add.rowTranslator();
+    final List<Expression> conditions = new ArrayList<>();
+    conditions.addAll(
+        translator.translateList(args, RexImpTable.NullAs.IS_NOT_NULL));
+    if (add.rexFilterArgument() != null) {
+      conditions.add(
+          translator.translate(add.rexFilterArgument(),
+              RexImpTable.NullAs.FALSE));
+    }
     Expression condition = Expressions.foldAnd(conditions);
     if (Expressions.constant(false).equals(condition)) {
       return;
@@ -137,7 +143,7 @@ public abstract class StrictAggImplementor implements AggImplementor {
       return;
     }
 
-    final Map<RexNode, Boolean> nullables = new HashMap<RexNode, Boolean>();
+    final Map<RexNode, Boolean> nullables = new HashMap<>();
     for (RexNode arg : args) {
       if (translator.isNullable(arg)) {
         nullables.put(arg, false);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
index d519029..307434f 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
@@ -70,6 +70,7 @@ import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.schema.ModifiableTable;
 import org.apache.calcite.sql.JoinConditionType;
 import org.apache.calcite.sql.JoinType;
+import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDialect;
 import org.apache.calcite.sql.SqlFunction;
@@ -97,6 +98,7 @@ import org.apache.calcite.util.trace.CalciteTrace;
 import com.google.common.collect.ImmutableList;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -678,6 +680,22 @@ public class JdbcRules {
         throws InvalidRelException {
       super(cluster, traitSet, input, indicator, groupSet, groupSets, aggCalls);
       assert getConvention() instanceof JdbcConvention;
+      for (AggregateCall aggCall : aggCalls) {
+        if (!canImplement(aggCall.getAggregation())) {
+          throw new InvalidRelException("cannot implement aggregate function "
+              + aggCall.getAggregation());
+        }
+      }
+    }
+
+    /** Returns whether this JDBC data source can implement a given aggregate
+     * function. */
+    private boolean canImplement(SqlAggFunction aggregation) {
+      return Arrays.asList(SqlStdOperatorTable.COUNT,
+          SqlStdOperatorTable.SUM,
+          SqlStdOperatorTable.SUM0,
+          SqlStdOperatorTable.MIN,
+          SqlStdOperatorTable.MAX).contains(aggregation);
     }
 
     @Override public JdbcAggregate copy(RelTraitSet traitSet, RelNode input,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/interpreter/AggregateNode.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/interpreter/AggregateNode.java b/core/src/main/java/org/apache/calcite/interpreter/AggregateNode.java
index 1dff33a..f17df8f 100644
--- a/core/src/main/java/org/apache/calcite/interpreter/AggregateNode.java
+++ b/core/src/main/java/org/apache/calcite/interpreter/AggregateNode.java
@@ -33,7 +33,6 @@ import org.apache.calcite.linq4j.tree.ParameterExpression;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.schema.impl.AggregateFunctionImpl;
@@ -145,16 +144,20 @@ public class AggregateNode extends AbstractSingleNode<Aggregate> {
       AggAddContext addContext =
           new AggAddContextImpl(builder2, accumulator) {
             public List<RexNode> rexArguments() {
-              List<RelDataTypeField> inputTypes =
-                  inputPhysType.getRowType().getFieldList();
               List<RexNode> args = new ArrayList<RexNode>();
-              for (Integer index : agg.call.getArgList()) {
-                args.add(
-                    new RexInputRef(index, inputTypes.get(index).getType()));
+              for (int index : agg.call.getArgList()) {
+                args.add(RexInputRef.of(index, inputPhysType.getRowType()));
               }
               return args;
             }
 
+            public RexNode rexFilterArgument() {
+              return agg.call.filterArg < 0
+                  ? null
+                  : RexInputRef.of(agg.call.filterArg,
+                      inputPhysType.getRowType());
+            }
+
             public RexToLixTranslator rowTranslator() {
               return RexToLixTranslator.forAggregation(typeFactory,
                   currentBlock(),

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index cf68db9..3deb4e4 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -694,19 +694,10 @@ public abstract class RelOptUtil {
     final List<AggregateCall> aggCalls = new ArrayList<>();
 
     for (int i = 0; i < aggCallCnt; i++) {
-      RelDataType returnType =
-          SqlStdOperatorTable.SINGLE_VALUE.inferReturnType(
-              cluster.getRexBuilder().getTypeFactory(),
-              ImmutableList.of(
-                  rel.getRowType().getFieldList().get(i).getType()));
-
       aggCalls.add(
-          new AggregateCall(
-              SqlStdOperatorTable.SINGLE_VALUE,
-              false,
-              ImmutableList.of(i),
-              returnType,
-              null));
+          AggregateCall.create(
+              SqlStdOperatorTable.SINGLE_VALUE, false, ImmutableList.of(i), -1,
+              0, rel, null, null));
     }
 
     return LogicalAggregate.create(rel, false,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
index e0ad056..410339f 100644
--- a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
+++ b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
@@ -1166,7 +1166,8 @@ public class SubstitutionVisitor {
     return Lists.transform(aggCallList,
         new Function<AggregateCall, AggregateCall>() {
           public AggregateCall apply(AggregateCall call) {
-            return call.copy(Mappings.apply2(mapping, call.getArgList()));
+            return call.copy(Mappings.apply2(mapping, call.getArgList()),
+                Mappings.apply(mapping, call.filterArg));
           }
         });
   }
@@ -1214,9 +1215,9 @@ public class SubstitutionVisitor {
           return null;
         }
         aggregateCalls.add(
-            new AggregateCall(getRollup(aggregateCall.getAggregation()),
+            AggregateCall.create(getRollup(aggregateCall.getAggregation()),
                 aggregateCall.isDistinct(),
-                ImmutableList.of(target.groupSet.cardinality() + i),
+                ImmutableList.of(target.groupSet.cardinality() + i), -1,
                 aggregateCall.type, aggregateCall.name));
       }
       result = MutableAggregate.of(target, false, groupSet.build(), null,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java b/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java
index d84ef8f..a483595 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java
@@ -137,6 +137,9 @@ public abstract class Aggregate extends SingleRel {
     assert groupSet.length() <= child.getRowType().getFieldCount();
     for (AggregateCall aggCall : aggCalls) {
       assert typeMatchesInferred(aggCall, true);
+      assert aggCall.filterArg < 0
+          || child.getRowType().getFieldList().get(aggCall.filterArg).getType()
+              .getSqlTypeName() == SqlTypeName.BOOLEAN;
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/core/AggregateCall.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/AggregateCall.java b/core/src/main/java/org/apache/calcite/rel/core/AggregateCall.java
index fa1620c..0589e58 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/AggregateCall.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/AggregateCall.java
@@ -23,6 +23,7 @@ import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.type.SqlTypeUtil;
 import org.apache.calcite.util.mapping.Mappings;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
 import java.util.List;
@@ -43,6 +44,7 @@ public class AggregateCall {
   // We considered using ImmutableIntList but we would not save much memory:
   // since all values are small, ImmutableList uses cached Integer values.
   private final ImmutableList<Integer> argList;
+  public final int filterArg;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -55,29 +57,56 @@ public class AggregateCall {
    * @param type        Result type
    * @param name        Name (may be null)
    */
+  @Deprecated // to be removed before 2.0
   public AggregateCall(
       SqlAggFunction aggFunction,
       boolean distinct,
       List<Integer> argList,
       RelDataType type,
       String name) {
-    this.type = type;
-    this.name = name;
-    assert aggFunction != null;
-    assert argList != null;
-    assert type != null;
-    this.aggFunction = aggFunction;
+    this(aggFunction, distinct, argList, -1, type, name);
+  }
 
+  /**
+   * Creates an AggregateCall.
+   *
+   * @param aggFunction Aggregate function
+   * @param distinct    Whether distinct
+   * @param argList     List of ordinals of arguments
+   * @param filterArg   Ordinal of filter argument, or -1
+   * @param type        Result type
+   * @param name        Name (may be null)
+   */
+  private AggregateCall(
+      SqlAggFunction aggFunction,
+      boolean distinct,
+      List<Integer> argList,
+      int filterArg,
+      RelDataType type,
+      String name) {
+    this.type = Preconditions.checkNotNull(type);
+    this.name = name;
+    this.aggFunction = Preconditions.checkNotNull(aggFunction);
     this.argList = ImmutableList.copyOf(argList);
+    this.filterArg = filterArg;
     this.distinct = distinct;
   }
 
   //~ Methods ----------------------------------------------------------------
 
   /** Creates an AggregateCall, inferring its type if {@code type} is null. */
+  @Deprecated // to be removed before 2.0
   public static AggregateCall create(SqlAggFunction aggFunction,
       boolean distinct, List<Integer> argList, int groupCount, RelNode input,
       RelDataType type, String name) {
+    return create(aggFunction, distinct, argList, -1, groupCount, input, type,
+        name);
+  }
+
+  /** Creates an AggregateCall, inferring its type if {@code type} is null. */
+  public static AggregateCall create(SqlAggFunction aggFunction,
+      boolean distinct, List<Integer> argList, int filterArg, int groupCount,
+      RelNode input, RelDataType type, String name) {
     if (type == null) {
       final RelDataTypeFactory typeFactory =
           input.getCluster().getTypeFactory();
@@ -88,7 +117,15 @@ public class AggregateCall {
               groupCount);
       type = aggFunction.inferReturnType(callBinding);
     }
-    return new AggregateCall(aggFunction, distinct, argList, type, name);
+    return create(aggFunction, distinct, argList, filterArg, type, name);
+  }
+
+  /** Creates an AggregateCall. */
+  public static AggregateCall create(SqlAggFunction aggFunction,
+      boolean distinct, List<Integer> argList, int filterArg, RelDataType type,
+      String name) {
+    return new AggregateCall(aggFunction, distinct, argList, filterArg, type,
+        name);
   }
 
   /**
@@ -146,7 +183,8 @@ public class AggregateCall {
    */
   public AggregateCall rename(String name) {
     // no need to copy argList - already immutable
-    return new AggregateCall(aggFunction, distinct, argList, type, name);
+    return new AggregateCall(aggFunction, distinct, argList, filterArg, type,
+        name);
   }
 
   public String toString() {
@@ -164,22 +202,25 @@ public class AggregateCall {
       buf.append(arg);
     }
     buf.append(")");
+    if (filterArg >= 0) {
+      buf.append(" FILTER $");
+      buf.append(filterArg);
+    }
     return buf.toString();
   }
 
-  // override Object
-  public boolean equals(Object o) {
+  @Override public boolean equals(Object o) {
     if (!(o instanceof AggregateCall)) {
       return false;
     }
     AggregateCall other = (AggregateCall) o;
     return aggFunction.equals(other.aggFunction)
         && (distinct == other.distinct)
-        && argList.equals(other.argList);
+        && argList.equals(other.argList)
+        && filterArg == other.filterArg;
   }
 
-  // override Object
-  public int hashCode() {
+  @Override public int hashCode() {
     return aggFunction.hashCode() + argList.hashCode();
   }
 
@@ -193,10 +234,9 @@ public class AggregateCall {
     final RelDataType rowType = aggregateRelBase.getInput().getRowType();
 
     return new Aggregate.AggCallBinding(
-        aggregateRelBase.getCluster().getTypeFactory(),
-        (SqlAggFunction) aggFunction,
+        aggregateRelBase.getCluster().getTypeFactory(), aggFunction,
         SqlTypeUtil.projectTypes(rowType, argList),
-        aggregateRelBase.getGroupCount());
+        filterArg >= 0 ? 0 : aggregateRelBase.getGroupCount());
   }
 
   /**
@@ -205,8 +245,14 @@ public class AggregateCall {
    * @param args Arguments
    * @return AggregateCall that suits new inputs and GROUP BY columns
    */
+  public AggregateCall copy(List<Integer> args, int filterArg) {
+    return new AggregateCall(aggFunction, distinct, args, filterArg, type,
+        name);
+  }
+
+  @Deprecated // to be removed before 2.0
   public AggregateCall copy(List<Integer> args) {
-    return new AggregateCall(aggFunction, distinct, args, type, name);
+    return copy(args, filterArg);
   }
 
   /**
@@ -214,26 +260,31 @@ public class AggregateCall {
    * and/or number of columns in GROUP BY.
    *
    * @param input relation that will be used as a child of aggregate
-   * @param aggArgs argument indices of the new call in the input
+   * @param argList argument indices of the new call in the input
+   * @param filterArg Index of the filter, or -1
    * @param oldGroupKeyCount number of columns in GROUP BY of old aggregate
    * @param newGroupKeyCount number of columns in GROUP BY of new aggregate
    * @return AggregateCall that suits new inputs and GROUP BY columns
    */
-  public AggregateCall adaptTo(RelNode input, List<Integer> aggArgs,
-      int oldGroupKeyCount, int newGroupKeyCount) {
-    final SqlAggFunction sqlAgg = (SqlAggFunction) aggFunction;
+  public AggregateCall adaptTo(RelNode input, List<Integer> argList,
+      int filterArg, int oldGroupKeyCount, int newGroupKeyCount) {
     // The return type of aggregate call need to be recomputed.
     // Since it might depend on the number of columns in GROUP BY.
     final RelDataType newType =
-        oldGroupKeyCount == newGroupKeyCount ? type : null;
-    return create(sqlAgg, distinct, aggArgs, newGroupKeyCount, input, newType,
-        getName());
+        oldGroupKeyCount == newGroupKeyCount
+            && argList.equals(this.argList)
+            && filterArg == this.filterArg
+            ? type
+            : null;
+    return create(aggFunction, distinct, argList, filterArg, newGroupKeyCount,
+        input, newType, getName());
   }
 
   /** Creates a copy of this aggregate call, applying a mapping to its
    * arguments. */
   public AggregateCall transform(Mappings.TargetMapping mapping) {
-    return copy(Mappings.permute(argList, mapping));
+    return copy(Mappings.permute(argList, mapping),
+        Mappings.apply(mapping, filterArg));
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/core/Window.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Window.java b/core/src/main/java/org/apache/calcite/rel/core/Window.java
index df7510d..103b24f 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Window.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Window.java
@@ -313,12 +313,9 @@ public abstract class Window extends SingleRel {
 
         public AggregateCall get(int index) {
           final RexWinAggCall aggCall = aggCalls.get(index);
-          return new AggregateCall(
-              (SqlAggFunction) aggCall.getOperator(),
-              false,
-              getProjectOrdinals(aggCall.getOperands()),
-              aggCall.getType(),
-              fieldNames.get(aggCall.ordinal));
+          return AggregateCall.create((SqlAggFunction) aggCall.getOperator(),
+              false, getProjectOrdinals(aggCall.getOperands()), -1,
+              aggCall.getType(), fieldNames.get(aggCall.ordinal));
         }
       };
     }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java b/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java
index e8740d7..12bf70c 100644
--- a/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java
+++ b/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonReader.java
@@ -267,9 +267,11 @@ public class RelJsonReader {
         relJson.toAggregation(aggName, jsonAggCall);
     final Boolean distinct = (Boolean) jsonAggCall.get("distinct");
     final List<Integer> operands = (List<Integer>) jsonAggCall.get("operands");
+    final Integer filterOperand = (Integer) jsonAggCall.get("filter");
     final RelDataType type =
         relJson.toType(cluster.getTypeFactory(), jsonAggCall.get("type"));
-    return new AggregateCall(aggregation, distinct, operands, type, null);
+    return AggregateCall.create(aggregation, distinct, operands,
+        filterOperand == null ? -1 : filterOperand, type, null);
   }
 
   private RelNode lookupInput(String jsonInput) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
index 3afe7cc..e05c781 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
@@ -37,6 +37,7 @@ import org.apache.calcite.util.Util;
 
 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 java.util.ArrayList;
@@ -92,27 +93,22 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
     // Find all of the agg expressions. We use a LinkedHashSet to ensure
     // determinism.
     int nonDistinctCount = 0;
-    Set<List<Integer>> argListSets = new LinkedHashSet<List<Integer>>();
+    final Set<Pair<List<Integer>, Integer>> argLists = new LinkedHashSet<>();
     for (AggregateCall aggCall : aggregate.getAggCallList()) {
       if (!aggCall.isDistinct()) {
         ++nonDistinctCount;
         continue;
       }
-      ArrayList<Integer> argList = new ArrayList<Integer>();
-      for (Integer arg : aggCall.getArgList()) {
-        argList.add(arg);
-      }
-      argListSets.add(argList);
+      argLists.add(Pair.of(aggCall.getArgList(), aggCall.filterArg));
     }
-    Util.permAssert(argListSets.size() > 0, "containsDistinctCall lied");
+    Util.permAssert(argLists.size() > 0, "containsDistinctCall lied");
 
     // If all of the agg expressions are distinct and have the same
     // arguments then we can use a more efficient form.
-    if ((nonDistinctCount == 0) && (argListSets.size() == 1)) {
-      RelNode converted =
-          convertMonopole(
-              aggregate,
-              argListSets.iterator().next());
+    if (nonDistinctCount == 0 && argLists.size() == 1) {
+      final Pair<List<Integer>, Integer> pair =
+          Iterables.getOnlyElement(argLists);
+      RelNode converted = convertMonopole(aggregate, pair.left, pair.right);
       call.transformTo(converted);
       return;
     }
@@ -121,7 +117,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
     // Initially, the expressions point to the input field.
     final List<RelDataTypeField> aggFields =
         aggregate.getRowType().getFieldList();
-    final List<RexInputRef> refs = new ArrayList<RexInputRef>();
+    final List<RexInputRef> refs = new ArrayList<>();
     final List<String> fieldNames = aggregate.getRowType().getFieldNames();
     final ImmutableBitSet groupSet = aggregate.getGroupSet();
     final int groupAndIndicatorCount =
@@ -131,7 +127,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
     }
 
     // Aggregate the original relation, including any non-distinct aggregates.
-    List<AggregateCall> newAggCallList = new ArrayList<AggregateCall>();
+    final List<AggregateCall> newAggCallList = new ArrayList<>();
     int i = -1;
     for (AggregateCall aggCall : aggregate.getAggCallList()) {
       ++i;
@@ -160,8 +156,8 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
 
     // For each set of operands, find and rewrite all calls which have that
     // set of operands.
-    for (List<Integer> argList : argListSets) {
-      rel = doRewrite(aggregate, rel, argList, refs);
+    for (Pair<List<Integer>, Integer> argList : argLists) {
+      rel = doRewrite(aggregate, rel, argList.left, argList.right, refs);
     }
 
     rel = RelOptUtil.createProject(rel, refs, fieldNames);
@@ -174,9 +170,8 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
    * distinct aggregate function (or perhaps several over the same arguments)
    * and no non-distinct aggregate functions.
    */
-  private RelNode convertMonopole(
-      Aggregate aggregate,
-      List<Integer> argList) {
+  private RelNode convertMonopole(Aggregate aggregate, List<Integer> argList,
+      int filterArg) {
     // For example,
     //    SELECT deptno, COUNT(DISTINCT sal), SUM(DISTINCT sal)
     //    FROM emp
@@ -192,9 +187,9 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
 
     // Project the columns of the GROUP BY plus the arguments
     // to the agg function.
-    Map<Integer, Integer> sourceOf = new HashMap<Integer, Integer>();
+    final Map<Integer, Integer> sourceOf = new HashMap<>();
     final Aggregate distinct =
-        createSelectDistinct(aggregate, argList, sourceOf);
+        createSelectDistinct(aggregate, argList, filterArg, sourceOf);
 
     // Create an aggregate on top, with the new aggregate list.
     final List<AggregateCall> newAggCalls =
@@ -220,16 +215,13 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
    *                  first distinct aggregate in a query with no non-distinct
    *                  aggregates)
    * @param argList   Arguments to the distinct aggregate function
+   * @param filterArg Argument that filters input to aggregate function, or -1
    * @param refs      Array of expressions which will be the projected by the
    *                  result of this rule. Those relating to this arg list will
-   *                  be modified
-   * @return Relational expression
+   *                  be modified  @return Relational expression
    */
-  private RelNode doRewrite(
-      Aggregate aggregate,
-      RelNode left,
-      List<Integer> argList,
-      List<RexInputRef> refs) {
+  private RelNode doRewrite(Aggregate aggregate, RelNode left,
+      List<Integer> argList, int filterArg, List<RexInputRef> refs) {
     final RexBuilder rexBuilder = aggregate.getCluster().getRexBuilder();
     final List<RelDataTypeField> leftFields;
     if (left == null) {
@@ -281,9 +273,9 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
 
     // Project the columns of the GROUP BY plus the arguments
     // to the agg function.
-    Map<Integer, Integer> sourceOf = new HashMap<Integer, Integer>();
+    final Map<Integer, Integer> sourceOf = new HashMap<>();
     final Aggregate distinct =
-        createSelectDistinct(aggregate, argList, sourceOf);
+        createSelectDistinct(aggregate, argList, filterArg, sourceOf);
 
     // Now compute the aggregate functions on top of the distinct dataset.
     // Each distinct agg becomes a non-distinct call to the corresponding
@@ -291,7 +283,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
     //   "COUNT(DISTINCT e.sal)"
     // becomes
     //   "COUNT(distinct_e.sal)".
-    List<AggregateCall> aggCallList = new ArrayList<AggregateCall>();
+    final List<AggregateCall> aggCallList = new ArrayList<>();
     final List<AggregateCall> aggCalls = aggregate.getAggCallList();
 
     final int groupAndIndicatorCount =
@@ -313,18 +305,16 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
 
       // Re-map arguments.
       final int argCount = aggCall.getArgList().size();
-      final List<Integer> newArgs = new ArrayList<Integer>(argCount);
+      final List<Integer> newArgs = new ArrayList<>(argCount);
       for (int j = 0; j < argCount; j++) {
         final Integer arg = aggCall.getArgList().get(j);
         newArgs.add(sourceOf.get(arg));
       }
+      final int newFilterArg =
+          aggCall.filterArg >= 0 ? sourceOf.get(aggCall.filterArg) : -1;
       final AggregateCall newAggCall =
-          new AggregateCall(
-              aggCall.getAggregation(),
-              false,
-              newArgs,
-              aggCall.getType(),
-              aggCall.getName());
+          AggregateCall.create(aggCall.getAggregation(), false, newArgs,
+              newFilterArg, aggCall.getType(), aggCall.getName());
       assert refs.get(i) == null;
       if (left == null) {
         refs.set(i,
@@ -385,7 +375,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
       final AggregateCall aggCall = newAggCalls.get(i);
 
       // Ignore agg calls which are not distinct or have the wrong set
-      // arguments. If we're rewriting aggs whose args are {sal}, we will
+      // arguments. If we're rewriting aggregates whose args are {sal}, we will
       // rewrite COUNT(DISTINCT sal) and SUM(DISTINCT sal) but ignore
       // COUNT(DISTINCT gender) or SUM(sal).
       if (!aggCall.isDistinct()) {
@@ -397,18 +387,14 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
 
       // Re-map arguments.
       final int argCount = aggCall.getArgList().size();
-      final List<Integer> newArgs = new ArrayList<Integer>(argCount);
+      final List<Integer> newArgs = new ArrayList<>(argCount);
       for (int j = 0; j < argCount; j++) {
         final Integer arg = aggCall.getArgList().get(j);
         newArgs.add(sourceOf.get(arg));
       }
       final AggregateCall newAggCall =
-          new AggregateCall(
-              aggCall.getAggregation(),
-              false,
-              newArgs,
-              aggCall.getType(),
-              aggCall.getName());
+          AggregateCall.create(aggCall.getAggregation(), false, newArgs, -1,
+              aggCall.getType(), aggCall.getName());
       newAggCalls.set(i, newAggCall);
     }
   }
@@ -427,7 +413,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
    * from t group by f0</pre>
    * </blockquote>
    *
-   * and the arglist
+   * and the argument list
    *
    * <blockquote>{2}</blockquote>
    *
@@ -444,17 +430,15 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
    *
    * @param aggregate Aggregate relational expression
    * @param argList   Ordinals of columns to make distinct
+   * @param filterArg Ordinal of column to filter on, or -1
    * @param sourceOf  Out parameter, is populated with a map of where each
    *                  output field came from
    * @return Aggregate relational expression which projects the required
    * columns
    */
-  private static Aggregate createSelectDistinct(
-      Aggregate aggregate,
-      List<Integer> argList,
-      Map<Integer, Integer> sourceOf) {
-    final List<Pair<RexNode, String>> projects =
-        new ArrayList<Pair<RexNode, String>>();
+  private static Aggregate createSelectDistinct(Aggregate aggregate,
+      List<Integer> argList, int filterArg, Map<Integer, Integer> sourceOf) {
+    final List<Pair<RexNode, String>> projects = new ArrayList<>();
     final RelNode child = aggregate.getInput();
     final List<RelDataTypeField> childFields =
         child.getRowType().getFieldList();
@@ -463,6 +447,29 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
       projects.add(RexInputRef.of2(i, childFields));
     }
     for (Integer arg : argList) {
+      if (filterArg >= 0) {
+        // Implement
+        //   agg(DISTINCT arg) FILTER $f
+        // by generating
+        //   SELECT DISTINCT ... CASE WHEN $f THEN arg ELSE NULL END AS arg
+        // and then applying
+        //   agg(arg)
+        // as usual.
+        //
+        // It works except for (rare) agg functions that need to see null
+        // values.
+        final RexBuilder rexBuilder = aggregate.getCluster().getRexBuilder();
+        final RexInputRef filterRef = RexInputRef.of(filterArg, childFields);
+        final Pair<RexNode, String> argRef = RexInputRef.of2(arg, childFields);
+        RexNode condition =
+            rexBuilder.makeCall(SqlStdOperatorTable.CASE, filterRef,
+                argRef.left,
+                rexBuilder.ensureType(argRef.left.getType(),
+                    rexBuilder.constantNull(), true));
+        sourceOf.put(arg, projects.size());
+        projects.add(Pair.of(condition, "i$" + argRef.right));
+        continue;
+      }
       if (sourceOf.get(arg) != null) {
         continue;
       }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java
index dfa325d..302a02e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateFilterTransposeRule.java
@@ -137,8 +137,9 @@ public class AggregateFilterTransposeRule extends RelOptRule {
           return;
         }
         topAggCallList.add(
-            new AggregateCall(rollup, aggregateCall.isDistinct(),
-                ImmutableList.of(i++), aggregateCall.type, aggregateCall.name));
+            AggregateCall.create(rollup, aggregateCall.isDistinct(),
+                ImmutableList.of(i++), -1, aggregateCall.type,
+                aggregateCall.name));
       }
       final Aggregate topAggregate =
           aggregate.copy(aggregate.getTraitSet(), newFilter,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java
index 91e33f2..e581e1a 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectMergeRule.java
@@ -103,7 +103,17 @@ public class AggregateProjectMergeRule extends RelOptRule {
           return null;
         }
       }
-      aggCalls.add(aggregateCall.copy(newArgs.build()));
+      final int newFilterArg;
+      if (aggregateCall.filterArg >= 0) {
+        final RexNode rex = project.getProjects().get(aggregateCall.filterArg);
+        if (!(rex instanceof RexInputRef)) {
+          return null;
+        }
+        newFilterArg = ((RexInputRef) rex).getIndex();
+      } else {
+        newFilterArg = -1;
+      }
+      aggCalls.add(aggregateCall.copy(newArgs.build(), newFilterArg));
     }
 
     final Aggregate newAggregate =

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java
index c7667a6..75bb99e 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateProjectPullUpConstantsRule.java
@@ -136,8 +136,8 @@ public class AggregateProjectPullUpConstantsRule extends RelOptRule {
           new ArrayList<AggregateCall>();
       for (AggregateCall aggCall : aggregate.getAggCallList()) {
         newAggCalls.add(
-            aggCall.adaptTo(input, aggCall.getArgList(), groupCount,
-                newGroupCount));
+            aggCall.adaptTo(input, aggCall.getArgList(), aggCall.filterArg,
+                groupCount, newGroupCount));
       }
       newAggregate =
           LogicalAggregate.create(input, false,
@@ -176,8 +176,11 @@ public class AggregateProjectPullUpConstantsRule extends RelOptRule {
           final Integer arg = aggCall.getArgList().get(j);
           args.add(mapping.getTarget(arg));
         }
+        final int filterArg = aggCall.filterArg < 0 ? aggCall.filterArg
+            : mapping.getTarget(aggCall.filterArg);
         newAggCalls.add(
-            aggCall.adaptTo(project, args, groupCount, newGroupCount));
+            aggCall.adaptTo(project, args, filterArg, groupCount,
+                newGroupCount));
       }
 
       // Aggregate on projection.

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java
index 95ca9c8..2a79f14 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateReduceFunctionsRule.java
@@ -281,10 +281,10 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             avgInputType.isNullable() || nGroups == 0);
     SqlAggFunction sumAgg = new SqlSumAggFunction(sumType);
     AggregateCall sumCall =
-        new AggregateCall(
-            sumAgg,
+        AggregateCall.create(sumAgg,
             oldCall.isDistinct(),
             oldCall.getArgList(),
+            oldCall.filterArg,
             sumType,
             null);
     AggregateCall countCall =
@@ -292,6 +292,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             SqlStdOperatorTable.COUNT,
             oldCall.isDistinct(),
             oldCall.getArgList(),
+            oldCall.filterArg,
             oldAggRel.getGroupCount(),
             oldAggRel.getInput(),
             null,
@@ -338,17 +339,14 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
         typeFactory.createTypeWithNullability(
             argType, argType.isNullable());
     final AggregateCall sumZeroCall =
-        new AggregateCall(
-            SqlStdOperatorTable.SUM0,
-            oldCall.isDistinct(),
-            oldCall.getArgList(),
-            sumType,
-            null);
+        AggregateCall.create(SqlStdOperatorTable.SUM0, oldCall.isDistinct(),
+            oldCall.getArgList(), oldCall.filterArg, sumType, null);
     final AggregateCall countCall =
         AggregateCall.create(
             SqlStdOperatorTable.COUNT,
             oldCall.isDistinct(),
             oldCall.getArgList(),
+            oldCall.filterArg,
             oldAggRel.getGroupCount(),
             oldAggRel,
             null,
@@ -425,10 +423,11 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             argType,
             true);
     final AggregateCall sumArgSquaredAggCall =
-        new AggregateCall(
+        AggregateCall.create(
             new SqlSumAggFunction(sumType),
             oldCall.isDistinct(),
             ImmutableIntList.of(argSquaredOrdinal),
+            oldCall.filterArg,
             sumType,
             null);
     final RexNode sumArgSquared =
@@ -440,10 +439,11 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             ImmutableList.of(argType));
 
     final AggregateCall sumArgAggCall =
-        new AggregateCall(
+        AggregateCall.create(
             new SqlSumAggFunction(sumType),
             oldCall.isDistinct(),
             ImmutableIntList.of(argOrdinal),
+            oldCall.filterArg,
             sumType,
             null);
     final RexNode sumArg =
@@ -463,6 +463,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             SqlStdOperatorTable.COUNT,
             oldCall.isDistinct(),
             oldCall.getArgList(),
+            oldCall.filterArg,
             oldAggRel.getGroupCount(),
             oldAggRel.getInput(),
             null,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index 32b16dc..335293a 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -290,7 +290,7 @@ public class RexBuilder {
       final List<Integer> args = aggCall.getArgList();
       final List<Integer> nullableArgs = nullableArgs(args, aggArgTypes);
       if (!nullableArgs.equals(args)) {
-        aggCall = aggCall.copy(nullableArgs);
+        aggCall = aggCall.copy(nullableArgs, aggCall.filterArg);
       }
     }
     RexNode rex = aggCallMapping.get(aggCall);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 245d00a..1e8badf 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -273,6 +273,9 @@ public interface CalciteResource {
   @BaseMessage("Aggregate expressions cannot be nested")
   ExInst<SqlValidatorException> nestedAggIllegal();
 
+  @BaseMessage("FILTER must not contain aggregate expression")
+  ExInst<SqlValidatorException> aggregateInFilterIllegal();
+
   @BaseMessage("Aggregate expression is illegal in ORDER BY clause of non-aggregating SELECT")
   ExInst<SqlValidatorException> aggregateIllegalInOrderBy();
 
@@ -285,6 +288,9 @@ public interface CalciteResource {
   @BaseMessage("OVER must be applied to aggregate function")
   ExInst<SqlValidatorException> overNonAggregate();
 
+  @BaseMessage("FILTER must be applied to aggregate function")
+  ExInst<SqlValidatorException> filterNonAggregate();
+
   @BaseMessage("Cannot override window attribute")
   ExInst<SqlValidatorException> cannotOverrideWindowAttribute();
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql/SqlAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlAggFunction.java b/core/src/main/java/org/apache/calcite/sql/SqlAggFunction.java
index 6949d94..4f51be2 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlAggFunction.java
@@ -72,7 +72,7 @@ public abstract class SqlAggFunction extends SqlFunction {
       SqlValidatorScope scope,
       SqlValidatorScope operandScope) {
     super.validateCall(call, validator, scope, operandScope);
-    validator.validateAggregateParams(call, scope);
+    validator.validateAggregateParams(call, null, scope);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql/SqlFilterOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlFilterOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlFilterOperator.java
new file mode 100644
index 0000000..5661da3
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/SqlFilterOperator.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorScope;
+
+import static org.apache.calcite.util.Static.RESOURCE;
+
+/**
+ * An operator that applies a filter before rows are included in an aggregate
+ * function.
+ *
+ * <p>Operands are as follows:</p>
+ *
+ * <ul>
+ * <li>0: a call to an aggregate function ({@link SqlCall})
+ * <li>1: predicate
+ * </ul>
+ */
+public class SqlFilterOperator extends SqlBinaryOperator {
+  //~ Constructors -----------------------------------------------------------
+
+  public SqlFilterOperator() {
+    super("FILTER", SqlKind.FILTER, 2, true, ReturnTypes.ARG0_FORCE_NULLABLE,
+        null, OperandTypes.ANY_ANY);
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+
+  @Override public void unparse(SqlWriter writer, SqlCall call, int leftPrec,
+      int rightPrec) {
+    assert call.operandCount() == 2;
+    final SqlWriter.Frame frame =
+        writer.startList(SqlWriter.FrameTypeEnum.SIMPLE);
+    call.operand(0).unparse(writer, leftPrec, getLeftPrec());
+    writer.sep(getName());
+    writer.sep("(");
+    writer.sep("WHERE");
+    call.operand(1).unparse(writer, getRightPrec(), rightPrec);
+    writer.sep(")");
+    writer.endList(frame);
+  }
+
+  public void validateCall(
+      SqlCall call,
+      SqlValidator validator,
+      SqlValidatorScope scope,
+      SqlValidatorScope operandScope) {
+    assert call.getOperator() == this;
+    assert call.operandCount() == 2;
+    SqlCall aggCall = call.operand(0);
+    if (!aggCall.getOperator().isAggregator()) {
+      throw validator.newValidationError(aggCall,
+          RESOURCE.filterNonAggregate());
+    }
+    final SqlNode condition = call.operand(1);
+    validator.validateAggregateParams(aggCall, condition, scope);
+
+    final RelDataType type = validator.deriveType(scope, condition);
+    if (!SqlTypeUtil.inBooleanFamily(type)) {
+      throw validator.newValidationError(condition,
+          RESOURCE.condMustBeBoolean("FILTER"));
+    }
+  }
+
+  public RelDataType deriveType(
+      SqlValidator validator,
+      SqlValidatorScope scope,
+      SqlCall call) {
+    // Validate type of the inner aggregate call
+    validateOperands(validator, scope, call);
+
+    // Assume the first operand is an aggregate call and derive its type.
+    SqlNode agg = call.operand(0);
+
+    if (!(agg instanceof SqlCall)) {
+      throw new IllegalStateException("Argument to SqlOverOperator"
+          + " should be SqlCall, got " + agg.getClass() + ": " + agg);
+    }
+
+    final SqlCall aggCall = (SqlCall) agg;
+
+    // Pretend that group-count is 0. This tells the aggregate function that it
+    // might be invoked with 0 rows in a group. Most aggregate functions will
+    // return NULL in this case.
+    SqlCallBinding opBinding = new SqlCallBinding(validator, scope, aggCall) {
+      @Override public int getGroupCount() {
+        return 0;
+      }
+    };
+
+    RelDataType ret = aggCall.getOperator().inferReturnType(opBinding);
+
+    // Copied from validateOperands
+    validator.setValidatedNodeType(call, ret);
+    validator.setValidatedNodeType(agg, ret);
+    return ret;
+  }
+}
+
+// End SqlFilterOperator.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql/SqlKind.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index 3843231..244a241 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -187,6 +187,11 @@ public enum SqlKind {
   OVER,
 
   /**
+   * FILTER operator
+   */
+  FILTER,
+
+  /**
    * Window specification
    */
   WINDOW,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql/SqlOverOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOverOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlOverOperator.java
index 6255aaa..f4f5b12 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlOverOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlOverOperator.java
@@ -68,7 +68,7 @@ public class SqlOverOperator extends SqlBinaryOperator {
       throw validator.newValidationError(aggCall, RESOURCE.overNonAggregate());
     }
     validator.validateWindow(call.operand(1), scope, aggCall);
-    validator.validateAggregateParams(aggCall, scope);
+    validator.validateAggregateParams(aggCall, null, scope);
   }
 
   public RelDataType deriveType(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index 0a86799..0f7ca67 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -21,6 +21,7 @@ import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlAsOperator;
 import org.apache.calcite.sql.SqlBinaryOperator;
 import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlFilterOperator;
 import org.apache.calcite.sql.SqlFunction;
 import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlFunctionalOperator;
@@ -149,6 +150,10 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
    */
   public static final SqlAsOperator AS = new SqlAsOperator();
 
+  /** <code>FILTER</code> operator filters which rows are included in an
+   *  aggregate function. */
+  public static final SqlFilterOperator FILTER = new SqlFilterOperator();
+
   /** {@code CUBE} operator, occurs within {@code GROUP BY} clause
    * or nested within a {@code GROUPING SETS}. */
   public static final SqlInternalOperator CUBE =

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java b/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java
index 1846618..f8c2776 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java
@@ -144,6 +144,10 @@ class AggChecker extends SqlBasicVisitor<Void> {
       // BY deptno'
       return null;
     }
+    if (call.getKind() == SqlKind.FILTER) {
+      call.operand(0).accept(this);
+      return null;
+    }
     if (isGroupExpr(call)) {
       // This call matches an expression in the GROUP BY clause.
       return null;

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/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 a02b43f..bd6d0b6 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
@@ -282,10 +282,12 @@ public interface SqlValidator {
   /**
    * Validates parameters for aggregate function.
    *
-   * @param aggFunction function containing COLUMN_LIST parameter
+   * @param aggCall     Function containing COLUMN_LIST parameter
+   * @param filter      Filter, or null
    * @param scope       Syntactic scope
    */
-  void validateAggregateParams(SqlCall aggFunction, SqlValidatorScope scope);
+  void validateAggregateParams(SqlCall aggCall, SqlNode filter,
+      SqlValidatorScope scope);
 
   /**
    * Validates a COLUMN_LIST parameter

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index a0e7fdd..4a47c78 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -3947,18 +3947,21 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     call.validate(this, scope);
   }
 
-  public void validateAggregateParams(
-      SqlCall aggFunction,
+  public void validateAggregateParams(SqlCall aggCall, SqlNode filter,
       SqlValidatorScope scope) {
     // For agg(expr), expr cannot itself contain aggregate function
     // invocations.  For example, SUM(2*MAX(x)) is illegal; when
     // we see it, we'll report the error for the SUM (not the MAX).
     // For more than one level of nesting, the error which results
     // depends on the traversal order for validation.
-    for (SqlNode param : aggFunction.getOperandList()) {
-      final SqlNode agg = aggOrOverFinder.findAgg(param);
+    for (SqlNode param : aggCall.getOperandList()) {
       if (aggOrOverFinder.findAgg(param) != null) {
-        throw newValidationError(aggFunction, RESOURCE.nestedAggIllegal());
+        throw newValidationError(aggCall, RESOURCE.nestedAggIllegal());
+      }
+    }
+    if (filter != null) {
+      if (aggOrOverFinder.findAgg(filter) != null) {
+        throw newValidationError(filter, RESOURCE.aggregateInFilterIllegal());
       }
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index 34e2f27..627c5b0 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -562,9 +562,11 @@ public class RelDecorrelator implements ReflectiveVisitor {
       for (int oldPos : oldAggArgs) {
         aggArgs.add(combinedMap.get(oldPos));
       }
+      final int filterArg = oldAggCall.filterArg < 0 ? oldAggCall.filterArg
+          : combinedMap.get(oldAggCall.filterArg);
 
       newAggCalls.add(
-          oldAggCall.adaptTo(newProjectRel, aggArgs,
+          oldAggCall.adaptTo(newProjectRel, aggArgs, filterArg,
               oldGroupKeyCount, newGroupKeyCount));
 
       // The old to new output position mapping will be the same as that
@@ -2297,23 +2299,24 @@ public class RelDecorrelator implements ReflectiveVisitor {
       k = -1;
       for (AggregateCall aggCall : aggCalls) {
         ++k;
-        final List<Integer> aggArgs = aggCall.getArgList();
-        final List<Integer> newAggArgs;
+        final List<Integer> argList;
 
         if (isCountStar.contains(k)) {
           // this is a count(*), transform it to count(nullIndicator)
           // the null indicator is located at the end
-          newAggArgs = Collections.singletonList(nullIndicatorPos);
+          argList = Collections.singletonList(nullIndicatorPos);
         } else {
-          newAggArgs = Lists.newArrayList();
+          argList = Lists.newArrayList();
 
-          for (Integer aggArg : aggArgs) {
-            newAggArgs.add(aggArg + groupCount);
+          for (Integer aggArg : aggCall.getArgList()) {
+            argList.add(aggArg + groupCount);
           }
         }
 
+        int filterArg = aggCall.filterArg < 0 ? aggCall.filterArg
+            : aggCall.filterArg + groupCount;
         newAggCalls.add(
-            aggCall.adaptTo(joinOutputProjRel, newAggArgs,
+            aggCall.adaptTo(joinOutputProjRel, argList, filterArg,
                 aggRel.getGroupCount(), groupCount));
       }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/f5434a49/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
index b8701e5..99975b3 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
@@ -464,8 +464,8 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     RexNode newConditionExpr =
         conditionExpr.accept(shuttle);
 
-    final RelNode newFilter = filterFactory.createFilter(newInput,
-        newConditionExpr);
+    final RelNode newFilter = filterFactory.createFilter(
+        newInput, newConditionExpr);
 
     // The result has the same mapping as the input gave us. Sometimes we
     // return fields that the consumer didn't ask for, because the filter
@@ -514,8 +514,8 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     final RelCollation newCollation =
         sort.getTraitSet().canonize(RexUtil.apply(inputMapping, collation));
     final RelTraitSet newTraitSet = sort.getTraitSet().replace(newCollation);
-    final RelNode newSort = sortFactory.createSort(newTraitSet, newInput,
-        newCollation, sort.offset, sort.fetch);
+    final RelNode newSort = sortFactory.createSort(
+        newTraitSet, newInput, newCollation, sort.offset, sort.fetch);
 
     // The result has the same mapping as the input gave us. Sometimes we
     // return fields that the consumer didn't ask for, because the filter
@@ -764,6 +764,9 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
       for (int i : aggCall.getArgList()) {
         inputFieldsUsed.set(i);
       }
+      if (aggCall.filterArg >= 0) {
+        inputFieldsUsed.set(aggCall.filterArg);
+      }
     }
 
     // Create input with trimmed columns.
@@ -830,13 +833,13 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
     }
 
     // Now create new agg calls, and populate mapping for them.
-    final List<AggregateCall> newAggCallList =
-        new ArrayList<AggregateCall>();
+    final List<AggregateCall> newAggCallList = new ArrayList<>();
     j = groupCount + indicatorCount;
     for (AggregateCall aggCall : aggregate.getAggCallList()) {
       if (fieldsUsed.get(j)) {
         AggregateCall newAggCall =
-            aggCall.copy(Mappings.apply2(inputMapping, aggCall.getArgList()));
+            aggCall.copy(Mappings.apply2(inputMapping, aggCall.getArgList()),
+                Mappings.apply(inputMapping, aggCall.filterArg));
         if (newAggCall.equals(aggCall)) {
           newAggCall = aggCall; // immutable -> canonize to save space
         }