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 2018/11/08 18:00:56 UTC

[1/6] calcite git commit: [CALCITE-2654] In RelBuilder, add a fluent API for building complex aggregate calls

Repository: calcite
Updated Branches:
  refs/heads/master 425fa7b7f -> ebc43d928


[CALCITE-2654] In RelBuilder, add a fluent API for building complex aggregate calls

To interface AggCall in RelBuilder, add methods distinct(boolean),
filter(RexNode), approximate(boolean), alias(String). And simplify the
RelBuilder.aggregateCall method to just two arguments:
aggregateCall(op, operands). Thus you only specify the arguments that
are of interest. Similar changes to count, countStar, min, max, sum,
avg.


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

Branch: refs/heads/master
Commit: 4cc46130f71d21e4f5b76f6e645380cd83d2c921
Parents: 425fa7b
Author: Julian Hyde <jh...@apache.org>
Authored: Sat Nov 3 19:59:11 2018 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Wed Nov 7 16:33:06 2018 -0800

----------------------------------------------------------------------
 .../rel/rules/AbstractMaterializedViewRule.java |  21 +-
 .../AggregateExpandDistinctAggregatesRule.java  |  21 +-
 .../rel/rules/AggregateExtractProjectRule.java  |   9 +-
 .../calcite/rel/rules/SubQueryRemoveRule.java   |   8 +-
 .../apache/calcite/sql2rel/RelFieldTrimmer.java |   8 +-
 .../org/apache/calcite/tools/PigRelBuilder.java |   3 +-
 .../org/apache/calcite/tools/RelBuilder.java    | 249 ++++++++++++++++---
 .../rel/rel2sql/RelToSqlConverterTest.java      |   4 +-
 .../org/apache/calcite/test/RelBuilderTest.java |  66 +++--
 site/_docs/algebra.md                           |  22 +-
 10 files changed, 298 insertions(+), 113 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
index 3a82f95..68a9382 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
@@ -1194,13 +1194,14 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
           // Cannot rollup this aggregate, bail out
           return null;
         }
+        final RexInputRef operand =
+            rexBuilder.makeInputRef(relBuilder.peek(),
+                aggregate.getGroupCount() + i);
         aggregateCalls.add(
-            relBuilder.aggregateCall(
-                rollupAgg,
-                aggCall.isDistinct(), aggCall.isApproximate(), null,
-                aggCall.name,
-                rexBuilder.makeInputRef(relBuilder.peek(),
-                    aggregate.getGroupCount() + i)));
+            relBuilder.aggregateCall(rollupAgg, operand)
+                .distinct(aggCall.isDistinct())
+                .approximate(aggCall.isApproximate())
+                .as(aggCall.name));
       }
       RelNode prevNode = relBuilder.peek();
       RelNode result = relBuilder
@@ -1466,10 +1467,12 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
                 // Cannot rollup this aggregate, bail out
                 return null;
               }
+              final RexInputRef operand = rexBuilder.makeInputRef(input, k);
               aggregateCalls.add(
-                  relBuilder.aggregateCall(
-                      rollupAgg, queryAggCall.isDistinct(), queryAggCall.isApproximate(),
-                      null, queryAggCall.name, rexBuilder.makeInputRef(input, k)));
+                  relBuilder.aggregateCall(rollupAgg, operand)
+                      .approximate(queryAggCall.isApproximate())
+                      .distinct(queryAggCall.isDistinct())
+                      .as(queryAggCall.name));
               rewritingMapping.set(k, sourceIdx);
               added = true;
             }

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/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 d150989..6d974d8 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
@@ -274,14 +274,15 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
 
     // Add the distinct aggregate column(s) to the group-by columns,
     // if not already a part of the group-by
-    final SortedSet<Integer> bottomGroupSet = new TreeSet<>();
-    bottomGroupSet.addAll(aggregate.getGroupSet().asList());
+    final SortedSet<Integer> bottomGroups = new TreeSet<>();
+    bottomGroups.addAll(aggregate.getGroupSet().asList());
     for (AggregateCall aggCall : originalAggCalls) {
       if (aggCall.isDistinct()) {
-        bottomGroupSet.addAll(aggCall.getArgList());
+        bottomGroups.addAll(aggCall.getArgList());
         break;  // since we only have single distinct call
       }
     }
+    final ImmutableBitSet bottomGroupSet = ImmutableBitSet.of(bottomGroups);
 
     // Generate the intermediate aggregate B, the one on the bottom that converts
     // a distinct call to group by call.
@@ -295,16 +296,15 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
         final AggregateCall newCall =
             AggregateCall.create(aggCall.getAggregation(), false,
                 aggCall.isApproximate(), aggCall.getArgList(), -1,
-                ImmutableBitSet.of(bottomGroupSet).cardinality(),
+                bottomGroupSet.cardinality(),
                 relBuilder.peek(), null, aggCall.name);
         bottomAggregateCalls.add(newCall);
       }
     }
     // Generate the aggregate B (see the reference example above)
     relBuilder.push(
-        aggregate.copy(
-            aggregate.getTraitSet(), relBuilder.build(),
-            false, ImmutableBitSet.of(bottomGroupSet), null, bottomAggregateCalls));
+        aggregate.copy(aggregate.getTraitSet(), relBuilder.build(),
+            false, bottomGroupSet, null, bottomAggregateCalls));
 
     // Add aggregate A (see the reference example above), the top aggregate
     // to handle the rest of the aggregation that the bottom aggregate hasn't handled
@@ -316,7 +316,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
       if (aggCall.isDistinct()) {
         List<Integer> newArgList = new ArrayList<>();
         for (int arg : aggCall.getArgList()) {
-          newArgList.add(bottomGroupSet.headSet(arg).size());
+          newArgList.add(bottomGroups.headSet(arg).size());
         }
         newCall =
             AggregateCall.create(aggCall.getAggregation(),
@@ -332,7 +332,8 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
         // If aggregate B had a COUNT aggregate call the corresponding aggregate at
         // aggregate A must be SUM. For other aggregates, it remains the same.
         final List<Integer> newArgs =
-            Lists.newArrayList(bottomGroupSet.size() + nonDistinctAggCallProcessedSoFar);
+            Lists.newArrayList(bottomGroups.size()
+                + nonDistinctAggCallProcessedSoFar);
         if (aggCall.getAggregation().getKind() == SqlKind.COUNT) {
           newCall =
               AggregateCall.create(new SqlSumEmptyIsZeroAggFunction(), false,
@@ -357,7 +358,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
     // output), minus the distinct aggCall's input.
     final Set<Integer> topGroupSet = new HashSet<>();
     int groupSetToAdd = 0;
-    for (int bottomGroup : bottomGroupSet) {
+    for (int bottomGroup : bottomGroups) {
       if (originalGroupSet.get(bottomGroup)) {
         topGroupSet.add(groupSetToAdd);
       }

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
index 5559c97..08b12dd 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
@@ -116,9 +116,12 @@ public class AggregateExtractProjectRule extends RelOptRule {
       final RexNode filterArg = aggCall.filterArg < 0 ? null
           : relBuilder.field(Mappings.apply(mapping, aggCall.filterArg));
       newAggCallList.add(
-          relBuilder.aggregateCall(aggCall.getAggregation(),
-              aggCall.isDistinct(), aggCall.isApproximate(),
-              filterArg, aggCall.name, args));
+          relBuilder.aggregateCall(aggCall.getAggregation(), args)
+              .distinct(aggCall.isDistinct())
+              .filter(filterArg)
+              .approximate(aggCall.isApproximate())
+              .approximate(aggCall.isApproximate())
+              .as(aggCall.name));
     }
 
     final RelBuilder.GroupKey groupKey =

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
index 974c57d..39bccbf 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
@@ -124,8 +124,8 @@ public abstract class SubQueryRemoveRule extends RelOptRule {
         ImmutableBitSet.of());
     if (unique == null || !unique) {
       builder.aggregate(builder.groupKey(),
-          builder.aggregateCall(SqlStdOperatorTable.SINGLE_VALUE, false,
-              false, null, null, builder.field(0)));
+          builder.aggregateCall(SqlStdOperatorTable.SINGLE_VALUE,
+              builder.field(0)));
     }
     builder.join(JoinRelType.LEFT, builder.literal(true), variablesSet);
     return field(builder, inputCount, offset);
@@ -385,8 +385,8 @@ public abstract class SubQueryRemoveRule extends RelOptRule {
         // Builds the cross join
         builder.aggregate(builder.groupKey(),
             builder.count(false, "c"),
-            builder.aggregateCall(SqlStdOperatorTable.COUNT, false, false, null,
-                "ck", builder.fields()));
+            builder.aggregateCall(SqlStdOperatorTable.COUNT, builder.fields())
+                .as("ck"));
         builder.as("ct");
         if (!variablesSet.isEmpty()) {
           builder.join(JoinRelType.LEFT, builder.literal(true), variablesSet);

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/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 733ee02..e9031ba 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
@@ -867,9 +867,11 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
         final RexNode filterArg = aggCall.filterArg < 0 ? null
             : relBuilder.field(Mappings.apply(inputMapping, aggCall.filterArg));
         RelBuilder.AggCall newAggCall =
-            relBuilder.aggregateCall(aggCall.getAggregation(),
-                aggCall.isDistinct(), aggCall.isApproximate(),
-                filterArg, aggCall.name, args);
+            relBuilder.aggregateCall(aggCall.getAggregation(), args)
+                .distinct(aggCall.isDistinct())
+                .filter(filterArg)
+                .approximate(aggCall.isApproximate())
+                .as(aggCall.name);
         mapping.set(j, groupCount + indicatorCount + newAggCallList.size());
         newAggCallList.add(newAggCall);
       }

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java b/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java
index dcac18b..50738cd 100644
--- a/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/PigRelBuilder.java
@@ -146,8 +146,7 @@ public class PigRelBuilder extends RelBuilder {
           cluster.getRexBuilder().makeCall(peek(1, 0).getRowType(),
               SqlStdOperatorTable.ROW, fields());
       aggregate(groupKey.e,
-          aggregateCall(SqlStdOperatorTable.COLLECT, false, false, null,
-              getAlias(), row));
+          aggregateCall(SqlStdOperatorTable.COLLECT, row).as(getAlias()));
       if (groupKey.i < n - 1) {
         push(r);
         List<RexNode> predicates = new ArrayList<>();

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index d2b5d5d..323f65a 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -763,72 +763,129 @@ public class RelBuilder {
   @Deprecated // to be removed before 2.0
   public AggCall aggregateCall(SqlAggFunction aggFunction, boolean distinct,
       RexNode filter, String alias, RexNode... operands) {
-    return aggregateCall(aggFunction, distinct, false, filter, alias,
-        ImmutableList.copyOf(operands));
+    return aggregateCall(aggFunction, distinct, false, filter,
+        ImmutableList.of(), alias, ImmutableList.copyOf(operands));
   }
 
-  /** Creates a call to an aggregate function. */
+  @Deprecated // to be removed before 2.0
   public AggCall aggregateCall(SqlAggFunction aggFunction, boolean distinct,
       boolean approximate, RexNode filter, String alias, RexNode... operands) {
-    return aggregateCall(aggFunction, distinct, approximate, filter, alias,
-        ImmutableList.copyOf(operands));
+    return aggregateCall(aggFunction, distinct, approximate, filter,
+        ImmutableList.of(), alias, ImmutableList.copyOf(operands));
   }
 
   @Deprecated // to be removed before 2.0
   public AggCall aggregateCall(SqlAggFunction aggFunction, boolean distinct,
       RexNode filter, String alias, Iterable<? extends RexNode> operands) {
-    return aggregateCall(aggFunction, distinct, false, filter, alias, operands);
+    return aggregateCall(aggFunction, distinct, false, filter,
+        ImmutableList.of(), alias, ImmutableList.copyOf(operands));
   }
 
-  /** Creates a call to an aggregate function. */
+  @Deprecated // to be removed before 2.0
   public AggCall aggregateCall(SqlAggFunction aggFunction, boolean distinct,
       boolean approximate, RexNode filter, String alias,
       Iterable<? extends RexNode> operands) {
-    if (filter != null) {
-      if (filter.getType().getSqlTypeName() != SqlTypeName.BOOLEAN) {
-        throw RESOURCE.filterMustBeBoolean().ex();
-      }
-      if (filter.getType().isNullable()) {
-        filter = call(SqlStdOperatorTable.IS_TRUE, filter);
-      }
-    }
+    return aggregateCall(aggFunction, distinct, filter, alias, operands)
+        .approximate(approximate);
+  }
+
+  /** Creates a call to an aggregate function.
+   *
+   * <p>To add other operands, apply
+   * {@link AggCall#filter(RexNode...)},
+   * {@link AggCall#distinct()},
+   * {@link AggCall#as},
+   * {@link AggCall#sort} to the result. */
+  public AggCall aggregateCall(SqlAggFunction aggFunction,
+      Iterable<? extends RexNode> operands) {
+    return aggregateCall(aggFunction, false, false, null, ImmutableList.of(),
+        null, ImmutableList.copyOf(operands));
+  }
+
+  /** Creates a call to an aggregate function.
+   *
+   * <p>To add other operands, apply
+   * {@link AggCall#filter(RexNode...)},
+   * {@link AggCall#distinct()},
+   * {@link AggCall#as},
+   * {@link AggCall#sort} to the result. */
+  public AggCall aggregateCall(SqlAggFunction aggFunction,
+      RexNode... operands) {
+    return aggregateCall(aggFunction, false, false, null, ImmutableList.of(),
+        null, ImmutableList.copyOf(operands));
+  }
+
+  /** Creates a call to an aggregate function with all applicable operands. */
+  AggCall aggregateCall(SqlAggFunction aggFunction, boolean distinct,
+      boolean approximate, RexNode filter, ImmutableList<RexNode> orderKeys,
+      String alias, ImmutableList<RexNode> operands) {
     return new AggCallImpl(aggFunction, distinct, approximate, filter, alias,
-        ImmutableList.copyOf(operands));
+        operands, orderKeys);
+  }
+
+  /** Creates a call to the {@code COUNT} aggregate function. */
+  public AggCall count(RexNode... operands) {
+    return count(false, null, operands);
   }
 
-  /** Creates a call to the COUNT aggregate function. */
+  /** Creates a call to the {@code COUNT} aggregate function,
+   * optionally distinct and with an alias. */
   public AggCall count(boolean distinct, String alias, RexNode... operands) {
-    return aggregateCall(SqlStdOperatorTable.COUNT, distinct, false, null,
-        alias, operands);
+    return aggregateCall(SqlStdOperatorTable.COUNT, distinct, null, alias,
+        ImmutableList.copyOf(operands));
   }
 
-  /** Creates a call to the COUNT(*) aggregate function. */
+  /** Creates a call to the {@code COUNT(*)} aggregate function. */
   public AggCall countStar(String alias) {
-    return aggregateCall(SqlStdOperatorTable.COUNT, false, false, null, alias);
+    return count(false, alias);
   }
 
-  /** Creates a call to the SUM aggregate function. */
+  /** Creates a call to the {@code SUM} aggregate function. */
+  public AggCall sum(RexNode operand) {
+    return sum(false, null, operand);
+  }
+
+  /** Creates a call to the {@code SUM} aggregate function,
+   * optionally distinct and with an alias. */
   public AggCall sum(boolean distinct, String alias, RexNode operand) {
-    return aggregateCall(SqlStdOperatorTable.SUM, distinct, false, null, alias,
-        operand);
+    return aggregateCall(SqlStdOperatorTable.SUM, distinct, null, alias,
+        ImmutableList.of(operand));
   }
 
-  /** Creates a call to the AVG aggregate function. */
+  /** Creates a call to the {@code AVG} aggregate function. */
+  public AggCall avg(RexNode operand) {
+    return avg(false, null, operand);
+  }
+
+  /** Creates a call to the {@code AVG} aggregate function,
+   * optionally distinct and with an alias. */
   public AggCall avg(boolean distinct, String alias, RexNode operand) {
-    return aggregateCall(SqlStdOperatorTable.AVG, distinct, false, null, alias,
-        operand);
+    return aggregateCall(SqlStdOperatorTable.AVG, distinct, null, alias,
+        ImmutableList.of(operand));
   }
 
-  /** Creates a call to the MIN aggregate function. */
+  /** Creates a call to the {@code MIN} aggregate function. */
+  public AggCall min(RexNode operand) {
+    return min(null, operand);
+  }
+
+  /** Creates a call to the {@code MIN} aggregate function,
+   * optionally with an alias. */
   public AggCall min(String alias, RexNode operand) {
-    return aggregateCall(SqlStdOperatorTable.MIN, false, false, null, alias,
-        operand);
+    return aggregateCall(SqlStdOperatorTable.MIN, false, null, alias,
+        ImmutableList.of(operand));
+  }
+
+  /** Creates a call to the {@code MAX} aggregate function,
+   * optionally with an alias. */
+  public AggCall max(RexNode operand) {
+    return max(null, operand);
   }
 
-  /** Creates a call to the MAX aggregate function. */
+  /** Creates a call to the {@code MAX} aggregate function. */
   public AggCall max(String alias, RexNode operand) {
-    return aggregateCall(SqlStdOperatorTable.MAX, false, false, null, alias,
-        operand);
+    return aggregateCall(SqlStdOperatorTable.MAX, false, null, alias,
+        ImmutableList.of(operand));
   }
 
   // Methods for patterns
@@ -2041,6 +2098,32 @@ public class RelBuilder {
    *
    * @see RelBuilder#aggregateCall */
   public interface AggCall {
+    /** Returns a copy of this AggCall that applies a filter before aggregating
+     * values. */
+    AggCall filter(RexNode condition);
+
+    /** Returns a copy of this AggCall that sorts its input values by
+     * {@code orderKeys} before aggregating, as in SQL's {@code WITHIN GROUP}
+     * clause. */
+    AggCall sort(Iterable<RexNode> orderKeys);
+
+    /** Returns a copy of this AggCall that sorts its input values by
+     * {@code orderKeys} before aggregating, as in SQL's {@code WITHIN GROUP}
+     * clause. */
+    AggCall sort(RexNode... orderKeys);
+
+    /** Returns a copy of this AggCall that may return approximate results
+     * if {@code approximate} is true. */
+    AggCall approximate(boolean approximate);
+
+    /** Returns a copy of this AggCall with a given alias. */
+    AggCall as(String alias);
+
+    /** Returns a copy of this AggCall that is optionally distinct. */
+    AggCall distinct(boolean distinct);
+
+    /** Returns a copy of this AggCall that is distinct. */
+    AggCall distinct();
   }
 
   /** Information necessary to create the GROUP BY clause of an Aggregate.
@@ -2081,23 +2164,79 @@ public class RelBuilder {
   }
 
   /** Implementation of {@link AggCall}. */
-  private static class AggCallImpl implements AggCall {
+  private class AggCallImpl implements AggCall {
     private final SqlAggFunction aggFunction;
     private final boolean distinct;
     private final boolean approximate;
-    private final RexNode filter;
-    private final String alias;
-    private final ImmutableList<RexNode> operands;
+    private final RexNode filter; // may be null
+    private final String alias; // may be null
+    private final ImmutableList<RexNode> operands; // may be empty, never null
+    private final ImmutableList<RexNode> orderKeys; // may be empty, never null
 
     AggCallImpl(SqlAggFunction aggFunction, boolean distinct,
         boolean approximate, RexNode filter,
-        String alias, ImmutableList<RexNode> operands) {
-      this.aggFunction = aggFunction;
+        String alias, ImmutableList<RexNode> operands,
+        ImmutableList<RexNode> orderKeys) {
+      this.aggFunction = Objects.requireNonNull(aggFunction);
       this.distinct = distinct;
       this.approximate = approximate;
-      this.filter = filter;
       this.alias = alias;
-      this.operands = operands;
+      this.operands = Objects.requireNonNull(operands);
+      this.orderKeys = Objects.requireNonNull(orderKeys);
+      if (filter != null) {
+        if (filter.getType().getSqlTypeName() != SqlTypeName.BOOLEAN) {
+          throw RESOURCE.filterMustBeBoolean().ex();
+        }
+        if (filter.getType().isNullable()) {
+          filter = call(SqlStdOperatorTable.IS_TRUE, filter);
+        }
+      }
+      this.filter = filter;
+    }
+
+    public AggCall sort(Iterable<RexNode> orderKeys) {
+      final ImmutableList<RexNode> orderKeyList =
+          ImmutableList.copyOf(orderKeys);
+      return orderKeyList.equals(this.orderKeys)
+          ? this
+          : new AggCallImpl(aggFunction, distinct, approximate, filter,
+              alias, operands, orderKeyList);
+    }
+
+    public AggCall sort(RexNode... orderKeys) {
+      return sort(ImmutableList.copyOf(orderKeys));
+    }
+
+    public AggCall approximate(boolean approximate) {
+      return approximate == this.approximate
+          ? this
+          : new AggCallImpl(aggFunction, distinct, approximate, filter,
+              alias, operands, orderKeys);
+    }
+
+    public AggCall filter(RexNode condition) {
+      return Objects.equals(condition, this.filter)
+          ? this
+          : new AggCallImpl(aggFunction, distinct, approximate, condition,
+              alias, operands, orderKeys);
+    }
+
+    public AggCall as(String alias) {
+      return Objects.equals(alias, this.alias)
+          ? this
+          : new AggCallImpl(aggFunction, distinct, approximate, filter,
+              alias, operands, orderKeys);
+    }
+
+    public AggCall distinct(boolean distinct) {
+      return distinct == this.distinct
+          ? this
+          : new AggCallImpl(aggFunction, distinct, approximate, filter,
+              alias, operands, orderKeys);
+    }
+
+    public AggCall distinct() {
+      return distinct(true);
     }
   }
 
@@ -2109,6 +2248,34 @@ public class RelBuilder {
     AggCallImpl2(AggregateCall aggregateCall) {
       this.aggregateCall = Objects.requireNonNull(aggregateCall);
     }
+
+    public AggCall sort(Iterable<RexNode> orderKeys) {
+      throw new UnsupportedOperationException();
+    }
+
+    public AggCall sort(RexNode... orderKeys) {
+      throw new UnsupportedOperationException();
+    }
+
+    public AggCall approximate(boolean approximate) {
+      throw new UnsupportedOperationException();
+    }
+
+    public AggCall filter(RexNode condition) {
+      throw new UnsupportedOperationException();
+    }
+
+    public AggCall as(String alias) {
+      throw new UnsupportedOperationException();
+    }
+
+    public AggCall distinct(boolean distinct) {
+      throw new UnsupportedOperationException();
+    }
+
+    public AggCall distinct() {
+      throw new UnsupportedOperationException();
+    }
   }
 
   /** Collects the extra expressions needed for {@link #aggregate}.

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 70bf317..94b98b7 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -274,8 +274,8 @@ public class RelToSqlConverterTest {
     final RelNode root = builder
         .scan("EMP")
         .aggregate(builder.groupKey(),
-            builder.aggregateCall(SqlStdOperatorTable.SUM0, false, false, null,
-                "s", builder.field(3)))
+            builder.aggregateCall(SqlStdOperatorTable.SUM0, false, null, "s",
+                builder.field(3)))
         .build();
     final String expectedMysql = "SELECT COALESCE(SUM(`MGR`), 0) AS `s`\n"
         + "FROM `scott`.`EMP`";

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 3624204..e3d5b95 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -505,7 +505,6 @@ public class RelBuilderTest {
                     builder.literal(20)))
             .aggregate(builder.groupKey(0, 1, 2),
                 builder.aggregateCall(SqlStdOperatorTable.SUM,
-                    false, false, null, null,
                     builder.field(0)))
             .project(builder.field("c"),
                 builder.field("a"))
@@ -661,8 +660,7 @@ public class RelBuilderTest {
     RelNode root =
         builder.scan("EMP")
             .aggregate(builder.groupKey(),
-                builder.aggregateCall(SqlStdOperatorTable.COUNT, true, false,
-                    null, "C", builder.field("DEPTNO")))
+                builder.count(true, "C", builder.field("DEPTNO")))
             .build();
     final String expected = ""
         + "LogicalAggregate(group=[{}], C=[COUNT(DISTINCT $7)])\n"
@@ -684,12 +682,10 @@ public class RelBuilderTest {
                         builder.field(4),
                         builder.field(3)),
                     builder.field(1)),
-                builder.aggregateCall(SqlStdOperatorTable.COUNT, false, false,
-                    null, "C"),
-                builder.aggregateCall(SqlStdOperatorTable.SUM, false, false,
-                    null, "S",
+                builder.countStar("C"),
+                builder.sum(
                     builder.call(SqlStdOperatorTable.PLUS, builder.field(3),
-                        builder.literal(1))))
+                        builder.literal(1))).as("S"))
             .build();
     final String expected = ""
         + "LogicalAggregate(group=[{1, 8}], C=[COUNT()], S=[SUM($9)])\n"
@@ -711,12 +707,9 @@ public class RelBuilderTest {
     final RelBuilder builder = RelBuilder.create(config().build());
     RelNode root =
         builder.scan("EMP")
-            .aggregate(
-                builder.groupKey(builder.field(1)),
-                builder.aggregateCall(SqlStdOperatorTable.COUNT, false, false,
-                    null, "C"))
-            .aggregate(
-                builder.groupKey(builder.field(0)))
+            .aggregate(builder.groupKey(builder.field(1)),
+                builder.count().as("C"))
+            .aggregate(builder.groupKey(builder.field(0)))
             .build();
     final String expected = ""
         + "LogicalProject(ENAME=[$0])\n"
@@ -738,8 +731,7 @@ public class RelBuilderTest {
         builder.scan("EMP")
             .aggregate(
                 builder.groupKey(builder.field(1)),
-                builder.aggregateCall(SqlStdOperatorTable.COUNT, false, false,
-                    null, "C"))
+                builder.count().as("C"))
             .filter(
                 builder.call(SqlStdOperatorTable.GREATER_THAN, builder.field(1),
                     builder.literal(3)))
@@ -766,9 +758,11 @@ public class RelBuilderTest {
                 builder.groupKey(ImmutableBitSet.of(7),
                     ImmutableList.of(ImmutableBitSet.of(7),
                         ImmutableBitSet.of())),
-                builder.aggregateCall(SqlStdOperatorTable.COUNT, false, false,
-                    builder.call(SqlStdOperatorTable.GREATER_THAN,
-                        builder.field("EMPNO"), builder.literal(100)), "C"))
+                builder.count()
+                    .filter(
+                        builder.call(SqlStdOperatorTable.GREATER_THAN,
+                            builder.field("EMPNO"), builder.literal(100)))
+                    .as("C"))
             .build();
     final String expected = ""
         + "LogicalAggregate(group=[{7}], groups=[[{7}, {}]], C=[COUNT() FILTER $8])\n"
@@ -788,8 +782,9 @@ public class RelBuilderTest {
           builder.scan("EMP")
               .aggregate(
                   builder.groupKey(builder.field("DEPTNO")),
-                  builder.aggregateCall(SqlStdOperatorTable.SUM, false, false,
-                      builder.field("COMM"), "C", builder.field("SAL")))
+                  builder.sum(builder.field("SAL"))
+                      .filter(builder.field("COMM"))
+                      .as("C"))
               .build();
       fail("expected error, got " + root);
     } catch (CalciteException e) {
@@ -808,10 +803,11 @@ public class RelBuilderTest {
         builder.scan("EMP")
             .aggregate(
                 builder.groupKey(builder.field("DEPTNO")),
-                builder.aggregateCall(SqlStdOperatorTable.SUM, false, false,
-                    builder.call(SqlStdOperatorTable.LESS_THAN,
-                        builder.field("COMM"), builder.literal(100)), "C",
-                    builder.field("SAL")))
+                builder.sum(builder.field("SAL"))
+                    .filter(
+                        builder.call(SqlStdOperatorTable.LESS_THAN,
+                            builder.field("COMM"), builder.literal(100)))
+                    .as("C"))
             .build();
     final String expected = ""
         + "LogicalAggregate(group=[{7}], C=[SUM($5) FILTER $8])\n"
@@ -912,8 +908,8 @@ public class RelBuilderTest {
     RelNode root =
         builder.scan("EMP")
             .aggregate(builder.groupKey(6, 7),
-                builder.aggregateCall(SqlStdOperatorTable.GROUPING, false,
-                    false, null, "g", builder.field("DEPTNO")))
+                builder.aggregateCall(SqlStdOperatorTable.GROUPING,
+                    builder.field("DEPTNO")).as("g"))
             .build();
     final String expected = ""
         + "LogicalAggregate(group=[{6, 7}], g=[GROUPING($7)])\n"
@@ -927,8 +923,10 @@ public class RelBuilderTest {
       RelNode root =
           builder.scan("EMP")
               .aggregate(builder.groupKey(6, 7),
-                  builder.aggregateCall(SqlStdOperatorTable.GROUPING, true,
-                      false, null, "g", builder.field("DEPTNO")))
+                  builder.aggregateCall(SqlStdOperatorTable.GROUPING,
+                      builder.field("DEPTNO"))
+                      .distinct(true)
+                      .as("g"))
               .build();
       fail("expected error, got " + root);
     } catch (IllegalArgumentException e) {
@@ -942,9 +940,10 @@ public class RelBuilderTest {
       RelNode root =
           builder.scan("EMP")
               .aggregate(builder.groupKey(6, 7),
-                  builder.aggregateCall(SqlStdOperatorTable.GROUPING, false,
-                      false, builder.literal(true), "g",
-                      builder.field("DEPTNO")))
+                  builder.aggregateCall(SqlStdOperatorTable.GROUPING,
+                      builder.field("DEPTNO"))
+                      .filter(builder.literal(true))
+                      .as("g"))
               .build();
       fail("expected error, got " + root);
     } catch (IllegalArgumentException e) {
@@ -1482,8 +1481,7 @@ public class RelBuilderTest {
             .project(builder.field("DEPTNO"),
                 builder.literal(20))
             .aggregate(builder.groupKey(builder.field("EMP_alias", "DEPTNO")),
-                builder.aggregateCall(SqlStdOperatorTable.SUM, false, false,
-                    null, null, builder.field(1)))
+                builder.sum(builder.field(1)))
             .project(builder.alias(builder.field(1), "sum"),
                 builder.field("EMP_alias", "DEPTNO"))
             .build();

http://git-wip-us.apache.org/repos/asf/calcite/blob/4cc46130/site/_docs/algebra.md
----------------------------------------------------------------------
diff --git a/site/_docs/algebra.md b/site/_docs/algebra.md
index 2b5c66a..e5b505b 100644
--- a/site/_docs/algebra.md
+++ b/site/_docs/algebra.md
@@ -390,9 +390,21 @@ The following methods return an
 
 | Method              | Description
 |:------------------- |:-----------
-| `aggregateCall(op, distinct, approximate, filter, alias, expr...)`<br/>`aggregateCall(op, distinct, approximate, filter, alias, exprList)` | Creates a call to a given aggregate function, with an optional filter expression
-| `count(distinct, alias, expr...)` | Creates a call to the COUNT aggregate function
+| `aggregateCall(op, distinct, approximate, filter, orderKeys, alias, expr...)`<br/>`aggregateCall(op, distinct, approximate, filter, orderKeys, alias, exprList)` | Creates a call to a given aggregate function, with an optional filter expression and a list of optional ordering keys (for sorting input values before aggregation)
+| `count( [ distinct, alias, ] expr...)` | Creates a call to the COUNT aggregate function
 | `countStar(alias)` | Creates a call to the COUNT(*) aggregate function
-| `sum(distinct, alias, expr)` | Creates a call to the SUM aggregate function
-| `min(alias, expr)` | Creates a call to the MIN aggregate function
-| `max(alias, expr)` | Creates a call to the MAX aggregate function
+| `sum( [ distinct, alias, ] expr)` | Creates a call to the SUM aggregate function
+| `min( [ alias, ] expr)` | Creates a call to the MIN aggregate function
+| `max( [ alias, ] expr)` | Creates a call to the MAX aggregate function
+
+To further modify the `AggCall`, call its methods:
+
+| Method               | Description
+|:-------------------- |:-----------
+| `distinct()`         | Eliminates duplicate values before aggregating (see SQL `DISTINCT`)
+| `distinct(distinct)` | Eliminates duplicate values before aggregating if `distinct`
+| `as(alias)`          | Assigns a column alias to this expression (see SQL `AS`)
+| `filter(expr)`       | Filters rows before aggregating (see SQL `FILTER (WHERE ...)`)
+| `sort(expr, ...)`<br/>`sort(exprList)` | Sorts rows before aggregating (see SQL `WITHIN GROUP`)
+| `approximate(approximate)` | Allows approximate value for the aggregate of `approximate`
+


[2/6] calcite git commit: [CALCITE-2224] Support WITHIN GROUP clause for aggregate functions (Hongze Zhang)

Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
index c385c02..b6a7270 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
@@ -595,13 +595,12 @@ public class DruidRules {
 
       // Erase references to filters
       for (AggregateCall aggCall : aggregate.getAggCallList()) {
-        int newFilterArg = aggCall.filterArg;
-        if (!aggCall.hasFilter()
-                || (uniqueFilterRefs.size() == 1 && allHaveFilters) // filters get extracted
-                || project.getProjects().get(newFilterArg).isAlwaysTrue()) {
-          newFilterArg = -1;
+        if ((uniqueFilterRefs.size() == 1
+                && allHaveFilters) // filters get extracted
+            || project.getProjects().get(aggCall.filterArg).isAlwaysTrue()) {
+          aggCall = aggCall.copy(aggCall.getArgList(), -1, aggCall.collation);
         }
-        newCalls.add(aggCall.copy(aggCall.getArgList(), newFilterArg));
+        newCalls.add(aggCall);
       }
       aggregate = aggregate.copy(aggregate.getTraitSet(), aggregate.getInput(),
               aggregate.indicator, aggregate.getGroupSet(), aggregate.getGroupSets(),

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 98fd037..4d238ff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -134,7 +134,7 @@ limitations under the License.
     <slf4j.version>1.7.13</slf4j.version>
     <log4j2.version>2.11.0</log4j2.version>
     <spark.version>2.2.0</spark.version>
-    <sqlline.version>1.5.0</sqlline.version>
+    <sqlline.version>1.6.0-SNAPSHOT</sqlline.version>
     <tpcds.version>1.2</tpcds.version>
     <xalan.version>2.7.1</xalan.version>
     <xerces.version>2.9.1</xerces.version>

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/site/_docs/algebra.md
----------------------------------------------------------------------
diff --git a/site/_docs/algebra.md b/site/_docs/algebra.md
index e5b505b..8ff2339 100644
--- a/site/_docs/algebra.md
+++ b/site/_docs/algebra.md
@@ -216,7 +216,7 @@ If you have a particular `RelNode` instance, you can rely on the field names not
 changing. In fact, the whole relational expression is immutable.
 
 But if a relational expression has passed through several rewrite rules (see
-([RelOptRule]({{ site.apiRoot }}/org/apache/calcite/plan/RelOptRule.html)), the field
+[RelOptRule]({{ site.apiRoot }}/org/apache/calcite/plan/RelOptRule.html)), the field
 names of the resulting expression might not look much like the originals.
 At that point it is better to reference fields by ordinal.
 
@@ -315,7 +315,7 @@ expression on the stack:
 | `as(alias)`         | Assigns a table alias to the top relational expression on the stack
 | `variable(varHolder)` | Creates a correlation variable referencing the top relational expression
 
-### Stack methods
+#### Stack methods
 
 | Method              | Description
 |:------------------- |:-----------
@@ -372,7 +372,7 @@ The following methods return patterns for use in `match`.
 | `patternPermute(pattern...)` | Permutes a pattern
 | `patternExclude(pattern)` | Excludes a pattern
 
-### Group key methods
+#### Group key methods
 
 The following methods return a
 [RelBuilder.GroupKey]({{ site.apiRoot }}/org/apache/calcite/tools/RelBuilder.GroupKey.html).
@@ -383,28 +383,28 @@ The following methods return a
 | `groupKey(exprList, exprListList)` | Creates a group key of the given expressions with grouping sets
 | `groupKey(bitSet [, bitSets])` | Creates a group key of the given input columns, with multiple grouping sets if `bitSets` is specified
 
-### Aggregate call methods
+#### Aggregate call methods
 
 The following methods return an
 [RelBuilder.AggCall]({{ site.apiRoot }}/org/apache/calcite/tools/RelBuilder.AggCall.html).
 
 | Method              | Description
 |:------------------- |:-----------
-| `aggregateCall(op, distinct, approximate, filter, orderKeys, alias, expr...)`<br/>`aggregateCall(op, distinct, approximate, filter, orderKeys, alias, exprList)` | Creates a call to a given aggregate function, with an optional filter expression and a list of optional ordering keys (for sorting input values before aggregation)
-| `count( [ distinct, alias, ] expr...)` | Creates a call to the COUNT aggregate function
-| `countStar(alias)` | Creates a call to the COUNT(*) aggregate function
-| `sum( [ distinct, alias, ] expr)` | Creates a call to the SUM aggregate function
-| `min( [ alias, ] expr)` | Creates a call to the MIN aggregate function
-| `max( [ alias, ] expr)` | Creates a call to the MAX aggregate function
+| `aggregateCall(op, expr...)`<br/>`aggregateCall(op, exprList)` | Creates a call to a given aggregate function
+| `count([ distinct, alias, ] expr...)`<br/>`count([ distinct, alias, ] exprList)` | Creates a call to the `COUNT` aggregate function
+| `countStar(alias)` | Creates a call to the `COUNT(*)` aggregate function
+| `sum([ distinct, alias, ] expr)` | Creates a call to the `SUM` aggregate function
+| `min([ alias, ] expr)` | Creates a call to the `MIN` aggregate function
+| `max([ alias, ] expr)` | Creates a call to the `MAX` aggregate function
 
 To further modify the `AggCall`, call its methods:
 
 | Method               | Description
 |:-------------------- |:-----------
+| `approximate(approximate)` | Allows approximate value for the aggregate of `approximate`
+| `as(alias)`          | Assigns a column alias to this expression (see SQL `AS`)
 | `distinct()`         | Eliminates duplicate values before aggregating (see SQL `DISTINCT`)
 | `distinct(distinct)` | Eliminates duplicate values before aggregating if `distinct`
-| `as(alias)`          | Assigns a column alias to this expression (see SQL `AS`)
 | `filter(expr)`       | Filters rows before aggregating (see SQL `FILTER (WHERE ...)`)
-| `sort(expr, ...)`<br/>`sort(exprList)` | Sorts rows before aggregating (see SQL `WITHIN GROUP`)
-| `approximate(approximate)` | Allows approximate value for the aggregate of `approximate`
+| `sort(expr...)`<br/>`sort(exprList)` | Sorts rows before aggregating (see SQL `WITHIN GROUP`)
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 81d21c3..1e48292 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1482,7 +1482,9 @@ Syntax:
 
 {% highlight sql %}
 aggregateCall:
-        agg( [ ALL | DISTINCT ] value [, value ]*) [ FILTER (WHERE condition) ]
+        agg( [ ALL | DISTINCT ] value [, value ]*)
+        [ WITHIN GROUP (ORDER BY orderItem [, orderItem ]*) ]
+        [ FILTER (WHERE condition) ]
     |   agg(*) [ FILTER (WHERE condition) ]
 {% endhighlight %}
 
@@ -1492,6 +1494,13 @@ If `FILTER` is present, the aggregate function only considers rows for which
 If `DISTINCT` is present, duplicate argument values are eliminated before being
 passed to the aggregate function.
 
+If `WITHIN GROUP` is present, the aggregate function sorts the input rows
+according to the `ORDER BY` clause inside `WITHIN GROUP` before aggregating
+values. `WITHIN GROUP` is only allowed for hypothetical set functions (`RANK`,
+`DENSE_RANK`, `PERCENT_RANK` and `CUME_DIST`), inverse distribution functions
+(`PERCENTILE_CONT` and `PERCENTILE_DISC`) and collection functions (`COLLECT`
+and `LISTAGG`).
+
 | Operator syntax                    | Description
 |:---------------------------------- |:-----------
 | COLLECT( [ ALL &#124; DISTINCT ] value)       | Returns a multiset of the values
@@ -1516,6 +1525,7 @@ passed to the aggregate function.
 
 Not implemented:
 
+* LISTAGG(string)
 * REGR_AVGX(numeric1, numeric2)
 * REGR_AVGY(numeric1, numeric2)
 * REGR_INTERCEPT(numeric1, numeric2)


[6/6] calcite git commit: [CALCITE-2652] SqlNode to SQL conversion fails if the join condition references a BOOLEAN column (Zoltan Haindrich)

Posted by jh...@apache.org.
[CALCITE-2652] SqlNode to SQL conversion fails if the join condition references a BOOLEAN column (Zoltan Haindrich)

Previously when a join ON condition have contained an exact reference it ended up with an exception.

Close apache/calcite#899


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

Branch: refs/heads/master
Commit: ebc43d9288dca1990992b16a1fa8bbfdd193f939
Parents: 042fa6b
Author: Zoltan Haindrich <ki...@rxd.hu>
Authored: Thu Nov 1 23:15:57 2018 +0100
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Nov 8 10:00:37 2018 -0800

----------------------------------------------------------------------
 .../org/apache/calcite/rel/rel2sql/SqlImplementor.java |  4 ++++
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java     | 13 +++++++++++++
 2 files changed, 17 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/ebc43d92/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index 1de1255..99e9220 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -186,6 +186,10 @@ public abstract class SqlImplementor {
     if (node.isAlwaysFalse()) {
       return SqlLiteral.createBoolean(false, POS);
     }
+    if (node instanceof RexInputRef) {
+      Context joinContext = leftContext.implementor().joinContext(leftContext, rightContext);
+      return joinContext.toSql(null, node);
+    }
     if (!(node instanceof RexCall)) {
       throw new AssertionError(node);
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/ebc43d92/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 7a5181e..351550a 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -61,6 +61,7 @@ import java.util.function.Function;
 import static org.apache.calcite.test.Matchers.isLinux;
 
 import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.junit.Assert.assertThat;
 
 /**
@@ -990,6 +991,18 @@ public class RelToSqlConverterTest {
     sql(query).ok(expected);
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-2652">[CALCITE-2652]
+   * SqlNode to SQL conversion fails if the join condition references a BOOLEAN
+   * column</a>. */
+  @Test public void testJoinOnBoolean() {
+    final String sql = "SELECT 1\n"
+        + "from emps\n"
+        + "join emp on (emp.deptno = emps.empno and manager)";
+    final String s = sql(sql).schema(CalciteAssert.SchemaSpec.POST).exec();
+    assertThat(s, notNullValue()); // sufficient that conversion did not throw
+  }
+
   @Test public void testCartesianProductWithInnerJoinSyntax() {
     String query = "select * from \"department\"\n"
         + "INNER JOIN \"employee\" ON TRUE";


[4/6] calcite git commit: [CALCITE-2224] Support WITHIN GROUP clause for aggregate functions (Hongze Zhang)

Posted by jh...@apache.org.
[CALCITE-2224] Support WITHIN GROUP clause for aggregate functions (Hongze Zhang)

Close apache/calcite#871


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

Branch: refs/heads/master
Commit: 7bc9f14032b7cf0761c0b2eefdb6bb588047ec8e
Parents: 4cc4613
Author: hongzezhang <ho...@tencent.com>
Authored: Thu Sep 27 17:55:02 2018 +0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Nov 8 10:00:28 2018 -0800

----------------------------------------------------------------------
 core/src/main/codegen/templates/Parser.jj       |  15 +-
 .../calcite/adapter/enumerable/AggImpState.java |   1 +
 .../enumerable/AggregateLambdaFactory.java      |  48 +++++
 .../adapter/enumerable/EnumerableAggregate.java | 195 ++++++++++++++-----
 .../OrderedAggregateLambdaFactory.java          | 105 ++++++++++
 .../SequencedAdderAggregateLambdaFactory.java   |  88 +++++++++
 .../adapter/enumerable/SourceSorter.java        |  60 ++++++
 .../org/apache/calcite/plan/RelOptUtil.java     |  10 +-
 .../calcite/plan/SubstitutionVisitor.java       |  13 +-
 .../calcite/prepare/CalciteCatalogReader.java   |   4 +-
 .../org/apache/calcite/rel/RelCollations.java   |  20 ++
 .../apache/calcite/rel/core/AggregateCall.java  | 112 ++++++++---
 .../org/apache/calcite/rel/core/Window.java     |   1 +
 .../calcite/rel/externalize/RelJsonReader.java  |   5 +-
 .../calcite/rel/rel2sql/SqlImplementor.java     |  51 +++--
 .../rel/rules/AbstractMaterializedViewRule.java |   2 +
 .../AggregateExpandDistinctAggregatesRule.java  |  27 +--
 .../rel/rules/AggregateExtractProjectRule.java  |   2 +-
 .../rel/rules/AggregateFilterTransposeRule.java |   1 +
 .../rel/rules/AggregateProjectMergeRule.java    |  59 +++---
 .../rel/rules/AggregateReduceFunctionsRule.java |  12 +-
 .../rel/rules/AggregateStarTableRule.java       |   3 +-
 .../rel/rules/AggregateUnionTransposeRule.java  |   8 +-
 .../calcite/rel/rules/SubQueryRemoveRule.java   |   3 +-
 .../java/org/apache/calcite/rex/RexBuilder.java |   3 +-
 .../apache/calcite/runtime/CalciteResource.java |  12 ++
 .../org/apache/calcite/sql/SqlAggFunction.java  |  59 +++++-
 .../apache/calcite/sql/SqlFilterOperator.java   |  47 +++--
 .../java/org/apache/calcite/sql/SqlKind.java    |   5 +
 .../org/apache/calcite/sql/SqlRankFunction.java |   3 +-
 .../calcite/sql/SqlSplittableAggFunction.java   |  16 +-
 .../calcite/sql/SqlWithinGroupOperator.java     |  85 ++++++++
 .../sql/fun/SqlAbstractGroupFunction.java       |   3 +-
 .../calcite/sql/fun/SqlAnyValueAggFunction.java |   4 +-
 .../calcite/sql/fun/SqlAvgAggFunction.java      |   7 +-
 .../calcite/sql/fun/SqlCountAggFunction.java    |   7 +-
 .../calcite/sql/fun/SqlCovarAggFunction.java    |   4 +-
 .../sql/fun/SqlFirstLastValueAggFunction.java   |   4 +-
 .../sql/fun/SqlHistogramAggFunction.java        |   4 +-
 .../calcite/sql/fun/SqlLeadLagAggFunction.java  |   4 +-
 .../calcite/sql/fun/SqlMinMaxAggFunction.java   |   4 +-
 .../calcite/sql/fun/SqlNthValueAggFunction.java |   3 +-
 .../calcite/sql/fun/SqlNtileAggFunction.java    |   4 +-
 .../sql/fun/SqlSingleValueAggFunction.java      |   4 +-
 .../calcite/sql/fun/SqlStdOperatorTable.java    |  11 +-
 .../calcite/sql/fun/SqlSumAggFunction.java      |   4 +-
 .../sql/fun/SqlSumEmptyIsZeroAggFunction.java   |   4 +-
 .../apache/calcite/sql/validate/AggChecker.java |   4 +
 .../sql/validate/SqlUserDefinedAggFunction.java |   7 +-
 .../calcite/sql/validate/SqlValidator.java      |   8 +-
 .../calcite/sql/validate/SqlValidatorImpl.java  |  39 +++-
 .../apache/calcite/sql2rel/RelFieldTrimmer.java |   7 +-
 .../calcite/sql2rel/SqlToRelConverter.java      |  67 ++++++-
 .../org/apache/calcite/tools/RelBuilder.java    |  65 +++++--
 .../org/apache/calcite/util/BuiltInMethod.java  |  20 +-
 .../org/apache/calcite/util/Optionality.java    |  41 ++++
 .../calcite/runtime/CalciteResource.properties  |   4 +
 .../org/apache/calcite/plan/RelWriterTest.java  |   5 +-
 .../plan/volcano/TraitPropagationTest.java      |   3 +-
 .../rel/rel2sql/RelToSqlConverterTest.java      |  51 ++++-
 .../calcite/sql/parser/SqlParserTest.java       |  58 ++++++
 .../calcite/sql/test/SqlOperatorBaseTest.java   |   2 +
 .../java/org/apache/calcite/test/JdbcTest.java  |  75 +++++++
 .../apache/calcite/test/RelMetadataTest.java    |   2 +-
 .../calcite/test/SqlToRelConverterTest.java     |  28 +++
 .../apache/calcite/test/SqlValidatorTest.java   |  35 ++++
 .../org/apache/calcite/tools/PlannerTest.java   |   4 +-
 .../calcite/test/SqlToRelConverterTest.xml      |  38 ++++
 core/src/test/resources/sql/agg.iq              | 123 ++++++++++++
 .../calcite/adapter/druid/DruidRules.java       |  11 +-
 pom.xml                                         |   2 +-
 site/_docs/algebra.md                           |  26 +--
 site/_docs/reference.md                         |  12 +-
 73 files changed, 1612 insertions(+), 271 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 bcc9e56..7bfc02c 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -4976,6 +4976,8 @@ SqlNode NamedFunctionCall() :
     final SqlNode filter;
     final SqlNode over;
     SqlLiteral quantifier = null;
+    SqlNodeList orderList = null;
+    final Span withinGroupSpan;
 }
 {
     (
@@ -5004,10 +5006,19 @@ SqlNode NamedFunctionCall() :
         }
     )
     {
-        call = createCall(qualifiedName, s.end(this), funcType, quantifier,
-            args);
+        call = createCall(qualifiedName, s.end(this), funcType, quantifier, args);
     }
     [
+        <WITHIN> { withinGroupSpan = span(); }
+        <GROUP>
+        <LPAREN>
+        orderList = OrderBy(true)
+        <RPAREN> {
+            call = SqlStdOperatorTable.WITHIN_GROUP.createCall(
+                withinGroupSpan.end(this), call, orderList);
+        }
+    ]
+    [
         <FILTER> { filterSpan = span(); }
         <LPAREN>
         <WHERE>

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/adapter/enumerable/AggImpState.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/AggImpState.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/AggImpState.java
index b100cec..aac5277 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/AggImpState.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/AggImpState.java
@@ -31,6 +31,7 @@ public class AggImpState {
   public AggContext context;
   public Expression result;
   public List<Expression> state;
+  public Expression accumulatorAdder;
 
   public AggImpState(int aggIdx, AggregateCall call, boolean windowContext) {
     this.aggIdx = aggIdx;

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/adapter/enumerable/AggregateLambdaFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/AggregateLambdaFactory.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/AggregateLambdaFactory.java
new file mode 100644
index 0000000..c5f87b4
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/AggregateLambdaFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.adapter.enumerable;
+
+import org.apache.calcite.linq4j.function.Function0;
+import org.apache.calcite.linq4j.function.Function1;
+import org.apache.calcite.linq4j.function.Function2;
+
+/**
+ * Generates lambda functions used in {@link EnumerableAggregate}.
+ *
+ * <p>This interface allows a implicit accumulator type variation.
+ * ({@code OAccumulate} {@literal ->} {@code TAccumulate})
+ *
+ * @param <TSource> Type of the enumerable input source
+ * @param <TOrigAccumulate> Type of the original accumulator
+ * @param <TAccumulate> Type of the varied accumulator
+ * @param <TResult> Type of the enumerable output result
+ * @param <TKey> Type of the group-by key
+ */
+public interface AggregateLambdaFactory<TSource, TOrigAccumulate, TAccumulate,
+    TResult, TKey> {
+  Function0<TAccumulate> accumulatorInitializer();
+
+  Function2<TAccumulate, TSource, TAccumulate> accumulatorAdder();
+
+  Function1<TAccumulate, TResult> singleGroupResultSelector(
+      Function1<TOrigAccumulate, TResult> resultSelector);
+
+  Function2<TKey, TAccumulate, TResult> resultSelector(
+      Function2<TKey, TOrigAccumulate, TResult> resultSelector);
+}
+
+// End AggregateLambdaFactory.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 49af4ba..1caf4e3 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
@@ -33,6 +33,7 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.prepare.CalcitePrepareImpl;
 import org.apache.calcite.rel.InvalidRelException;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
@@ -52,6 +53,7 @@ import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 
 /** Implementation of {@link org.apache.calcite.rel.core.Aggregate} in
@@ -231,37 +233,10 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
         PhysTypeImpl.of(typeFactory,
             typeFactory.createSyntheticType(aggStateTypes));
 
-
-    if (accPhysType.getJavaRowType() instanceof JavaTypeFactoryImpl.SyntheticRecordType) {
-      // We have to initialize the SyntheticRecordType instance this way, to avoid using
-      // class constructor with too many parameters.
-      JavaTypeFactoryImpl.SyntheticRecordType synType =
-          (JavaTypeFactoryImpl.SyntheticRecordType)
-          accPhysType.getJavaRowType();
-      final ParameterExpression record0_ =
-          Expressions.parameter(accPhysType.getJavaRowType(), "record0");
-      initBlock.add(Expressions.declare(0, record0_, null));
-      initBlock.add(
-          Expressions.statement(
-              Expressions.assign(record0_,
-                  Expressions.new_(accPhysType.getJavaRowType()))));
-      List<Types.RecordField> fieldList = synType.getRecordFields();
-      for (int i = 0; i < initExpressions.size(); i++) {
-        Expression right = initExpressions.get(i);
-        initBlock.add(
-            Expressions.statement(
-                Expressions.assign(
-                    Expressions.field(record0_, fieldList.get(i)),
-                    right)));
-      }
-      initBlock.add(record0_);
-    } else {
-      initBlock.add(accPhysType.record(initExpressions));
-    }
+    declareParentAccumulator(initExpressions, initBlock, accPhysType);
 
     final Expression accumulatorInitializer =
-        builder.append(
-            "accumulatorInitializer",
+        builder.append("accumulatorInitializer",
             Expressions.lambda(
                 Function0.class,
                 initBlock.toBlock()));
@@ -274,12 +249,12 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
     //             return acc;
     //         }
     //     };
-    final BlockBuilder builder2 = new BlockBuilder();
     final ParameterExpression inParameter =
         Expressions.parameter(inputPhysType.getJavaRowType(), "in");
     final ParameterExpression acc_ =
         Expressions.parameter(accPhysType.getJavaRowType(), "acc");
     for (int i = 0, stateOffset = 0; i < aggs.size(); i++) {
+      final BlockBuilder builder2 = new BlockBuilder();
       final AggImpState agg = aggs.get(i);
 
       final int stateSize = agg.state.size();
@@ -315,24 +290,25 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
                   currentBlock(),
                   new RexToLixTranslator.InputGetterImpl(
                       Collections.singletonList(
-                          Pair.of((Expression) inParameter, inputPhysType))),
+                          Pair.of(inParameter, inputPhysType))),
                   implementor.getConformance())
                   .setNullable(currentNullables());
             }
           };
 
       agg.implementor.implementAdd(agg.context, addContext);
+      builder2.add(acc_);
+      agg.accumulatorAdder = builder.append("accumulatorAdder",
+          Expressions.lambda(Function2.class, builder2.toBlock(), acc_,
+              inParameter));
     }
-    builder2.add(acc_);
-    final Expression accumulatorAdder =
-        builder.append(
-            "accumulatorAdder",
-            Expressions.lambda(
-                Function2.class,
-                builder2.toBlock(),
-                acc_,
-                inParameter));
 
+    final ParameterExpression lambdaFactory =
+        Expressions.parameter(AggregateLambdaFactory.class,
+            builder.newName("lambdaFactory"));
+
+    implementLambdaFactory(builder, inputPhysType, aggs, accumulatorInitializer,
+        hasOrderedCall(aggs), lambdaFactory);
     // Function2<Integer, Object[], Object[]> resultSelector =
     //     new Function2<Integer, Object[], Object[]>() {
     //         public Object[] apply(Integer key, Object[] acc) {
@@ -390,9 +366,13 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
                   BuiltInMethod.GROUP_BY_MULTIPLE.method,
                   Expressions.list(childExp,
                       keySelectors_,
-                      accumulatorInitializer,
-                      accumulatorAdder,
-                      resultSelector)
+                      Expressions.call(lambdaFactory,
+                          BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_INITIALIZER.method),
+                      Expressions.call(lambdaFactory,
+                          BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_ADDER.method),
+                      Expressions.call(lambdaFactory,
+                          BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_RESULT_SELECTOR.method,
+                          resultSelector))
                       .appendIfNotNull(keyPhysType.comparer()))));
     } else if (groupCount == 0) {
       final Expression resultSelector =
@@ -410,9 +390,15 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
                   Expressions.call(
                       childExp,
                       BuiltInMethod.AGGREGATE.method,
-                      Expressions.call(accumulatorInitializer, "apply"),
-                      accumulatorAdder,
-                      resultSelector))));
+                      Expressions.call(
+                          Expressions.call(lambdaFactory,
+                              BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_INITIALIZER.method),
+                          BuiltInMethod.FUNCTION0_APPLY.method),
+                      Expressions.call(lambdaFactory,
+                          BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_ADDER.method),
+                      Expressions.call(lambdaFactory,
+                          BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_SINGLE_GROUP_RESULT_SELECTOR.method,
+                          resultSelector)))));
     } else if (aggCalls.isEmpty()
         && groupSet.equals(
             ImmutableBitSet.range(child.getRowType().getFieldCount()))) {
@@ -441,14 +427,125 @@ public class EnumerableAggregate extends Aggregate implements EnumerableRel {
               Expressions.call(childExp,
                   BuiltInMethod.GROUP_BY2.method,
                   Expressions.list(keySelector_,
-                      accumulatorInitializer,
-                      accumulatorAdder,
-                      resultSelector_)
+                      Expressions.call(lambdaFactory,
+                          BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_INITIALIZER.method),
+                      Expressions.call(lambdaFactory,
+                          BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_ADDER.method),
+                      Expressions.call(lambdaFactory,
+                          BuiltInMethod.AGG_LAMBDA_FACTORY_ACC_RESULT_SELECTOR.method,
+                          resultSelector_))
                       .appendIfNotNull(keyPhysType.comparer()))));
     }
     return implementor.result(physType, builder.toBlock());
   }
 
+  private static boolean hasOrderedCall(List<AggImpState> aggs) {
+    for (AggImpState agg : aggs) {
+      if (!agg.call.collation.equals(RelCollations.EMPTY)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void declareParentAccumulator(List<Expression> initExpressions,
+      BlockBuilder initBlock, PhysType accPhysType) {
+    if (accPhysType.getJavaRowType()
+        instanceof JavaTypeFactoryImpl.SyntheticRecordType) {
+      // We have to initialize the SyntheticRecordType instance this way, to
+      // avoid using a class constructor with too many parameters.
+      final JavaTypeFactoryImpl.SyntheticRecordType synType =
+          (JavaTypeFactoryImpl.SyntheticRecordType)
+          accPhysType.getJavaRowType();
+      final ParameterExpression record0_ =
+          Expressions.parameter(accPhysType.getJavaRowType(), "record0");
+      initBlock.add(Expressions.declare(0, record0_, null));
+      initBlock.add(
+          Expressions.statement(
+              Expressions.assign(record0_,
+                  Expressions.new_(accPhysType.getJavaRowType()))));
+      List<Types.RecordField> fieldList = synType.getRecordFields();
+      for (int i = 0; i < initExpressions.size(); i++) {
+        Expression right = initExpressions.get(i);
+        initBlock.add(
+            Expressions.statement(
+                Expressions.assign(
+                    Expressions.field(record0_, fieldList.get(i)), right)));
+      }
+      initBlock.add(record0_);
+    } else {
+      initBlock.add(accPhysType.record(initExpressions));
+    }
+  }
+
+  /**
+   * Implements the {@link AggregateLambdaFactory}.
+   *
+   * <p>Behavior depends upon ordering:
+   * <ul>
+   *
+   * <li>{@code hasOrderedCall == true} means there is at least one aggregate
+   * call including sort spec. We use {@link OrderedAggregateLambdaFactory}
+   * implementation to implement sorted aggregates for that.
+   *
+   * <li>{@code hasOrderedCall == false} indicates to use
+   * {@link SequencedAdderAggregateLambdaFactory} to implement a non-sort
+   * aggregate.
+   *
+   * </ul>
+   */
+  private void implementLambdaFactory(BlockBuilder builder,
+      PhysType inputPhysType,
+      List<AggImpState> aggs,
+      Expression accumulatorInitializer,
+      boolean hasOrderedCall,
+      ParameterExpression lambdaFactory) {
+    if (hasOrderedCall) {
+      ParameterExpression pe = Expressions.parameter(List.class,
+          builder.newName("sourceSorters"));
+      builder.add(
+          Expressions.declare(0, pe, Expressions.new_(LinkedList.class)));
+
+      for (AggImpState agg : aggs) {
+        if (agg.call.collation.equals(RelCollations.EMPTY)) {
+          continue;
+        }
+        final Pair<Expression, Expression> pair =
+            inputPhysType.generateCollationKey(
+                agg.call.collation.getFieldCollations());
+        builder.add(
+            Expressions.statement(
+                Expressions.call(pe,
+                    BuiltInMethod.COLLECTION_ADD.method,
+                    Expressions.new_(BuiltInMethod.SOURCE_SORTER.constructor,
+                        agg.accumulatorAdder, pair.left, pair.right))));
+      }
+      builder.add(
+          Expressions.declare(0, lambdaFactory,
+              Expressions.new_(
+                  BuiltInMethod.ORDERED_AGGREGATE_LAMBDA_FACTORY.constructor,
+                  accumulatorInitializer, pe)));
+    } else {
+      // when hasOrderedCall == false
+      ParameterExpression pe = Expressions.parameter(List.class,
+          builder.newName("accumulatorAdders"));
+      builder.add(
+          Expressions.declare(0, pe, Expressions.new_(LinkedList.class)));
+
+      for (AggImpState agg : aggs) {
+        builder.add(
+            Expressions.statement(
+                Expressions.call(pe, BuiltInMethod.COLLECTION_ADD.method,
+                    agg.accumulatorAdder)));
+      }
+      builder.add(
+          Expressions.declare(0, lambdaFactory,
+              Expressions.new_(
+                  BuiltInMethod.SEQUENCED_ADDER_AGGREGATE_LAMBDA_FACTORY.constructor,
+                  accumulatorInitializer, pe)));
+    }
+  }
+
   /** An implementation of {@link AggContext}. */
   private class AggContextImpl implements AggContext {
     private final AggImpState agg;

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/adapter/enumerable/OrderedAggregateLambdaFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/OrderedAggregateLambdaFactory.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/OrderedAggregateLambdaFactory.java
new file mode 100644
index 0000000..3c7c72a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/OrderedAggregateLambdaFactory.java
@@ -0,0 +1,105 @@
+/*
+ * 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.adapter.enumerable;
+
+import org.apache.calcite.linq4j.function.Function0;
+import org.apache.calcite.linq4j.function.Function1;
+import org.apache.calcite.linq4j.function.Function2;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Generate aggregate lambdas that sorts the input source before calling each
+ * aggregate adder.
+ *
+ * @param <TSource> Type of the enumerable input source
+ * @param <TKey> Type of the group-by key
+ * @param <TSortKey> Type of the sort key
+ * @param <TOrigAccumulate> Type of the original accumulator
+ * @param <TResult> Type of the enumerable output result
+ */
+public class OrderedAggregateLambdaFactory<TSource, TKey, TSortKey,
+    TOrigAccumulate, TResult>
+    implements AggregateLambdaFactory<TSource, TOrigAccumulate,
+    OrderedAggregateLambdaFactory.LazySource<TSource>, TResult, TKey> {
+
+  private final Function0<TOrigAccumulate> accumulatorInitializer;
+  private final List<SourceSorter<TOrigAccumulate, TSource, TSortKey>> sourceSorters;
+
+  public OrderedAggregateLambdaFactory(
+      Function0<TOrigAccumulate> accumulatorInitializer,
+      List<SourceSorter<TOrigAccumulate, TSource, TSortKey>> sourceSorters) {
+    this.accumulatorInitializer = accumulatorInitializer;
+    this.sourceSorters = sourceSorters;
+  }
+
+  public Function0<LazySource<TSource>> accumulatorInitializer() {
+    return LazySource::new;
+  }
+
+  public Function2<LazySource<TSource>,
+      TSource, LazySource<TSource>> accumulatorAdder() {
+    return (lazySource, source) -> {
+      lazySource.add(source);
+      return lazySource;
+    };
+  }
+
+  public Function1<LazySource<TSource>, TResult> singleGroupResultSelector(
+      Function1<TOrigAccumulate, TResult> resultSelector) {
+    return lazySource -> {
+      final TOrigAccumulate accumulator = accumulatorInitializer.apply();
+      for (SourceSorter<TOrigAccumulate, TSource, TSortKey> acc : sourceSorters) {
+        acc.sortAndAccumulate(lazySource, accumulator);
+      }
+      return resultSelector.apply(accumulator);
+    };
+  }
+
+  public Function2<TKey, LazySource<TSource>, TResult> resultSelector(
+      Function2<TKey, TOrigAccumulate, TResult> resultSelector) {
+    return (groupByKey, lazySource) -> {
+      final TOrigAccumulate accumulator = accumulatorInitializer.apply();
+      for (SourceSorter<TOrigAccumulate, TSource, TSortKey> acc : sourceSorters) {
+        acc.sortAndAccumulate(lazySource, accumulator);
+      }
+      return resultSelector.apply(groupByKey, accumulator);
+    };
+  }
+
+  /**
+   * Cache the input sources. (Will be sorted, aggregated in result selector.)
+   *
+   * @param <TSource> Type of the enumerable input source.
+   */
+  public static class LazySource<TSource> implements Iterable<TSource> {
+    private final List<TSource> list = new ArrayList<>();
+
+    private void add(TSource source) {
+      list.add(source);
+    }
+
+    @Override public Iterator<TSource> iterator() {
+      return list.iterator();
+    }
+  }
+
+}
+
+// End OrderedAggregateLambdaFactory.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/adapter/enumerable/SequencedAdderAggregateLambdaFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/SequencedAdderAggregateLambdaFactory.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/SequencedAdderAggregateLambdaFactory.java
new file mode 100644
index 0000000..4b4577c
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/SequencedAdderAggregateLambdaFactory.java
@@ -0,0 +1,88 @@
+/*
+ * 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.adapter.enumerable;
+
+import org.apache.calcite.linq4j.function.Function0;
+import org.apache.calcite.linq4j.function.Function1;
+import org.apache.calcite.linq4j.function.Function2;
+
+import java.util.List;
+
+/**
+ * Implementation of {@link AggregateLambdaFactory} that applies a sequence of
+ * accumulator adders to input source.
+ *
+ * @param <TSource> Type of the enumerable input source
+ * @param <TAccumulate> Type of the accumulator
+ * @param <TResult> Type of the enumerable output result
+ * @param <TKey> Type of the group-by key
+ */
+public class SequencedAdderAggregateLambdaFactory<TSource, TAccumulate, TResult, TKey>
+    implements AggregateLambdaFactory<TSource, TAccumulate, TAccumulate, TResult, TKey> {
+
+  private final Function0<TAccumulate> accumulatorInitializer;
+  private final Function2<TAccumulate, TSource, TAccumulate> accumulatorAdderDecorator;
+
+  public SequencedAdderAggregateLambdaFactory(
+      Function0<TAccumulate> accumulatorInitializer,
+      List<Function2<TAccumulate, TSource, TAccumulate>> accumulatorAdders) {
+    this.accumulatorInitializer = accumulatorInitializer;
+    this.accumulatorAdderDecorator = new AccumulatorAdderSeq(accumulatorAdders);
+  }
+
+  @Override public Function0<TAccumulate> accumulatorInitializer() {
+    return accumulatorInitializer;
+  }
+
+  @Override public Function2<TAccumulate, TSource, TAccumulate> accumulatorAdder() {
+    return accumulatorAdderDecorator;
+  }
+
+  @Override public Function1<TAccumulate, TResult> singleGroupResultSelector(
+      Function1<TAccumulate, TResult> resultSelector) {
+    return resultSelector;
+  }
+
+  @Override public Function2<TKey, TAccumulate, TResult> resultSelector(
+      Function2<TKey, TAccumulate, TResult> resultSelector) {
+    return resultSelector;
+  }
+
+  /**
+   * Decorator class of a sequence of accumulator adder functions.
+   */
+  private class AccumulatorAdderSeq
+      implements Function2<TAccumulate, TSource, TAccumulate> {
+    private final List<Function2<TAccumulate, TSource, TAccumulate>> accumulatorAdders;
+
+    AccumulatorAdderSeq(
+        List<Function2<TAccumulate, TSource, TAccumulate>> accumulatorAdders) {
+      this.accumulatorAdders = accumulatorAdders;
+    }
+
+    @Override public TAccumulate apply(TAccumulate accumulator, TSource source) {
+      TAccumulate result = accumulator;
+      for (Function2<TAccumulate, TSource, TAccumulate> accumulatorAdder
+          : accumulatorAdders) {
+        result = accumulatorAdder.apply(accumulator, source);
+      }
+      return result;
+    }
+  }
+}
+
+// End SequencedAdderAggregateLambdaFactory.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/adapter/enumerable/SourceSorter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/SourceSorter.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/SourceSorter.java
new file mode 100644
index 0000000..9345da4
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/SourceSorter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.adapter.enumerable;
+
+import org.apache.calcite.linq4j.Linq4j;
+import org.apache.calcite.linq4j.function.Function1;
+import org.apache.calcite.linq4j.function.Function2;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Helper that combines the sorting process and accumulating process against the
+ * aggregate execution, used with {@link OrderedAggregateLambdaFactory}.
+ *
+ * @param <TAccumulate> Type of the accumulator
+ * @param <TSource> Type of the enumerable input source
+ * @param <TSortKey> Type of the sort key
+ */
+public class SourceSorter<TAccumulate, TSource, TSortKey> {
+  private final Function2<TAccumulate, TSource, TAccumulate> accumulatorAdder;
+  private final Function1<TSource, TSortKey> keySelector;
+  private final Comparator<TSortKey> comparator;
+
+  public SourceSorter(
+      Function2<TAccumulate, TSource, TAccumulate> accumulatorAdder,
+      Function1<TSource, TSortKey> keySelector,
+      Comparator<TSortKey> comparator) {
+    this.accumulatorAdder = accumulatorAdder;
+    this.keySelector = keySelector;
+    this.comparator = comparator;
+  }
+
+  public void sortAndAccumulate(Iterable<TSource> sourceIterable,
+      TAccumulate accumulator) {
+    List<TSource> sorted = Linq4j.asEnumerable(sourceIterable)
+        .orderBy(keySelector, comparator)
+        .toList();
+    TAccumulate accumulator1 = accumulator;
+    for (TSource source : sorted) {
+      accumulator1 = accumulatorAdder.apply(accumulator1, source);
+    }
+  }
+}
+
+// End SourceSorter.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 cda1e66..e15e491 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -18,6 +18,7 @@ package org.apache.calcite.plan;
 
 import org.apache.calcite.avatica.AvaticaConnection;
 import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelHomogeneousShuttle;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelShuttle;
@@ -475,6 +476,7 @@ public abstract class RelOptUtil {
               false,
               ImmutableList.of(0),
               -1,
+              RelCollations.EMPTY,
               0,
               ret,
               null,
@@ -567,6 +569,7 @@ public abstract class RelOptUtil {
             false,
             ImmutableList.of(projectedKeyCount),
             -1,
+            RelCollations.EMPTY,
             projectedKeyCount,
             ret,
             null,
@@ -765,15 +768,14 @@ public abstract class RelOptUtil {
   public static RelNode createSingleValueAggRel(
       RelOptCluster cluster,
       RelNode rel) {
-    // assert (rel.getRowType().getFieldCount() == 1);
     final int aggCallCnt = rel.getRowType().getFieldCount();
     final List<AggregateCall> aggCalls = new ArrayList<>();
 
     for (int i = 0; i < aggCallCnt; i++) {
       aggCalls.add(
-          AggregateCall.create(
-              SqlStdOperatorTable.SINGLE_VALUE, false, false,
-              ImmutableList.of(i), -1, 0, rel, null, null));
+          AggregateCall.create(SqlStdOperatorTable.SINGLE_VALUE, false, false,
+              ImmutableList.of(i), -1, RelCollations.EMPTY, 0, rel, null,
+              null));
     }
 
     return LogicalAggregate.create(rel, ImmutableBitSet.of(), null, aggCalls);

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 f37d3cd..4212fae 100644
--- a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
+++ b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java
@@ -61,7 +61,6 @@ import org.apache.calcite.util.trace.CalciteTrace;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 
@@ -1247,17 +1246,10 @@ public class SubstitutionVisitor {
     ImmutableList<ImmutableBitSet> groupSets =
         Mappings.apply2(mapping, aggregate.groupSets);
     List<AggregateCall> aggregateCalls =
-        apply(mapping, aggregate.aggCalls);
+        Util.transform(aggregate.aggCalls, call -> call.transform(mapping));
     return MutableAggregate.of(input, groupSet, groupSets, aggregateCalls);
   }
 
-  private static List<AggregateCall> apply(final Mapping mapping,
-      List<AggregateCall> aggCallList) {
-    return Lists.transform(aggCallList,
-        call -> call.copy(Mappings.apply2(mapping, call.getArgList()),
-            Mappings.apply(mapping, call.filterArg)));
-  }
-
   public static MutableRel unifyAggregates(MutableAggregate query,
       MutableAggregate target) {
     if (query.getGroupType() != Aggregate.Group.SIMPLE
@@ -1304,7 +1296,8 @@ public class SubstitutionVisitor {
             AggregateCall.create(getRollup(aggregateCall.getAggregation()),
                 aggregateCall.isDistinct(), aggregateCall.isApproximate(),
                 ImmutableList.of(target.groupSet.cardinality() + i), -1,
-                aggregateCall.type, aggregateCall.name));
+                aggregateCall.collation, aggregateCall.type,
+                aggregateCall.name));
       }
       result = MutableAggregate.of(target, groupSet.build(), null,
           aggregateCalls);

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
index cbfd145..9685239 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
@@ -60,6 +60,7 @@ import org.apache.calcite.sql.validate.SqlUserDefinedFunction;
 import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
 import org.apache.calcite.sql.validate.SqlUserDefinedTableMacro;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.util.Optionality;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
@@ -324,7 +325,8 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
     } else if (function instanceof AggregateFunction) {
       return new SqlUserDefinedAggFunction(name,
           infer((AggregateFunction) function), InferTypes.explicit(argTypes),
-          typeChecker, (AggregateFunction) function, false, false, typeFactory);
+          typeChecker, (AggregateFunction) function, false, false,
+          Optionality.FORBIDDEN, typeFactory);
     } else if (function instanceof TableMacro) {
       return new SqlUserDefinedTableMacro(name, ReturnTypes.CURSOR,
           InferTypes.explicit(argTypes), typeChecker, paramTypes,

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/rel/RelCollations.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/RelCollations.java b/core/src/main/java/org/apache/calcite/rel/RelCollations.java
index c3b16c6..5d7c7bb 100644
--- a/core/src/main/java/org/apache/calcite/rel/RelCollations.java
+++ b/core/src/main/java/org/apache/calcite/rel/RelCollations.java
@@ -19,6 +19,7 @@ package org.apache.calcite.rel;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Util;
+import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -26,6 +27,7 @@ import com.google.common.collect.Lists;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -186,6 +188,24 @@ public class RelCollations {
     }
     return new RelCollationImpl(fieldCollations.build());
   }
+
+  /** Creates a copy of this collation that changes the ordinals of input
+   * fields. */
+  public static RelCollation permute(RelCollation collation,
+      Map<Integer, Integer> mapping) {
+    return of(
+        Util.transform(collation.getFieldCollations(),
+            fc -> fc.copy(mapping.get(fc.getFieldIndex()))));
+  }
+
+  /** Creates a copy of this collation that changes the ordinals of input
+   * fields. */
+  public static RelCollation permute(RelCollation collation,
+      Mappings.TargetMapping mapping) {
+    return of(
+        Util.transform(collation.getFieldCollations(),
+            fc -> fc.copy(mapping.getTarget(fc.getFieldIndex()))));
+  }
 }
 
 // End RelCollations.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 5df2a51..fb32cbc 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
@@ -16,6 +16,8 @@
  */
 package org.apache.calcite.rel.core;
 
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -30,8 +32,8 @@ import java.util.List;
 import java.util.Objects;
 
 /**
- * Call to an aggFunction function within an
- * {@link org.apache.calcite.rel.logical.LogicalAggregate}.
+ * Call to an aggregate function within an
+ * {@link org.apache.calcite.rel.core.Aggregate}.
  */
 public class AggregateCall {
   //~ Instance fields --------------------------------------------------------
@@ -47,6 +49,7 @@ public class AggregateCall {
   // since all values are small, ImmutableList uses cached Integer values.
   private final ImmutableList<Integer> argList;
   public final int filterArg;
+  public final RelCollation collation;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -66,7 +69,8 @@ public class AggregateCall {
       List<Integer> argList,
       RelDataType type,
       String name) {
-    this(aggFunction, distinct, false, argList, -1, type, name);
+    this(aggFunction, distinct, false,
+        argList, -1, RelCollations.EMPTY, type, name);
   }
 
   /**
@@ -76,23 +80,22 @@ public class AggregateCall {
    * @param distinct    Whether distinct
    * @param approximate Whether approximate
    * @param argList     List of ordinals of arguments
-   * @param filterArg   Ordinal of filter argument, or -1
+   * @param filterArg   Ordinal of filter argument (the
+   *                    {@code FILTER (WHERE ...)} clause in SQL), or -1
+   * @param collation   How to sort values before aggregation (the
+   *                    {@code WITHIN GROUP} clause in SQL)
    * @param type        Result type
    * @param name        Name (may be null)
    */
-  private AggregateCall(
-      SqlAggFunction aggFunction,
-      boolean distinct,
-      boolean approximate,
-      List<Integer> argList,
-      int filterArg,
-      RelDataType type,
-      String name) {
+  private AggregateCall(SqlAggFunction aggFunction, boolean distinct,
+      boolean approximate, List<Integer> argList, int filterArg,
+      RelCollation collation, RelDataType type, String name) {
     this.type = Objects.requireNonNull(type);
     this.name = name;
     this.aggFunction = Objects.requireNonNull(aggFunction);
     this.argList = ImmutableList.copyOf(argList);
     this.filterArg = filterArg;
+    this.collation = Objects.requireNonNull(collation);
     this.distinct = distinct;
     this.approximate = approximate;
   }
@@ -103,23 +106,32 @@ public class AggregateCall {
   public static AggregateCall create(SqlAggFunction aggFunction,
       boolean distinct, List<Integer> argList, int groupCount, RelNode input,
       RelDataType type, String name) {
-    return create(aggFunction, distinct, false, argList, -1, groupCount, input,
-        type, name);
+    return create(aggFunction, distinct, false, argList, -1,
+        RelCollations.EMPTY, groupCount, input, type, name);
   }
 
   @Deprecated // to be removed before 2.0
   public static AggregateCall create(SqlAggFunction aggFunction,
       boolean distinct, List<Integer> argList, int filterArg, int groupCount,
       RelNode input, RelDataType type, String name) {
-    return create(aggFunction, distinct, false, argList, -1, groupCount, input,
-        type, name);
+    return create(aggFunction, distinct, false, argList, filterArg,
+        RelCollations.EMPTY, groupCount, input, type, name);
   }
 
-  /** 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, boolean approximate, List<Integer> argList,
       int filterArg, int groupCount,
       RelNode input, RelDataType type, String name) {
+    return create(aggFunction, distinct, approximate, argList,
+        filterArg, RelCollations.EMPTY, groupCount, input, type, name);
+  }
+
+  /** Creates an AggregateCall, inferring its type if {@code type} is null. */
+  public static AggregateCall create(SqlAggFunction aggFunction,
+      boolean distinct, boolean approximate, List<Integer> argList,
+      int filterArg, RelCollation collation, int groupCount,
+      RelNode input, RelDataType type, String name) {
     if (type == null) {
       final RelDataTypeFactory typeFactory =
           input.getCluster().getTypeFactory();
@@ -130,23 +142,32 @@ public class AggregateCall {
               groupCount, filterArg >= 0);
       type = aggFunction.inferReturnType(callBinding);
     }
-    return create(aggFunction, distinct, approximate, argList, filterArg, type,
-        name);
+    return create(aggFunction, distinct, approximate, argList, filterArg,
+        collation, type, name);
   }
 
   @Deprecated // to be removed before 2.0
   public static AggregateCall create(SqlAggFunction aggFunction,
       boolean distinct, List<Integer> argList, int filterArg, RelDataType type,
       String name) {
-    return create(aggFunction, distinct, false, argList, filterArg, type, name);
+    return create(aggFunction, distinct, false, argList, filterArg,
+        RelCollations.EMPTY, type, name);
   }
 
-  /** Creates an AggregateCall. */
+  @Deprecated // to be removed before 2.0
   public static AggregateCall create(SqlAggFunction aggFunction,
       boolean distinct, boolean approximate, List<Integer> argList,
       int filterArg, RelDataType type, String name) {
+    return create(aggFunction, distinct, approximate, argList, filterArg,
+        RelCollations.EMPTY, type, name);
+  }
+
+  /** Creates an AggregateCall. */
+  public static AggregateCall create(SqlAggFunction aggFunction,
+      boolean distinct, boolean approximate, List<Integer> argList,
+      int filterArg, RelCollation collation, RelDataType type, String name) {
     return new AggregateCall(aggFunction, distinct, approximate, argList,
-        filterArg, type, name);
+        filterArg, collation, type, name);
   }
 
   /**
@@ -179,6 +200,16 @@ public class AggregateCall {
   }
 
   /**
+   * Returns the aggregate ordering definition (the {@code WITHIN GROUP} clause
+   * in SQL), or the empty list if not specified.
+   *
+   * @return ordering definition
+   */
+  public RelCollation getCollation() {
+    return collation;
+  }
+
+  /**
    * Returns the ordinals of the arguments to this call.
    *
    * <p>The list is immutable.
@@ -216,8 +247,10 @@ public class AggregateCall {
     if (Objects.equals(this.name, name)) {
       return this;
     }
-    return new AggregateCall(aggFunction, distinct, approximate, argList,
-        filterArg, type, name);
+    return new AggregateCall(aggFunction, distinct, approximate,
+        argList,
+        filterArg, RelCollations.EMPTY, type,
+        name);
   }
 
   public String toString() {
@@ -235,6 +268,11 @@ public class AggregateCall {
       buf.append(arg);
     }
     buf.append(")");
+    if (!collation.equals(RelCollations.EMPTY)) {
+      buf.append(" WITHIN GROUP (");
+      buf.append(collation);
+      buf.append(")");
+    }
     if (hasFilter()) {
       buf.append(" FILTER $");
       buf.append(filterArg);
@@ -257,11 +295,12 @@ public class AggregateCall {
     return aggFunction.equals(other.aggFunction)
         && (distinct == other.distinct)
         && argList.equals(other.argList)
-        && filterArg == other.filterArg;
+        && filterArg == other.filterArg
+        && Objects.equals(collation, other.collation);
   }
 
   @Override public int hashCode() {
-    return Objects.hash(aggFunction, distinct, argList, filterArg);
+    return Objects.hash(aggFunction, distinct, argList, filterArg, collation);
   }
 
   /**
@@ -282,17 +321,27 @@ public class AggregateCall {
   /**
    * Creates an equivalent AggregateCall with new argument ordinals.
    *
+   * @see #transform(Mappings.TargetMapping)
+   *
    * @param args Arguments
    * @return AggregateCall that suits new inputs and GROUP BY columns
    */
-  public AggregateCall copy(List<Integer> args, int filterArg) {
+  public AggregateCall copy(List<Integer> args, int filterArg,
+      RelCollation collation) {
     return new AggregateCall(aggFunction, distinct, approximate, args,
-        filterArg, type, name);
+        filterArg, collation, type, name);
+  }
+
+  @Deprecated // to be removed before 2.0
+  public AggregateCall copy(List<Integer> args, int filterArg) {
+    // ignoring collation is error-prone
+    return copy(args, filterArg, collation);
   }
 
   @Deprecated // to be removed before 2.0
   public AggregateCall copy(List<Integer> args) {
-    return copy(args, filterArg);
+    // ignoring filterArg and collation is error-prone
+    return copy(args, filterArg, collation);
   }
 
   /**
@@ -317,14 +366,15 @@ public class AggregateCall {
             ? type
             : null;
     return create(aggFunction, distinct, approximate, argList, filterArg,
-        newGroupKeyCount, input, newType, getName());
+        collation, 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.apply2((Mapping) mapping, argList),
-        hasFilter() ? Mappings.apply(mapping, filterArg) : -1);
+        hasFilter() ? Mappings.apply(mapping, filterArg) : -1,
+        RelCollations.permute(collation, mapping));
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 1e00bba..186f7d3 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
@@ -316,6 +316,7 @@ public abstract class Window extends SingleRel {
           final SqlAggFunction op = (SqlAggFunction) aggCall.getOperator();
           return AggregateCall.create(op, aggCall.distinct,
               false, getProjectOrdinals(aggCall.getOperands()), -1,
+              RelCollations.EMPTY,
               aggCall.getType(), fieldNames.get(aggCall.ordinal));
         }
       };

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 695ab0b..29c3f44 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
@@ -22,6 +22,7 @@ import org.apache.calcite.plan.RelOptSchema;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelDistribution;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
@@ -280,7 +281,9 @@ public class RelJsonReader {
     final RelDataType type =
         relJson.toType(cluster.getTypeFactory(), jsonAggCall.get("type"));
     return AggregateCall.create(aggregation, distinct, false, operands,
-        filterOperand == null ? -1 : filterOperand, type, null);
+        filterOperand == null ? -1 : filterOperand,
+        RelCollations.EMPTY,
+        type, null);
   }
 
   private RelNode lookupInput(String jsonInput) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index 0323a52..1de1255 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -768,6 +768,23 @@ public abstract class SqlImplementor {
       };
     }
 
+    void addOrderItem(List<SqlNode> orderByList, RelFieldCollation field) {
+      if (field.nullDirection != RelFieldCollation.NullDirection.UNSPECIFIED) {
+        final boolean first =
+            field.nullDirection == RelFieldCollation.NullDirection.FIRST;
+        SqlNode nullDirectionNode =
+            dialect.emulateNullDirection(field(field.getFieldIndex()),
+                first, field.direction.isDescending());
+        if (nullDirectionNode != null) {
+          orderByList.add(nullDirectionNode);
+          field = new RelFieldCollation(field.getFieldIndex(),
+              field.getDirection(),
+              RelFieldCollation.NullDirection.UNSPECIFIED);
+        }
+      }
+      orderByList.add(toSql(field));
+    }
+
     /** Converts a call to an aggregate function to an expression. */
     public SqlNode toSql(AggregateCall aggCall) {
       final SqlOperator op = aggCall.getAggregation();
@@ -778,16 +795,32 @@ public abstract class SqlImplementor {
       final SqlLiteral qualifier =
           aggCall.isDistinct() ? SqlSelectKeyword.DISTINCT.symbol(POS) : null;
       final SqlNode[] operands = operandList.toArray(new SqlNode[0]);
+      List<SqlNode> orderByList = Expressions.list();
+      for (RelFieldCollation field : aggCall.collation.getFieldCollations()) {
+        addOrderItem(orderByList, field);
+      }
+      SqlNodeList orderList = new SqlNodeList(orderByList, POS);
       if (op instanceof SqlSumEmptyIsZeroAggFunction) {
         final SqlNode node =
-            SqlStdOperatorTable.SUM.createCall(qualifier, POS, operands);
+            withOrder(
+                SqlStdOperatorTable.SUM.createCall(qualifier, POS, operands),
+                orderList);
         return SqlStdOperatorTable.COALESCE.createCall(POS, node,
             SqlLiteral.createExactNumeric("0", POS));
       } else {
-        return op.createCall(qualifier, POS, operands);
+        return withOrder(op.createCall(qualifier, POS, operands), orderList);
       }
     }
 
+    /** Wraps a call in a {@link SqlKind#WITHIN_GROUP} call, if
+     * {@code orderList} is non-empty. */
+    private SqlNode withOrder(SqlCall call, SqlNodeList orderList) {
+      if (orderList == null || orderList.size() == 0) {
+        return call;
+      }
+      return SqlStdOperatorTable.WITHIN_GROUP.createCall(POS, call, orderList);
+    }
+
     /** Converts a collation to an ORDER BY item. */
     public SqlNode toSql(RelFieldCollation collation) {
       SqlNode node = field(collation.getFieldIndex());
@@ -1217,19 +1250,7 @@ public abstract class SqlImplementor {
 
     public void addOrderItem(List<SqlNode> orderByList,
         RelFieldCollation field) {
-      if (field.nullDirection != RelFieldCollation.NullDirection.UNSPECIFIED) {
-        boolean first = field.nullDirection == RelFieldCollation.NullDirection.FIRST;
-        SqlNode nullDirectionNode =
-            dialect.emulateNullDirection(context.field(field.getFieldIndex()),
-                first, field.direction.isDescending());
-        if (nullDirectionNode != null) {
-          orderByList.add(nullDirectionNode);
-          field = new RelFieldCollation(field.getFieldIndex(),
-              field.getDirection(),
-              RelFieldCollation.NullDirection.UNSPECIFIED);
-        }
-      }
-      orderByList.add(context.toSql(field));
+      context.addOrderItem(orderByList, field);
     }
 
     public Result result() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
index 68a9382..b164fe0 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
@@ -1198,6 +1198,7 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
             rexBuilder.makeInputRef(relBuilder.peek(),
                 aggregate.getGroupCount() + i);
         aggregateCalls.add(
+            // TODO: handle aggregate ordering
             relBuilder.aggregateCall(rollupAgg, operand)
                 .distinct(aggCall.isDistinct())
                 .approximate(aggCall.isApproximate())
@@ -1469,6 +1470,7 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
               }
               final RexInputRef operand = rexBuilder.makeInputRef(input, k);
               aggregateCalls.add(
+                  // TODO: handle aggregate ordering
                   relBuilder.aggregateCall(rollupAgg, operand)
                       .approximate(queryAggCall.isApproximate())
                       .distinct(queryAggCall.isDistinct())

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 6d974d8..7175a5f 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
@@ -20,6 +20,7 @@ import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Aggregate.Group;
@@ -296,7 +297,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
         final AggregateCall newCall =
             AggregateCall.create(aggCall.getAggregation(), false,
                 aggCall.isApproximate(), aggCall.getArgList(), -1,
-                bottomGroupSet.cardinality(),
+                aggCall.collation, bottomGroupSet.cardinality(),
                 relBuilder.peek(), null, aggCall.name);
         bottomAggregateCalls.add(newCall);
       }
@@ -324,6 +325,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
                 aggCall.isApproximate(),
                 newArgList,
                 -1,
+                aggCall.collation,
                 originalGroupSet.cardinality(),
                 relBuilder.peek(),
                 aggCall.getType(),
@@ -331,19 +333,18 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
       } else {
         // If aggregate B had a COUNT aggregate call the corresponding aggregate at
         // aggregate A must be SUM. For other aggregates, it remains the same.
-        final List<Integer> newArgs =
-            Lists.newArrayList(bottomGroups.size()
-                + nonDistinctAggCallProcessedSoFar);
+        final int arg = bottomGroups.size() + nonDistinctAggCallProcessedSoFar;
+        final List<Integer> newArgs = ImmutableList.of(arg);
         if (aggCall.getAggregation().getKind() == SqlKind.COUNT) {
           newCall =
               AggregateCall.create(new SqlSumEmptyIsZeroAggFunction(), false,
-                  aggCall.isApproximate(), newArgs, -1,
+                  aggCall.isApproximate(), newArgs, -1, aggCall.collation,
                   originalGroupSet.cardinality(), relBuilder.peek(),
                   aggCall.getType(), aggCall.getName());
         } else {
           newCall =
               AggregateCall.create(aggCall.getAggregation(), false,
-                  aggCall.isApproximate(), newArgs, -1,
+                  aggCall.isApproximate(), newArgs, -1, aggCall.collation,
                   originalGroupSet.cardinality(),
                   relBuilder.peek(), aggCall.getType(), aggCall.name);
         }
@@ -409,8 +410,8 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
     final int z = groupCount + distinctAggCalls.size();
     distinctAggCalls.add(
         AggregateCall.create(SqlStdOperatorTable.GROUPING, false, false,
-            ImmutableIntList.copyOf(fullGroupSet), -1, groupSets.size(),
-            relBuilder.peek(), null, "$g"));
+            ImmutableIntList.copyOf(fullGroupSet), -1, RelCollations.EMPTY,
+            groupSets.size(), relBuilder.peek(), null, "$g"));
     for (Ord<ImmutableBitSet> groupSet : Ord.zip(groupSets)) {
       filters.put(groupSet.e, z + groupSet.i);
     }
@@ -455,8 +456,8 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
       }
       final AggregateCall newCall =
           AggregateCall.create(aggregation, false, aggCall.isApproximate(),
-              newArgList, newFilterArg, aggregate.getGroupCount(), distinct,
-              null, aggCall.name);
+              newArgList, newFilterArg, aggCall.collation,
+              aggregate.getGroupCount(), distinct, null, aggCall.name);
       newCalls.add(newCall);
     }
 
@@ -665,8 +666,8 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
           aggCall.filterArg >= 0 ? sourceOf.get(aggCall.filterArg) : -1;
       final AggregateCall newAggCall =
           AggregateCall.create(aggCall.getAggregation(), false,
-              aggCall.isApproximate(), newArgs,
-              newFilterArg, aggCall.getType(), aggCall.getName());
+              aggCall.isApproximate(), newArgs, newFilterArg, aggCall.collation,
+              aggCall.getType(), aggCall.getName());
       assert refs.get(i) == null;
       if (n == 0) {
         refs.set(i,
@@ -754,7 +755,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
       }
       final AggregateCall newAggCall =
           AggregateCall.create(aggCall.getAggregation(), false,
-              aggCall.isApproximate(), newArgs, -1,
+              aggCall.isApproximate(), newArgs, -1, aggCall.collation,
               aggCall.getType(), aggCall.getName());
       newAggCalls.set(i, newAggCall);
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
index 08b12dd..bd5a348 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExtractProjectRule.java
@@ -120,7 +120,7 @@ public class AggregateExtractProjectRule extends RelOptRule {
               .distinct(aggCall.isDistinct())
               .filter(filterArg)
               .approximate(aggCall.isApproximate())
-              .approximate(aggCall.isApproximate())
+              .sort(relBuilder.fields(aggCall.collation))
               .as(aggCall.name));
     }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 5c0e531..9b54f08 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
@@ -145,6 +145,7 @@ public class AggregateFilterTransposeRule extends RelOptRule {
         topAggCallList.add(
             AggregateCall.create(rollup, aggregateCall.isDistinct(),
                 aggregateCall.isApproximate(), ImmutableList.of(i++), -1,
+                aggregateCall.collation,
                 aggregateCall.type, aggregateCall.name));
       }
       final Aggregate topAggregate =

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 c5ec515..925afbe 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
@@ -18,6 +18,7 @@ package org.apache.calcite.rel.rules;
 
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Aggregate.Group;
@@ -29,13 +30,17 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.tools.RelBuilderFactory;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * Planner rule that recognizes a {@link org.apache.calcite.rel.core.Aggregate}
@@ -73,18 +78,26 @@ public class AggregateProjectMergeRule extends RelOptRule {
 
   public static RelNode apply(RelOptRuleCall call, Aggregate aggregate,
       Project project) {
-    final List<Integer> newKeys = new ArrayList<>();
+    // Find all fields which we need to be straightforward field projections.
+    final Set<Integer> interestingFields = new TreeSet<>();
+    interestingFields.addAll(aggregate.getGroupSet().asList());
+    for (AggregateCall aggregateCall : aggregate.getAggCallList()) {
+      interestingFields.addAll(aggregateCall.getArgList());
+      if (aggregateCall.filterArg >= 0) {
+        interestingFields.add(aggregateCall.filterArg);
+      }
+      interestingFields.addAll(RelCollations.ordinals(aggregateCall.collation));
+    }
+
+    // Build the map from old to new; abort if any entry is not a
+    // straightforward field projection.
     final Map<Integer, Integer> map = new HashMap<>();
-    for (int key : aggregate.getGroupSet()) {
-      final RexNode rex = project.getProjects().get(key);
-      if (rex instanceof RexInputRef) {
-        final int newKey = ((RexInputRef) rex).getIndex();
-        newKeys.add(newKey);
-        map.put(key, newKey);
-      } else {
-        // Cannot handle "GROUP BY expression"
+    for (int source : interestingFields) {
+      final RexNode rex = project.getProjects().get(source);
+      if (!(rex instanceof RexInputRef)) {
         return null;
       }
+      map.put(source, ((RexInputRef) rex).getIndex());
     }
 
     final ImmutableBitSet newGroupSet = aggregate.getGroupSet().permute(map);
@@ -97,28 +110,12 @@ public class AggregateProjectMergeRule extends RelOptRule {
 
     final ImmutableList.Builder<AggregateCall> aggCalls =
         ImmutableList.builder();
+    final int sourceCount = aggregate.getInput().getRowType().getFieldCount();
+    final int targetCount = project.getInput().getRowType().getFieldCount();
+    final Mappings.TargetMapping targetMapping =
+        Mappings.target(map, sourceCount, targetCount);
     for (AggregateCall aggregateCall : aggregate.getAggCallList()) {
-      final ImmutableList.Builder<Integer> newArgs = ImmutableList.builder();
-      for (int arg : aggregateCall.getArgList()) {
-        final RexNode rex = project.getProjects().get(arg);
-        if (rex instanceof RexInputRef) {
-          newArgs.add(((RexInputRef) rex).getIndex());
-        } else {
-          // Cannot handle "AGG(expression)"
-          return null;
-        }
-      }
-      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));
+      aggCalls.add(aggregateCall.transform(targetMapping));
     }
 
     final Aggregate newAggregate =
@@ -130,6 +127,8 @@ public class AggregateProjectMergeRule extends RelOptRule {
     // contains duplicates.
     final RelBuilder relBuilder = call.builder();
     relBuilder.push(newAggregate);
+    final List<Integer> newKeys =
+        Lists.transform(aggregate.getGroupSet().asList(), map::get);
     if (!newKeys.equals(newGroupSet.asList())) {
       final List<Integer> posList = new ArrayList<>();
       for (int newKey : newKeys) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 68f6b16..2c9c4e5 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
@@ -316,6 +316,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
         oldCall.isApproximate(),
         ImmutableIntList.of(argOrdinal),
         filter,
+        oldCall.collation,
         aggFunction.inferReturnType(binding),
         null);
   }
@@ -339,6 +340,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             oldCall.isApproximate(),
             oldCall.getArgList(),
             oldCall.filterArg,
+            oldCall.collation,
             oldAggRel.getGroupCount(),
             oldAggRel.getInput(),
             null,
@@ -349,6 +351,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             oldCall.isApproximate(),
             oldCall.getArgList(),
             oldCall.filterArg,
+            oldCall.collation,
             oldAggRel.getGroupCount(),
             oldAggRel.getInput(),
             null,
@@ -396,14 +399,15 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
     final AggregateCall sumZeroCall =
         AggregateCall.create(SqlStdOperatorTable.SUM0, oldCall.isDistinct(),
             oldCall.isApproximate(), oldCall.getArgList(), oldCall.filterArg,
-            oldAggRel.getGroupCount(), oldAggRel.getInput(), null,
-            oldCall.name);
+            oldCall.collation, oldAggRel.getGroupCount(), oldAggRel.getInput(),
+            null, oldCall.name);
     final AggregateCall countCall =
         AggregateCall.create(SqlStdOperatorTable.COUNT,
             oldCall.isDistinct(),
             oldCall.isApproximate(),
             oldCall.getArgList(),
             oldCall.filterArg,
+            oldCall.collation,
             oldAggRel.getGroupCount(),
             oldAggRel,
             null,
@@ -494,6 +498,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             oldCall.isApproximate(),
             ImmutableIntList.of(argOrdinal),
             oldCall.filterArg,
+            oldCall.collation,
             oldAggRel.getGroupCount(),
             oldAggRel.getInput(),
             null,
@@ -517,6 +522,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             oldCall.isApproximate(),
             oldCall.getArgList(),
             oldCall.filterArg,
+            oldCall.collation,
             oldAggRel.getGroupCount(),
             oldAggRel,
             null,
@@ -589,6 +595,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             oldCall.isApproximate(),
             ImmutableIntList.of(argOrdinal),
             filterArg,
+            oldCall.collation,
             oldAggRel.getGroupCount(),
             oldAggRel.getInput(),
             null,
@@ -634,6 +641,7 @@ public class AggregateReduceFunctionsRule extends RelOptRule {
             oldCall.isApproximate(),
             argOrdinals,
             filterArg,
+            oldCall.collation,
             oldAggRel.getGroupCount(),
             oldAggRel,
             null,

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
index e974763..317b0c5 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateStarTableRule.java
@@ -236,6 +236,7 @@ public class AggregateStarTableRule extends RelOptRule {
       }
       return AggregateCall.create(roll, false,
           aggregateCall.isApproximate(), ImmutableList.of(offset + i), -1,
+          aggregateCall.collation,
           groupCount, relBuilder.peek(), null, aggregateCall.name);
     }
 
@@ -251,7 +252,7 @@ public class AggregateStarTableRule extends RelOptRule {
         newArgs.add(z);
       }
       return AggregateCall.create(aggregation, false,
-          aggregateCall.isApproximate(), newArgs, -1,
+          aggregateCall.isApproximate(), newArgs, -1, aggregateCall.collation,
           groupCount, relBuilder.peek(), null, aggregateCall.name);
     }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java
index d6fd60e..0416338 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateUnionTransposeRule.java
@@ -173,8 +173,12 @@ public class AggregateUnionTransposeRule extends RelOptRule {
       AggregateCall newCall =
           AggregateCall.create(aggFun, origCall.isDistinct(),
               origCall.isApproximate(),
-              ImmutableList.of(groupCount + ord.i), -1, groupCount, input,
-              aggType, origCall.getName());
+              ImmutableList.of(groupCount + ord.i), -1,
+              origCall.collation,
+              groupCount,
+              input,
+              aggType,
+              origCall.getName());
       newCalls.add(newCall);
     }
     return newCalls;

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
index 39bccbf..3383f97 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
@@ -385,8 +385,7 @@ public abstract class SubQueryRemoveRule extends RelOptRule {
         // Builds the cross join
         builder.aggregate(builder.groupKey(),
             builder.count(false, "c"),
-            builder.aggregateCall(SqlStdOperatorTable.COUNT, builder.fields())
-                .as("ck"));
+            builder.count(builder.fields()).as("ck"));
         builder.as("ct");
         if (!variablesSet.isEmpty()) {
           builder.join(JoinRelType.LEFT, builder.literal(true), variablesSet);

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 4a26cd8..5741a11 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -293,7 +293,8 @@ 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.filterArg);
+        aggCall = aggCall.copy(nullableArgs, aggCall.filterArg,
+            aggCall.collation);
       }
     }
     RexNode rex = aggCallMapping.get(aggCall);

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 122b39e..54a8c2d 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -326,6 +326,15 @@ public interface CalciteResource {
   @BaseMessage("FILTER must not contain aggregate expression")
   ExInst<SqlValidatorException> aggregateInFilterIllegal();
 
+  @BaseMessage("WITHIN GROUP must not contain aggregate expression")
+  ExInst<SqlValidatorException> aggregateInWithinGroupIllegal();
+
+  @BaseMessage("Aggregate expression ''{0}'' must contain a within group clause")
+  ExInst<SqlValidatorException> aggregateMissingWithinGroupClause(String a0);
+
+  @BaseMessage("Aggregate expression ''{0}'' must not contain a within group clause")
+  ExInst<SqlValidatorException> withinGroupClauseIllegalInAggregate(String a0);
+
   @BaseMessage("Aggregate expression is illegal in ORDER BY clause of non-aggregating SELECT")
   ExInst<SqlValidatorException> aggregateIllegalInOrderBy();
 
@@ -436,6 +445,9 @@ public interface CalciteResource {
   @BaseMessage("DISTINCT/ALL not allowed with {0} function")
   ExInst<SqlValidatorException> functionQuantifierNotAllowed(String a0);
 
+  @BaseMessage("WITHIN GROUP not allowed with {0} function")
+  ExInst<SqlValidatorException> withinGroupNotAllowed(String a0);
+
   @BaseMessage("Some but not all arguments are named")
   ExInst<SqlValidatorException> someButNotAllArgumentsAreNamed();
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 6c1f648..cca0ad0 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlAggFunction.java
@@ -24,8 +24,11 @@ import org.apache.calcite.sql.type.SqlOperandTypeInference;
 import org.apache.calcite.sql.type.SqlReturnTypeInference;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
+import org.apache.calcite.util.Optionality;
 
 import java.util.List;
+import java.util.Objects;
+import javax.annotation.Nonnull;
 
 /**
  * Abstract base class for the definition of an aggregate function: an operator
@@ -34,6 +37,7 @@ import java.util.List;
 public abstract class SqlAggFunction extends SqlFunction implements Context {
   private final boolean requiresOrder;
   private final boolean requiresOver;
+  private final Optionality requiresGroupOrder;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -48,7 +52,8 @@ public abstract class SqlAggFunction extends SqlFunction implements Context {
       SqlFunctionCategory funcType) {
     // We leave sqlIdentifier as null to indicate that this is a builtin.
     this(name, null, kind, returnTypeInference, operandTypeInference,
-        operandTypeChecker, funcType, false, false);
+        operandTypeChecker, funcType, false, false,
+        Optionality.FORBIDDEN);
   }
 
   /** Creates a user-defined SqlAggFunction. */
@@ -62,7 +67,24 @@ public abstract class SqlAggFunction extends SqlFunction implements Context {
       SqlOperandTypeChecker operandTypeChecker,
       SqlFunctionCategory funcType) {
     this(name, sqlIdentifier, kind, returnTypeInference, operandTypeInference,
-        operandTypeChecker, funcType, false, false);
+        operandTypeChecker, funcType, false, false,
+        Optionality.FORBIDDEN);
+  }
+
+  @Deprecated // to be removed before 2.0
+  protected SqlAggFunction(
+      String name,
+      SqlIdentifier sqlIdentifier,
+      SqlKind kind,
+      SqlReturnTypeInference returnTypeInference,
+      SqlOperandTypeInference operandTypeInference,
+      SqlOperandTypeChecker operandTypeChecker,
+      SqlFunctionCategory funcType,
+      boolean requiresOrder,
+      boolean requiresOver) {
+    this(name, sqlIdentifier, kind, returnTypeInference, operandTypeInference,
+        operandTypeChecker, funcType, requiresOrder, requiresOver,
+        Optionality.FORBIDDEN);
   }
 
   /** Creates a built-in or user-defined SqlAggFunction or window function.
@@ -78,11 +100,13 @@ public abstract class SqlAggFunction extends SqlFunction implements Context {
       SqlOperandTypeChecker operandTypeChecker,
       SqlFunctionCategory funcType,
       boolean requiresOrder,
-      boolean requiresOver) {
+      boolean requiresOver,
+      Optionality requiresGroupOrder) {
     super(name, sqlIdentifier, kind, returnTypeInference, operandTypeInference,
         operandTypeChecker, null, funcType);
     this.requiresOrder = requiresOrder;
     this.requiresOver = requiresOver;
+    this.requiresGroupOrder = Objects.requireNonNull(requiresGroupOrder);
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -105,13 +129,40 @@ public abstract class SqlAggFunction extends SqlFunction implements Context {
       SqlValidatorScope scope,
       SqlValidatorScope operandScope) {
     super.validateCall(call, validator, scope, operandScope);
-    validator.validateAggregateParams(call, null, scope);
+    validator.validateAggregateParams(call, null, null, scope);
   }
 
   @Override public final boolean requiresOrder() {
     return requiresOrder;
   }
 
+  /** Returns whether this aggregate function must, may, or must not contain a
+   * {@code WITHIN GROUP (ORDER ...)} clause.
+   *
+   * <p>Cases:<ul>
+   *
+   * <li>If {@link Optionality#MANDATORY},
+   * then {@code AGG(x) WITHIN GROUP (ORDER BY 1)} is valid,
+   * and {@code AGG(x)} is invalid.
+   *
+   * <li>If {@link Optionality#OPTIONAL},
+   * then {@code AGG(x) WITHIN GROUP (ORDER BY 1)}
+   * and {@code AGG(x)} are both valid.
+   *
+   * <li>If {@link Optionality#IGNORED},
+   * then {@code AGG(x)} is valid,
+   * and {@code AGG(x) WITHIN GROUP (ORDER BY 1)} is valid but is
+   * treated the same as {@code AGG(x)}.
+   *
+   * <li>If {@link Optionality#FORBIDDEN},
+   * then {@code AGG(x) WITHIN GROUP (ORDER BY 1)} is invalid,
+   * and {@code AGG(x)} is valid.
+   * </ul>
+   */
+  public @Nonnull Optionality requiresGroupOrder() {
+    return requiresGroupOrder;
+  }
+
   @Override public final boolean requiresOver() {
     return requiresOver;
   }


[5/6] calcite git commit: [CALCITE-2657] In RexShuttle, use "RexCall.clone" instead of "new RexCall" (Chunwei Lei)

Posted by jh...@apache.org.
[CALCITE-2657] In RexShuttle, use "RexCall.clone" instead of "new RexCall" (Chunwei Lei)

Close apache/calcite#905


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

Branch: refs/heads/master
Commit: 042fa6b7135fc8861816985253657dd685471f25
Parents: 7bc9f14
Author: chunwei.lcw <ch...@163.com>
Authored: Wed Nov 7 15:17:02 2018 +0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Nov 8 10:00:37 2018 -0800

----------------------------------------------------------------------
 core/src/main/java/org/apache/calcite/rex/RexShuttle.java | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/042fa6b7/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexShuttle.java b/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
index d79dcbf..b1f1593 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
@@ -104,10 +104,7 @@ public class RexShuttle implements RexVisitor<RexNode> {
       // To do that, we would need to take a RexBuilder and
       // watch out for special operators like CAST and NEW where
       // the type is embedded in the original call.
-      return new RexCall(
-          call.getType(),
-          call.getOperator(),
-          clonedOperands);
+      return call.clone(call.getType(), clonedOperands);
     } else {
       return call;
     }


[3/6] calcite git commit: [CALCITE-2224] Support WITHIN GROUP clause for aggregate functions (Hongze Zhang)

Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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
index 3c0b527..8215dca 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlFilterOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlFilterOperator.java
@@ -69,13 +69,18 @@ public class SqlFilterOperator extends SqlBinaryOperator {
       SqlValidatorScope operandScope) {
     assert call.getOperator() == this;
     assert call.operandCount() == 2;
-    SqlCall aggCall = call.operand(0);
+    SqlCall aggCall = getAggCall(call);
     if (!aggCall.getOperator().isAggregator()) {
       throw validator.newValidationError(aggCall,
           RESOURCE.filterNonAggregate());
     }
     final SqlNode condition = call.operand(1);
-    validator.validateAggregateParams(aggCall, condition, scope);
+    SqlNodeList orderList = null;
+    if (hasWithinGroupCall(call)) {
+      SqlCall withinGroupCall = getWithinGroupCall(call);
+      orderList = withinGroupCall.operand(1);
+    }
+    validator.validateAggregateParams(aggCall, condition, orderList, scope);
 
     final RelDataType type = validator.deriveType(scope, condition);
     if (!SqlTypeUtil.inBooleanFamily(type)) {
@@ -92,14 +97,7 @@ public class SqlFilterOperator extends SqlBinaryOperator {
     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;
+    final SqlCall aggCall = getAggCall(call);
 
     // 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
@@ -114,9 +112,36 @@ public class SqlFilterOperator extends SqlBinaryOperator {
 
     // Copied from validateOperands
     ((SqlValidatorImpl) validator).setValidatedNodeType(call, ret);
-    ((SqlValidatorImpl) validator).setValidatedNodeType(agg, ret);
+    ((SqlValidatorImpl) validator).setValidatedNodeType(aggCall, ret);
+    if (hasWithinGroupCall(call)) {
+      ((SqlValidatorImpl) validator).setValidatedNodeType(getWithinGroupCall(call), ret);
+    }
     return ret;
   }
+
+  private static SqlCall getAggCall(SqlCall call) {
+    assert call.getOperator().getKind() == SqlKind.FILTER;
+    call = call.operand(0);
+    if (call.getOperator().getKind() == SqlKind.WITHIN_GROUP) {
+      call = call.operand(0);
+    }
+    return call;
+  }
+
+  private static SqlCall getWithinGroupCall(SqlCall call) {
+    assert call.getOperator().getKind() == SqlKind.FILTER;
+    call = call.operand(0);
+    if (call.getOperator().getKind() == SqlKind.WITHIN_GROUP) {
+      return call;
+    }
+    throw new AssertionError();
+  }
+
+  private static boolean hasWithinGroupCall(SqlCall call) {
+    assert call.getOperator().getKind() == SqlKind.FILTER;
+    call = call.operand(0);
+    return call.getOperator().getKind() == SqlKind.WITHIN_GROUP;
+  }
 }
 
 // End SqlFilterOperator.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 e77b4be..225d3b4 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -213,6 +213,11 @@ public enum SqlKind {
   FILTER,
 
   /**
+   * WITHIN_GROUP operator
+   */
+  WITHIN_GROUP,
+
+  /**
    * Window specification
    */
   WINDOW,

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/SqlRankFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlRankFunction.java b/core/src/main/java/org/apache/calcite/sql/SqlRankFunction.java
index f330463..0b01d91 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlRankFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlRankFunction.java
@@ -19,6 +19,7 @@ package org.apache.calcite.sql;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
 import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.util.Optionality;
 
 /**
  * Operator which aggregates sets of values into a result.
@@ -35,7 +36,7 @@ public class SqlRankFunction extends SqlAggFunction {
       boolean requiresOrder) {
     super(kind.name(), null, kind, returnTypes, null,
         OperandTypes.NILADIC, SqlFunctionCategory.NUMERIC, requiresOrder,
-        true);
+        true, Optionality.FORBIDDEN);
   }
 
   //~ Methods ----------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java b/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java
index 83dad3a..652886f 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.sql;
 
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -118,7 +119,7 @@ public interface SqlSplittableAggFunction {
 
     public AggregateCall other(RelDataTypeFactory typeFactory, AggregateCall e) {
       return AggregateCall.create(SqlStdOperatorTable.COUNT, false, false,
-          ImmutableIntList.of(), -1,
+          ImmutableIntList.of(), -1, RelCollations.EMPTY,
           typeFactory.createSqlType(SqlTypeName.BIGINT), null);
     }
 
@@ -147,8 +148,8 @@ public interface SqlSplittableAggFunction {
       }
       int ordinal = extra.register(node);
       return AggregateCall.create(SqlStdOperatorTable.SUM0, false, false,
-          ImmutableList.of(ordinal), -1, aggregateCall.type,
-          aggregateCall.name);
+          ImmutableList.of(ordinal), -1, aggregateCall.collation,
+          aggregateCall.type, aggregateCall.name);
     }
 
     /**
@@ -207,8 +208,10 @@ public interface SqlSplittableAggFunction {
         Registry<RexNode> extra, int offset, RelDataType inputRowType,
         AggregateCall aggregateCall, int leftSubTotal, int rightSubTotal) {
       assert (leftSubTotal >= 0) != (rightSubTotal >= 0);
+      assert aggregateCall.collation.getFieldCollations().isEmpty();
       final int arg = leftSubTotal >= 0 ? leftSubTotal : rightSubTotal;
-      return aggregateCall.copy(ImmutableIntList.of(arg), -1);
+      return aggregateCall.copy(ImmutableIntList.of(arg), -1,
+          RelCollations.EMPTY);
     }
   }
 
@@ -230,6 +233,7 @@ public interface SqlSplittableAggFunction {
     public AggregateCall other(RelDataTypeFactory typeFactory, AggregateCall e) {
       return AggregateCall.create(SqlStdOperatorTable.COUNT, false, false,
           ImmutableIntList.of(), -1,
+          RelCollations.EMPTY,
           typeFactory.createSqlType(SqlTypeName.BIGINT), null);
     }
 
@@ -260,8 +264,8 @@ public interface SqlSplittableAggFunction {
       }
       int ordinal = extra.register(node);
       return AggregateCall.create(getMergeAggFunctionOfTopSplit(), false, false,
-          ImmutableList.of(ordinal), -1, aggregateCall.type,
-          aggregateCall.name);
+          ImmutableList.of(ordinal), -1, aggregateCall.collation,
+          aggregateCall.type, aggregateCall.name);
     }
 
     protected abstract SqlAggFunction getMergeAggFunctionOfTopSplit();

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java
new file mode 100644
index 0000000..3ec0ee8
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java
@@ -0,0 +1,85 @@
+/*
+ * 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.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorScope;
+
+import static org.apache.calcite.util.Static.RESOURCE;
+
+/**
+ * An operator that applies a sort operation 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: order operation list
+ * </ul>
+ */
+public class SqlWithinGroupOperator extends SqlBinaryOperator {
+
+  public SqlWithinGroupOperator() {
+    super("WITHIN GROUP", SqlKind.WITHIN_GROUP, 100, true, ReturnTypes.ARG0,
+        null, OperandTypes.ANY_ANY);
+  }
+
+  @Override public void unparse(SqlWriter writer, SqlCall call, int leftPrec, int rightPrec) {
+    assert call.operandCount() == 2;
+    call.operand(0).unparse(writer, 0, 0);
+    writer.keyword("WITHIN GROUP");
+    final SqlWriter.Frame orderFrame =
+        writer.startList(SqlWriter.FrameTypeEnum.ORDER_BY_LIST, "(", ")");
+    writer.keyword("ORDER BY");
+    ((SqlNodeList) call.operand(1)).commaList(writer);
+    writer.endList(orderFrame);
+  }
+
+  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(call,
+          RESOURCE.withinGroupNotAllowed(aggCall.getOperator().getName()));
+    }
+    final SqlNodeList orderList = call.operand(1);
+    for (SqlNode order : orderList) {
+      RelDataType nodeType =
+          validator.deriveType(scope, order);
+      assert nodeType != null;
+    }
+    validator.validateAggregateParams(aggCall, null, orderList, scope);
+  }
+
+  public RelDataType deriveType(
+      SqlValidator validator,
+      SqlValidatorScope scope,
+      SqlCall call) {
+    // Validate type of the inner aggregate call
+    return validateOperands(validator, scope, call);
+  }
+}
+
+// End SqlWithinGroupOperator.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java
index d1001dd..132c8cd 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java
@@ -31,6 +31,7 @@ import org.apache.calcite.sql.validate.SelectScope;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.util.Optionality;
 import org.apache.calcite.util.Static;
 
 /**
@@ -55,7 +56,7 @@ public class SqlAbstractGroupFunction extends SqlAggFunction {
       SqlOperandTypeChecker operandTypeChecker,
       SqlFunctionCategory category) {
     super(name, null, kind, returnTypeInference, operandTypeInference,
-        operandTypeChecker, category, false, false);
+        operandTypeChecker, category, false, false, Optionality.FORBIDDEN);
   }
 
   @Override public void validateCall(SqlCall call, SqlValidator validator,

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlAnyValueAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlAnyValueAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlAnyValueAggFunction.java
index 49208b9..32e9792 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlAnyValueAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlAnyValueAggFunction.java
@@ -21,6 +21,7 @@ import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.base.Preconditions;
 
@@ -44,7 +45,8 @@ public class SqlAnyValueAggFunction extends SqlAggFunction {
         OperandTypes.ANY,
         SqlFunctionCategory.SYSTEM,
         false,
-        false);
+        false,
+        Optionality.FORBIDDEN);
     Preconditions.checkArgument(kind == SqlKind.ANY_VALUE);
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlAvgAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlAvgAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlAvgAggFunction.java
index 6be1ce9..f1ffcf9 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlAvgAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlAvgAggFunction.java
@@ -22,6 +22,7 @@ import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.base.Preconditions;
 
@@ -51,8 +52,10 @@ public class SqlAvgAggFunction extends SqlAggFunction {
         OperandTypes.NUMERIC,
         SqlFunctionCategory.NUMERIC,
         false,
-        false);
-    Preconditions.checkArgument(SqlKind.AVG_AGG_FUNCTIONS.contains(kind), "unsupported sql kind");
+        false,
+        Optionality.FORBIDDEN);
+    Preconditions.checkArgument(SqlKind.AVG_AGG_FUNCTIONS.contains(kind),
+        "unsupported sql kind");
   }
 
   @Deprecated // to be removed before 2.0

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlCountAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlCountAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlCountAggFunction.java
index db54102..1678727 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCountAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCountAggFunction.java
@@ -30,6 +30,7 @@ import org.apache.calcite.sql.type.SqlOperandTypeChecker;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.collect.ImmutableList;
 
@@ -49,9 +50,11 @@ public class SqlCountAggFunction extends SqlAggFunction {
     this(name, SqlValidator.STRICT ? OperandTypes.ANY : OperandTypes.ONE_OR_MORE);
   }
 
-  public SqlCountAggFunction(String name, SqlOperandTypeChecker sqlOperandTypeChecker) {
+  public SqlCountAggFunction(String name,
+      SqlOperandTypeChecker sqlOperandTypeChecker) {
     super(name, null, SqlKind.COUNT, ReturnTypes.BIGINT, null,
-        sqlOperandTypeChecker, SqlFunctionCategory.NUMERIC, false, false);
+        sqlOperandTypeChecker, SqlFunctionCategory.NUMERIC, false, false,
+        Optionality.FORBIDDEN);
   }
 
   //~ Methods ----------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlCovarAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlCovarAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlCovarAggFunction.java
index 8591959..764339d 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCovarAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCovarAggFunction.java
@@ -22,6 +22,7 @@ import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.base.Preconditions;
 
@@ -48,7 +49,8 @@ public class SqlCovarAggFunction extends SqlAggFunction {
         OperandTypes.NUMERIC_NUMERIC,
         SqlFunctionCategory.NUMERIC,
         false,
-        false);
+        false,
+        Optionality.FORBIDDEN);
     Preconditions.checkArgument(SqlKind.COVAR_AVG_AGG_FUNCTIONS.contains(kind),
         "unsupported sql kind: " + kind);
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlFirstLastValueAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlFirstLastValueAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlFirstLastValueAggFunction.java
index 72dccd1..397acf8 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlFirstLastValueAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlFirstLastValueAggFunction.java
@@ -24,6 +24,7 @@ import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -48,7 +49,8 @@ public class SqlFirstLastValueAggFunction extends SqlAggFunction {
         OperandTypes.ANY,
         SqlFunctionCategory.NUMERIC,
         false,
-        true);
+        true,
+        Optionality.FORBIDDEN);
     Preconditions.checkArgument(kind == SqlKind.FIRST_VALUE
         || kind == SqlKind.LAST_VALUE);
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlHistogramAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlHistogramAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlHistogramAggFunction.java
index a3fb5f2..864f8b4 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlHistogramAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlHistogramAggFunction.java
@@ -23,6 +23,7 @@ import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.collect.ImmutableList;
 
@@ -53,7 +54,8 @@ public class SqlHistogramAggFunction extends SqlAggFunction {
         OperandTypes.NUMERIC_OR_STRING,
         SqlFunctionCategory.NUMERIC,
         false,
-        false);
+        false,
+        Optionality.FORBIDDEN);
     this.type = type;
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlLeadLagAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLeadLagAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLeadLagAggFunction.java
index d52c82b..55dc0cf 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLeadLagAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLeadLagAggFunction.java
@@ -28,6 +28,7 @@ import org.apache.calcite.sql.type.SqlSingleOperandTypeChecker;
 import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeTransform;
 import org.apache.calcite.sql.type.SqlTypeTransforms;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -78,7 +79,8 @@ public class SqlLeadLagAggFunction extends SqlAggFunction {
         OPERAND_TYPES,
         SqlFunctionCategory.NUMERIC,
         false,
-        true);
+        true,
+        Optionality.FORBIDDEN);
     Preconditions.checkArgument(kind == SqlKind.LEAD
         || kind == SqlKind.LAG);
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlMinMaxAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlMinMaxAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlMinMaxAggFunction.java
index 438c7f1..edbc4b0 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlMinMaxAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlMinMaxAggFunction.java
@@ -24,6 +24,7 @@ import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlSplittableAggFunction;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -75,7 +76,8 @@ public class SqlMinMaxAggFunction extends SqlAggFunction {
         OperandTypes.COMPARABLE_ORDERED,
         SqlFunctionCategory.SYSTEM,
         false,
-        false);
+        false,
+        Optionality.FORBIDDEN);
     this.argTypes = ImmutableList.of();
     this.minMaxKind = MINMAX_COMPARABLE;
     Preconditions.checkArgument(kind == SqlKind.MIN

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlNthValueAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlNthValueAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlNthValueAggFunction.java
index b1a2a86..6e66653 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlNthValueAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlNthValueAggFunction.java
@@ -21,6 +21,7 @@ import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 /**
  * <code>NTH_VALUE</code> windowed aggregate function
@@ -31,7 +32,7 @@ public class SqlNthValueAggFunction extends SqlAggFunction {
   public SqlNthValueAggFunction(SqlKind kind) {
     super(kind.name(), null, kind, ReturnTypes.ARG0_NULLABLE_IF_EMPTY,
         null, OperandTypes.ANY_NUMERIC, SqlFunctionCategory.NUMERIC, false,
-        true);
+        true, Optionality.FORBIDDEN);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlNtileAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlNtileAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlNtileAggFunction.java
index 18677f5..d451c0b 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlNtileAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlNtileAggFunction.java
@@ -21,6 +21,7 @@ import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 /**
  * <code>NTILE</code> aggregate function
@@ -37,7 +38,8 @@ public class SqlNtileAggFunction extends SqlAggFunction {
         OperandTypes.POSITIVE_INTEGER_LITERAL,
         SqlFunctionCategory.NUMERIC,
         false,
-        true);
+        true,
+        Optionality.FORBIDDEN);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlSingleValueAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlSingleValueAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlSingleValueAggFunction.java
index 6e745df..6f4518e 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlSingleValueAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlSingleValueAggFunction.java
@@ -23,6 +23,7 @@ import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.collect.ImmutableList;
 
@@ -51,7 +52,8 @@ public class SqlSingleValueAggFunction extends SqlAggFunction {
         OperandTypes.ANY,
         SqlFunctionCategory.SYSTEM,
         false,
-        false);
+        false,
+        Optionality.FORBIDDEN);
     this.type = type;
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 827a516..ac09932 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
@@ -46,6 +46,7 @@ import org.apache.calcite.sql.SqlUnnestOperator;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlValuesOperator;
 import org.apache.calcite.sql.SqlWindow;
+import org.apache.calcite.sql.SqlWithinGroupOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.type.InferTypes;
 import org.apache.calcite.sql.type.OperandTypes;
@@ -57,6 +58,7 @@ import org.apache.calcite.sql.validate.SqlConformance;
 import org.apache.calcite.sql.validate.SqlModality;
 import org.apache.calcite.sql2rel.AuxiliaryConverter;
 import org.apache.calcite.util.Litmus;
+import org.apache.calcite.util.Optionality;
 import org.apache.calcite.util.Pair;
 
 import com.google.common.collect.ImmutableList;
@@ -177,6 +179,9 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
    *  aggregate function. */
   public static final SqlFilterOperator FILTER = new SqlFilterOperator();
 
+  /** <code>WITHIN_GROUP</code> operator performs aggregations on ordered data input. */
+  public static final SqlWithinGroupOperator WITHIN_GROUP = new SqlWithinGroupOperator();
+
   /** {@code CUBE} operator, occurs within {@code GROUP BY} clause
    * or nested within a {@code GROUPING SETS}. */
   public static final SqlInternalOperator CUBE =
@@ -1980,7 +1985,8 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
           ReturnTypes.TO_MULTISET,
           null,
           OperandTypes.ANY,
-          SqlFunctionCategory.SYSTEM, false, false) {
+          SqlFunctionCategory.SYSTEM, false, false,
+          Optionality.OPTIONAL) {
       };
 
   /**
@@ -1992,7 +1998,8 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
           ReturnTypes.ARG0,
           null,
           OperandTypes.MULTISET,
-          SqlFunctionCategory.SYSTEM, false, false) {
+          SqlFunctionCategory.SYSTEM, false, false,
+          Optionality.FORBIDDEN) {
       };
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlSumAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlSumAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlSumAggFunction.java
index eac5731..941a4e7 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlSumAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlSumAggFunction.java
@@ -24,6 +24,7 @@ import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlSplittableAggFunction;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.collect.ImmutableList;
 
@@ -54,7 +55,8 @@ public class SqlSumAggFunction extends SqlAggFunction {
         OperandTypes.NUMERIC,
         SqlFunctionCategory.NUMERIC,
         false,
-        false);
+        false,
+        Optionality.FORBIDDEN);
     this.type = type;
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/fun/SqlSumEmptyIsZeroAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlSumEmptyIsZeroAggFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlSumEmptyIsZeroAggFunction.java
index 1fd9bf1..b74e9f9 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlSumEmptyIsZeroAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlSumEmptyIsZeroAggFunction.java
@@ -25,6 +25,7 @@ import org.apache.calcite.sql.SqlSplittableAggFunction;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Optionality;
 
 import com.google.common.collect.ImmutableList;
 
@@ -48,7 +49,8 @@ public class SqlSumEmptyIsZeroAggFunction extends SqlAggFunction {
         OperandTypes.NUMERIC,
         SqlFunctionCategory.NUMERIC,
         false,
-        false);
+        false,
+        Optionality.FORBIDDEN);
   }
 
   //~ Methods ----------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 a00a523..2962459 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
@@ -156,6 +156,10 @@ class AggChecker extends SqlBasicVisitor<Void> {
       call.operand(0).accept(this);
       return null;
     }
+    if (call.getKind() == SqlKind.WITHIN_GROUP) {
+      call.operand(0).accept(this);
+      return null;
+    }
     // Visit the operand in window function
     if (call.getKind() == SqlKind.OVER) {
       for (SqlNode operand : call.<SqlCall>operand(0).getOperandList()) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/sql/validate/SqlUserDefinedAggFunction.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlUserDefinedAggFunction.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlUserDefinedAggFunction.java
index 0cf8c0f..186daf5 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlUserDefinedAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlUserDefinedAggFunction.java
@@ -31,6 +31,7 @@ import org.apache.calcite.sql.type.SqlOperandTypeChecker;
 import org.apache.calcite.sql.type.SqlOperandTypeInference;
 import org.apache.calcite.sql.type.SqlReturnTypeInference;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Optionality;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.Lists;
@@ -57,10 +58,12 @@ public class SqlUserDefinedAggFunction extends SqlAggFunction {
       SqlReturnTypeInference returnTypeInference,
       SqlOperandTypeInference operandTypeInference,
       SqlOperandTypeChecker operandTypeChecker, AggregateFunction function,
-      boolean requiresOrder, boolean requiresOver, RelDataTypeFactory typeFactory) {
+      boolean requiresOrder, boolean requiresOver,
+      Optionality requiresGroupOrder, RelDataTypeFactory typeFactory) {
     super(Util.last(opName.names), opName, SqlKind.OTHER_FUNCTION,
         returnTypeInference, operandTypeInference, operandTypeChecker,
-        SqlFunctionCategory.USER_DEFINED_FUNCTION, requiresOrder, requiresOver);
+        SqlFunctionCategory.USER_DEFINED_FUNCTION, requiresOrder, requiresOver,
+        requiresGroupOrder);
     this.function = function;
     this.typeFactory = typeFactory;
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 a4b4297..2045eac 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
@@ -294,12 +294,14 @@ public interface SqlValidator {
   /**
    * Validates parameters for aggregate function.
    *
-   * @param aggCall     Function containing COLUMN_LIST parameter
-   * @param filter      Filter, or null
+   * @param aggCall     Call to aggregate function
+   * @param filter      Filter ({@code FILTER (WHERE)} clause), or null
+   * @param orderList   Ordering specification ({@code WITHING GROUP} clause),
+   *                    or null
    * @param scope       Syntactic scope
    */
   void validateAggregateParams(SqlCall aggCall, SqlNode filter,
-      SqlValidatorScope scope);
+      SqlNodeList orderList, SqlValidatorScope scope);
 
   /**
    * Validates a COLUMN_LIST parameter

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 f7ce4d6..626399f 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
@@ -45,6 +45,7 @@ import org.apache.calcite.sql.JoinConditionType;
 import org.apache.calcite.sql.JoinType;
 import org.apache.calcite.sql.SqlAccessEnum;
 import org.apache.calcite.sql.SqlAccessType;
+import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlBasicCall;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCallBinding;
@@ -4868,7 +4869,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     targetWindow.setWindowCall(null);
     call.validate(this, scope);
 
-    validateAggregateParams(call, null, scope);
+    validateAggregateParams(call, null, null, scope);
 
     // Disable nested aggregates post validation
     inWindow = false;
@@ -5141,7 +5142,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
   }
 
   public void validateAggregateParams(SqlCall aggCall, SqlNode filter,
-      SqlValidatorScope scope) {
+      SqlNodeList orderList, 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).
@@ -5175,6 +5176,40 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         throw newValidationError(filter, RESOURCE.aggregateInFilterIllegal());
       }
     }
+    if (orderList != null) {
+      for (SqlNode param : orderList) {
+        if (a.findAgg(param) != null) {
+          throw newValidationError(aggCall,
+              RESOURCE.aggregateInWithinGroupIllegal());
+        }
+      }
+    }
+
+    final SqlAggFunction op = (SqlAggFunction) aggCall.getOperator();
+    switch (op.requiresGroupOrder()) {
+    case MANDATORY:
+      if (orderList == null || orderList.size() == 0) {
+        throw newValidationError(aggCall,
+            RESOURCE.aggregateMissingWithinGroupClause(op.getName()));
+      }
+      break;
+    case OPTIONAL:
+      break;
+    case IGNORED:
+      // rewrite the order list to empty
+      if (orderList != null) {
+        orderList.getList().clear();
+      }
+      break;
+    case FORBIDDEN:
+      if (orderList != null && orderList.size() != 0) {
+        throw newValidationError(aggCall,
+            RESOURCE.withinGroupClauseIllegalInAggregate(op.getName()));
+      }
+      break;
+    default:
+      throw new AssertionError(op);
+    }
   }
 
   public void validateCall(

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 e9031ba..5d6fc16 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java
@@ -20,6 +20,7 @@ import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
@@ -794,12 +795,11 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
         aggregate.getGroupSet().rebuild();
     // 2. agg functions
     for (AggregateCall aggCall : aggregate.getAggCallList()) {
-      for (int i : aggCall.getArgList()) {
-        inputFieldsUsed.set(i);
-      }
+      inputFieldsUsed.addAll(aggCall.getArgList());
       if (aggCall.filterArg >= 0) {
         inputFieldsUsed.set(aggCall.filterArg);
       }
+      inputFieldsUsed.addAll(RelCollations.ordinals(aggCall.collation));
     }
 
     // Create input with trimmed columns.
@@ -871,6 +871,7 @@ public class RelFieldTrimmer implements ReflectiveVisitor {
                 .distinct(aggCall.isDistinct())
                 .filter(filterArg)
                 .approximate(aggCall.isApproximate())
+                .sort(relBuilder.fields(aggCall.collation))
                 .as(aggCall.name);
         mapping.set(j, groupCount + indicatorCount + newAggCallList.size());
         newAggCallList.add(newAggCall);

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 9750664..9512312 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -199,6 +199,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
 
 import static org.apache.calcite.sql.SqlUtil.stripAs;
@@ -1124,9 +1125,10 @@ public class SqlToRelConverter {
             LogicalAggregate.create(seek, ImmutableBitSet.of(), null,
                 ImmutableList.of(
                     AggregateCall.create(SqlStdOperatorTable.COUNT, false,
-                        false, ImmutableList.of(), -1, longType, null),
+                        false, ImmutableList.of(), -1, RelCollations.EMPTY,
+                        longType, null),
                     AggregateCall.create(SqlStdOperatorTable.COUNT, false,
-                        false, args, -1, longType, null)));
+                        false, args, -1, RelCollations.EMPTY, longType, null)));
         LogicalJoin join =
             LogicalJoin.create(bb.root, aggregate, rexBuilder.makeLiteral(true),
                 ImmutableSet.of(), JoinRelType.INNER);
@@ -2727,6 +2729,10 @@ public class SqlToRelConverter {
     replaceSubQueries(bb, aggregateFinder.filterList,
         RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
 
+    // also replace sub-queries inside ordering spec in the aggregates
+    replaceSubQueries(bb, aggregateFinder.orderList,
+        RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
+
     // If group-by clause is missing, pretend that it has zero elements.
     if (groupList == null) {
       groupList = SqlNodeList.EMPTY;
@@ -4667,7 +4673,9 @@ public class SqlToRelConverter {
       if (agg != null) {
         final SqlOperator op = call.getOperator();
         if (window == null
-            && (op.isAggregator() || op.getKind() == SqlKind.FILTER)) {
+            && (op.isAggregator()
+            || op.getKind() == SqlKind.FILTER
+            || op.getKind() == SqlKind.WITHIN_GROUP)) {
           return agg.lookupAggregates(call);
         }
       }
@@ -4918,7 +4926,8 @@ public class SqlToRelConverter {
     public Void visit(SqlCall call) {
       switch (call.getKind()) {
       case FILTER:
-        translateAgg((SqlCall) call.operand(0), call.operand(1), call);
+      case WITHIN_GROUP:
+        translateAgg(call);
         return null;
       case SELECT:
         // rchen 2006-10-17:
@@ -4952,7 +4961,7 @@ public class SqlToRelConverter {
           inOver = false;
         } else {
           // We're beyond the one ignored level
-          translateAgg(call, null, call);
+          translateAgg(call);
           return null;
         }
       }
@@ -4967,8 +4976,24 @@ public class SqlToRelConverter {
       return null;
     }
 
-    private void translateAgg(SqlCall call, SqlNode filter, SqlCall outerCall) {
+    private void translateAgg(SqlCall call) {
+      translateAgg(call, null, null, call);
+    }
+
+    private void translateAgg(SqlCall call, SqlNode filter,
+        SqlNodeList orderList, SqlCall outerCall) {
       assert bb.agg == this;
+      assert outerCall != null;
+      switch (call.getKind()) {
+      case FILTER:
+        assert filter == null;
+        translateAgg(call.operand(0), call.operand(1), orderList, outerCall);
+        return;
+      case WITHIN_GROUP:
+        assert orderList == null;
+        translateAgg(call.operand(0), filter, call.operand(1), outerCall);
+        return;
+      }
       final List<Integer> args = new ArrayList<>();
       int filterArg = -1;
       final List<RelDataType> argTypes =
@@ -5026,6 +5051,24 @@ public class SqlToRelConverter {
         distinct = true;
         approximate = true;
       }
+      final RelCollation collation;
+      if (orderList == null || orderList.size() == 0) {
+        collation = RelCollations.EMPTY;
+      } else {
+        collation = RelCollations.of(
+            orderList.getList()
+                .stream()
+                .map(order ->
+                    bb.convertSortExpression(order,
+                        RelFieldCollation.Direction.ASCENDING,
+                        RelFieldCollation.NullDirection.UNSPECIFIED))
+                .map(fieldCollation ->
+                    new RelFieldCollation(
+                        lookupOrCreateGroupExpr(fieldCollation.left),
+                        fieldCollation.getDirection(),
+                        fieldCollation.getNullDirection()))
+                .collect(Collectors.toList()));
+      }
       final AggregateCall aggCall =
           AggregateCall.create(
               aggFunction,
@@ -5033,6 +5076,7 @@ public class SqlToRelConverter {
               approximate,
               args,
               filterArg,
+              collation,
               type,
               nameMap.get(outerCall.toString()));
       final AggregatingSelectScope.Resolved r =
@@ -5358,6 +5402,7 @@ public class SqlToRelConverter {
   private static class AggregateFinder extends SqlBasicVisitor<Void> {
     final SqlNodeList list = new SqlNodeList(SqlParserPos.ZERO);
     final SqlNodeList filterList = new SqlNodeList(SqlParserPos.ZERO);
+    final SqlNodeList orderList = new SqlNodeList(SqlParserPos.ZERO);
 
     @Override public Void visit(SqlCall call) {
       // ignore window aggregates and ranking functions (associated with OVER operator)
@@ -5375,6 +5420,16 @@ public class SqlToRelConverter {
         return null;
       }
 
+      if (call.getOperator().getKind() == SqlKind.WITHIN_GROUP) {
+        // the WHERE in a WITHIN_GROUP must be tracked too so we can call replaceSubQueries on it.
+        // see https://issues.apache.org/jira/browse/CALCITE-1910
+        final SqlNode aggCall = call.getOperandList().get(0);
+        final SqlNodeList orderList = (SqlNodeList) call.getOperandList().get(1);
+        list.add(aggCall);
+        orderList.getList().forEach(this.orderList::add);
+        return null;
+      }
+
       if (call.getOperator().isAggregator()) {
         list.add(call);
         return null;

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 323f65a..3b0be9c 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -94,6 +94,7 @@ import java.math.BigDecimal;
 import java.util.AbstractList;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -785,17 +786,18 @@ public class RelBuilder {
   public AggCall aggregateCall(SqlAggFunction aggFunction, boolean distinct,
       boolean approximate, RexNode filter, String alias,
       Iterable<? extends RexNode> operands) {
-    return aggregateCall(aggFunction, distinct, filter, alias, operands)
-        .approximate(approximate);
+    return aggregateCall(aggFunction, distinct, approximate, filter,
+        ImmutableList.of(), alias, ImmutableList.copyOf(operands));
   }
 
   /** Creates a call to an aggregate function.
    *
    * <p>To add other operands, apply
-   * {@link AggCall#filter(RexNode...)},
    * {@link AggCall#distinct()},
-   * {@link AggCall#as},
-   * {@link AggCall#sort} to the result. */
+   * {@link AggCall#approximate(boolean)},
+   * {@link AggCall#filter(RexNode...)},
+   * {@link AggCall#sort},
+   * {@link AggCall#as} to the result. */
   public AggCall aggregateCall(SqlAggFunction aggFunction,
       Iterable<? extends RexNode> operands) {
     return aggregateCall(aggFunction, false, false, null, ImmutableList.of(),
@@ -805,10 +807,11 @@ public class RelBuilder {
   /** Creates a call to an aggregate function.
    *
    * <p>To add other operands, apply
-   * {@link AggCall#filter(RexNode...)},
    * {@link AggCall#distinct()},
-   * {@link AggCall#as},
-   * {@link AggCall#sort} to the result. */
+   * {@link AggCall#approximate(boolean)},
+   * {@link AggCall#filter(RexNode...)},
+   * {@link AggCall#sort},
+   * {@link AggCall#as} to the result. */
   public AggCall aggregateCall(SqlAggFunction aggFunction,
       RexNode... operands) {
     return aggregateCall(aggFunction, false, false, null, ImmutableList.of(),
@@ -816,7 +819,7 @@ public class RelBuilder {
   }
 
   /** Creates a call to an aggregate function with all applicable operands. */
-  AggCall aggregateCall(SqlAggFunction aggFunction, boolean distinct,
+  protected AggCall aggregateCall(SqlAggFunction aggFunction, boolean distinct,
       boolean approximate, RexNode filter, ImmutableList<RexNode> orderKeys,
       String alias, ImmutableList<RexNode> operands) {
     return new AggCallImpl(aggFunction, distinct, approximate, filter, alias,
@@ -828,11 +831,24 @@ public class RelBuilder {
     return count(false, null, operands);
   }
 
+  /** Creates a call to the {@code COUNT} aggregate function. */
+  public AggCall count(Iterable<? extends RexNode> operands) {
+    return count(false, null, operands);
+  }
+
   /** Creates a call to the {@code COUNT} aggregate function,
    * optionally distinct and with an alias. */
   public AggCall count(boolean distinct, String alias, RexNode... operands) {
-    return aggregateCall(SqlStdOperatorTable.COUNT, distinct, null, alias,
-        ImmutableList.copyOf(operands));
+    return aggregateCall(SqlStdOperatorTable.COUNT, distinct, false, null,
+        ImmutableList.of(), alias, ImmutableList.copyOf(operands));
+  }
+
+  /** Creates a call to the {@code COUNT} aggregate function,
+   * optionally distinct and with an alias. */
+  public AggCall count(boolean distinct, String alias,
+      Iterable<? extends RexNode> operands) {
+    return aggregateCall(SqlStdOperatorTable.COUNT, distinct, false, null,
+        ImmutableList.of(), alias, ImmutableList.copyOf(operands));
   }
 
   /** Creates a call to the {@code COUNT(*)} aggregate function. */
@@ -848,8 +864,8 @@ public class RelBuilder {
   /** Creates a call to the {@code SUM} aggregate function,
    * optionally distinct and with an alias. */
   public AggCall sum(boolean distinct, String alias, RexNode operand) {
-    return aggregateCall(SqlStdOperatorTable.SUM, distinct, null, alias,
-        ImmutableList.of(operand));
+    return aggregateCall(SqlStdOperatorTable.SUM, distinct, false, null,
+        ImmutableList.of(), alias, ImmutableList.of(operand));
   }
 
   /** Creates a call to the {@code AVG} aggregate function. */
@@ -860,8 +876,8 @@ public class RelBuilder {
   /** Creates a call to the {@code AVG} aggregate function,
    * optionally distinct and with an alias. */
   public AggCall avg(boolean distinct, String alias, RexNode operand) {
-    return aggregateCall(SqlStdOperatorTable.AVG, distinct, null, alias,
-        ImmutableList.of(operand));
+    return aggregateCall(SqlStdOperatorTable.AVG, distinct, false, null,
+        ImmutableList.of(), alias, ImmutableList.of(operand));
   }
 
   /** Creates a call to the {@code MIN} aggregate function. */
@@ -872,8 +888,8 @@ public class RelBuilder {
   /** Creates a call to the {@code MIN} aggregate function,
    * optionally with an alias. */
   public AggCall min(String alias, RexNode operand) {
-    return aggregateCall(SqlStdOperatorTable.MIN, false, null, alias,
-        ImmutableList.of(operand));
+    return aggregateCall(SqlStdOperatorTable.MIN, false, false, null,
+        ImmutableList.of(), alias, ImmutableList.of(operand));
   }
 
   /** Creates a call to the {@code MAX} aggregate function,
@@ -884,8 +900,8 @@ public class RelBuilder {
 
   /** Creates a call to the {@code MAX} aggregate function. */
   public AggCall max(String alias, RexNode operand) {
-    return aggregateCall(SqlStdOperatorTable.MAX, false, null, alias,
-        ImmutableList.of(operand));
+    return aggregateCall(SqlStdOperatorTable.MAX, false, false, null,
+        ImmutableList.of(), alias, ImmutableList.of(operand));
   }
 
   // Methods for patterns
@@ -1447,10 +1463,17 @@ public class RelBuilder {
         if (aggCall1.filter != null && !aggCall1.aggFunction.allowsFilter()) {
           throw new IllegalArgumentException("FILTER not allowed");
         }
+        RelCollation collation =
+            RelCollations.of(aggCall1.orderKeys
+                .stream()
+                .map(orderKey ->
+                    collation(orderKey, RelFieldCollation.Direction.ASCENDING,
+                        null, Collections.emptyList()))
+                .collect(Collectors.toList()));
         aggregateCall =
             AggregateCall.create(aggCall1.aggFunction, aggCall1.distinct,
-                aggCall1.approximate, args,
-                filterArg, groupSet.cardinality(), r, null, aggCall1.alias);
+                aggCall1.approximate, args, filterArg, collation,
+                groupSet.cardinality(), r, null, aggCall1.alias);
       } else {
         aggregateCall = ((AggCallImpl2) aggCall).aggregateCall;
       }

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index c7a3d22..8462d95 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -17,6 +17,10 @@
 package org.apache.calcite.util;
 
 import org.apache.calcite.DataContext;
+import org.apache.calcite.adapter.enumerable.AggregateLambdaFactory;
+import org.apache.calcite.adapter.enumerable.OrderedAggregateLambdaFactory;
+import org.apache.calcite.adapter.enumerable.SequencedAdderAggregateLambdaFactory;
+import org.apache.calcite.adapter.enumerable.SourceSorter;
 import org.apache.calcite.adapter.java.ReflectiveSchema;
 import org.apache.calcite.adapter.jdbc.JdbcSchema;
 import org.apache.calcite.avatica.util.DateTimeUtils;
@@ -433,7 +437,21 @@ public enum BuiltInMethod {
   CONTEXT_ROOT(Context.class, "root", true),
   DATA_CONTEXT_GET_QUERY_PROVIDER(DataContext.class, "getQueryProvider"),
   METADATA_REL(Metadata.class, "rel"),
-  STRUCT_ACCESS(SqlFunctions.class, "structAccess", Object.class, int.class, String.class);
+  STRUCT_ACCESS(SqlFunctions.class, "structAccess", Object.class, int.class,
+      String.class),
+  SOURCE_SORTER(SourceSorter.class, Function2.class, Function1.class,
+      Comparator.class),
+  ORDERED_AGGREGATE_LAMBDA_FACTORY(OrderedAggregateLambdaFactory.class,
+      Function0.class, List.class),
+  SEQUENCED_ADDER_AGGREGATE_LAMBDA_FACTORY(SequencedAdderAggregateLambdaFactory.class,
+      Function0.class, List.class),
+  AGG_LAMBDA_FACTORY_ACC_INITIALIZER(AggregateLambdaFactory.class,
+      "accumulatorInitializer"),
+  AGG_LAMBDA_FACTORY_ACC_ADDER(AggregateLambdaFactory.class, "accumulatorAdder"),
+  AGG_LAMBDA_FACTORY_ACC_RESULT_SELECTOR(AggregateLambdaFactory.class,
+      "resultSelector", Function2.class),
+  AGG_LAMBDA_FACTORY_ACC_SINGLE_GROUP_RESULT_SELECTOR(AggregateLambdaFactory.class,
+      "singleGroupResultSelector", Function1.class);
 
   public final Method method;
   public final Constructor constructor;

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/main/java/org/apache/calcite/util/Optionality.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Optionality.java b/core/src/main/java/org/apache/calcite/util/Optionality.java
new file mode 100644
index 0000000..43da19d
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/Optionality.java
@@ -0,0 +1,41 @@
+/*
+ * 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.util;
+
+/**
+ * Four states that describe whether a particular behavior or
+ * property is allowed and/or not allowed.
+ */
+public enum Optionality {
+  /** A property is <em>mandatory</em> if an instance must possess it;
+   * it is an error if it does not. */
+  MANDATORY,
+
+  /** A property is <em>optional</em> if an instance may or may not possess it;
+   * neither state is an error. */
+  OPTIONAL,
+
+  /** A property is <em>ignored</em> if an instance may or may not possess it;
+   * if it possesses the property, the effect is as if it does not. */
+  IGNORED,
+
+  /** A property is <em>forbidden</em> if an instance must not possess it;
+   * it is an error if the instance has the property. */
+  FORBIDDEN
+}
+
+// End Optionality.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 faefd68..edb93b9 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -109,6 +109,9 @@ AggregateIllegalInClause=Aggregate expression is illegal in {0} clause
 WindowedAggregateIllegalInClause=Windowed aggregate expression is illegal in {0} clause
 NestedAggIllegal=Aggregate expressions cannot be nested
 AggregateInFilterIllegal=FILTER must not contain aggregate expression
+AggregateInWithinGroupIllegal=WITHIN GROUP must not contain aggregate expression
+AggregateMissingWithinGroupClause=Aggregate expression ''{0}'' must contain a within group clause
+WithinGroupClauseIllegalInAggregate=Aggregate expression ''{0}'' must not contain a within group clause
 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
@@ -146,6 +149,7 @@ OrderByOverlap=ORDER BY not allowed in both base and referenced windows
 RefWindowWithFrame=Referenced window cannot have framing declarations
 TypeNotSupported=Type ''{0}'' is not supported
 FunctionQuantifierNotAllowed=DISTINCT/ALL not allowed with {0} function
+WithinGroupNotAllowed=WITHIN GROUP not allowed with {0} function
 SomeButNotAllArgumentsAreNamed=Some but not all arguments are named
 DuplicateArgumentName=Duplicate argument name ''{0}''
 DefaultForOptionalParameter=DEFAULT is only allowed for optional parameters

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 c411888..7b63497 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.plan;
 
 import org.apache.calcite.adapter.java.ReflectiveSchema;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.externalize.RelJsonReader;
@@ -140,10 +141,10 @@ public class RelWriterTest {
                   ImmutableList.of(
                       AggregateCall.create(SqlStdOperatorTable.COUNT,
                           true, false, ImmutableList.of(1), -1,
-                          bigIntType, "c"),
+                          RelCollations.EMPTY, bigIntType, "c"),
                       AggregateCall.create(SqlStdOperatorTable.COUNT,
                           false, false, ImmutableList.of(), -1,
-                          bigIntType, "d")));
+                          RelCollations.EMPTY, bigIntType, "d")));
           aggregate.explain(writer);
           return writer.asString();
         });

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 2822c1c..e901868 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
@@ -165,7 +165,8 @@ public class TraitPropagationTest {
 
       // aggregate on s, count
       AggregateCall aggCall = AggregateCall.create(SqlStdOperatorTable.COUNT,
-          false, false, Collections.singletonList(1), -1, sqlBigInt, "cnt");
+          false, false, Collections.singletonList(1), -1, RelCollations.EMPTY,
+          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/calcite/blob/7bc9f140/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 94b98b7..7a5181e 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -274,8 +274,8 @@ public class RelToSqlConverterTest {
     final RelNode root = builder
         .scan("EMP")
         .aggregate(builder.groupKey(),
-            builder.aggregateCall(SqlStdOperatorTable.SUM0, false, null, "s",
-                builder.field(3)))
+            builder.aggregateCall(SqlStdOperatorTable.SUM0, builder.field(3))
+                .as("s"))
         .build();
     final String expectedMysql = "SELECT COALESCE(SUM(`MGR`), 0) AS `s`\n"
         + "FROM `scott`.`EMP`";
@@ -2763,6 +2763,53 @@ public class RelToSqlConverterTest {
         callsUnparseCallOnSqlSelect[0], is(true));
   }
 
+  @Test public void testWithinGroup1() {
+    final String query = "select \"product_class_id\", collect(\"net_weight\") "
+        + "within group (order by \"net_weight\" desc) "
+        + "from \"product\" group by \"product_class_id\"";
+    final String expected = "SELECT \"product_class_id\", COLLECT(\"net_weight\") "
+        + "WITHIN GROUP (ORDER BY \"net_weight\" DESC)\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "GROUP BY \"product_class_id\"";
+    sql(query).ok(expected);
+  }
+
+  @Test public void testWithinGroup2() {
+    final String query = "select \"product_class_id\", collect(\"net_weight\") "
+        + "within group (order by \"low_fat\", \"net_weight\" desc nulls last) "
+        + "from \"product\" group by \"product_class_id\"";
+    final String expected = "SELECT \"product_class_id\", COLLECT(\"net_weight\") "
+        + "WITHIN GROUP (ORDER BY \"low_fat\", \"net_weight\" DESC NULLS LAST)\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "GROUP BY \"product_class_id\"";
+    sql(query).ok(expected);
+  }
+
+  @Test public void testWithinGroup3() {
+    final String query = "select \"product_class_id\", collect(\"net_weight\") "
+        + "within group (order by \"net_weight\" desc), "
+        + "min(\"low_fat\")"
+        + "from \"product\" group by \"product_class_id\"";
+    final String expected = "SELECT \"product_class_id\", COLLECT(\"net_weight\") "
+        + "WITHIN GROUP (ORDER BY \"net_weight\" DESC), MIN(\"low_fat\")\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "GROUP BY \"product_class_id\"";
+    sql(query).ok(expected);
+  }
+
+  @Test public void testWithinGroup4() {
+    // filter in AggregateCall is not unparsed
+    final String query = "select \"product_class_id\", collect(\"net_weight\") "
+        + "within group (order by \"net_weight\" desc) filter (where \"net_weight\" > 0)"
+        + "from \"product\" group by \"product_class_id\"";
+    final String expected = "SELECT \"product_class_id\", COLLECT(\"net_weight\") "
+        + "WITHIN GROUP (ORDER BY \"net_weight\" DESC)\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "GROUP BY \"product_class_id\"";
+    sql(query).ok(expected);
+  }
+
+
   /** Fluid interface to run tests. */
   static class Sql {
     private final SchemaPlus schema;

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 6ad2769..d8dddc3 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
@@ -8194,6 +8194,64 @@ public class SqlParserTest {
     sql(sql).ok(expected);
   }
 
+  @Test public void testWithinGroupClause1() {
+    final String sql = "select col1,\n"
+        + " collect(col2) within group (order by col3)\n"
+        + "from t\n"
+        + "order by col1 limit 10";
+    final String expected = "SELECT `COL1`,"
+        + " (COLLECT(`COL2`) WITHIN GROUP (ORDER BY `COL3`))\n"
+        + "FROM `T`\n"
+        + "ORDER BY `COL1`\n"
+        + "FETCH NEXT 10 ROWS ONLY";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testWithinGroupClause2() {
+    final String sql = "select collect(col2) within group (order by col3)\n"
+        + "from t\n"
+        + "order by col1 limit 10";
+    final String expected = "SELECT"
+        + " (COLLECT(`COL2`) WITHIN GROUP (ORDER BY `COL3`))\n"
+        + "FROM `T`\n"
+        + "ORDER BY `COL1`\n"
+        + "FETCH NEXT 10 ROWS ONLY";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testWithinGroupClause3() {
+    final String sql = "select collect(col2) within group (^)^ "
+        + "from t order by col1 limit 10";
+    sql(sql).fails("(?s).*Encountered \"\\)\" at line 1, column 36\\..*");
+  }
+
+  @Test public void testWithinGroupClause4() {
+    final String sql = "select col1,\n"
+        + " collect(col2) within group (order by col3, col4)\n"
+        + "from t\n"
+        + "order by col1 limit 10";
+    final String expected = "SELECT `COL1`,"
+        + " (COLLECT(`COL2`) WITHIN GROUP (ORDER BY `COL3`, `COL4`))\n"
+        + "FROM `T`\n"
+        + "ORDER BY `COL1`\n"
+        + "FETCH NEXT 10 ROWS ONLY";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testWithinGroupClause5() {
+    final String sql = "select col1,\n"
+        + " collect(col2) within group (\n"
+        + "  order by col3 desc nulls first, col4 asc nulls last)\n"
+        + "from t\n"
+        + "order by col1 limit 10";
+    final String expected = "SELECT `COL1`, (COLLECT(`COL2`) "
+        + "WITHIN GROUP (ORDER BY `COL3` DESC NULLS FIRST, `COL4` NULLS LAST))\n"
+        + "FROM `T`\n"
+        + "ORDER BY `COL1`\n"
+        + "FETCH NEXT 10 ROWS ONLY";
+    sql(sql).ok(expected);
+  }
+
   //~ Inner Interfaces -------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
index 4484762..da7d099 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
@@ -5622,6 +5622,8 @@ public abstract class SqlOperatorBaseTest {
     final String[] values = {"0", "CAST(null AS INTEGER)", "2", "2"};
     tester.checkAgg("collect(x)", values,
         Collections.singletonList("[0, 2, 2]"), (double) 0);
+    tester.checkAgg("collect(x) within group(order by x desc)", values,
+        Collections.singletonList("[2, 2, 0]"), (double) 0);
     Object result1 = -3;
     if (!enable) {
       return;

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 cd87e5f..888ed6c 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -6657,6 +6657,81 @@ public class JdbcTest {
     connection.close();
   }
 
+
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-2224">[CALCITE-2224]
+   * WITHIN GROUP clause for aggregate functions</a>. */
+  @Test public void testWithinGroupClause1() {
+    final String sql = "select X,\n"
+        + " collect(Y) within group (order by Y desc) as \"SET\"\n"
+        + "from (values (1, 'a'), (1, 'b'),\n"
+        + "             (3, 'c'), (3, 'd')) AS t(X, Y)\n"
+        + "group by X\n"
+        + "limit 10";
+    CalciteAssert.that().query(sql)
+        .returnsUnordered("X=1; SET=[b, a]",
+            "X=3; SET=[d, c]");
+  }
+
+  @Test public void testWithinGroupClause2() {
+    final String sql = "select X,\n"
+        + " collect(Y) within group (order by Y desc) as SET_1,\n"
+        + " collect(Y) within group (order by Y asc) as SET_2\n"
+        + "from (values (1, 'a'), (1, 'b'), (3, 'c'), (3, 'd')) AS t(X, Y)\n"
+        + "group by X\n"
+        + "limit 10";
+    CalciteAssert
+        .that()
+        .query(sql)
+        .returnsUnordered("X=1; SET_1=[b, a]; SET_2=[a, b]",
+            "X=3; SET_1=[d, c]; SET_2=[c, d]");
+  }
+
+  @Test public void testWithinGroupClause3() {
+    final String sql = "select"
+        + " collect(Y) within group (order by Y desc) as SET_1,\n"
+        + " collect(Y) within group (order by Y asc) as SET_2\n"
+        + "from (values (1, 'a'), (1, 'b'), (3, 'c'), (3, 'd')) AS t(X, Y)\n"
+        + "limit 10";
+    CalciteAssert.that().query(sql)
+        .returns("SET_1=[d, c, b, a]; SET_2=[a, b, c, d]\n");
+  }
+
+  @Test public void testWithinGroupClause4() {
+    final String sql = "select"
+        + " collect(Y) within group (order by Y desc) as SET_1,\n"
+        + " collect(Y) within group (order by Y asc) as SET_2\n"
+        + "from (values (1, 'a'), (1, 'b'), (3, 'c'), (3, 'd')) AS t(X, Y)\n"
+        + "group by X\n"
+        + "limit 10";
+    CalciteAssert.that().query(sql)
+        .returnsUnordered("SET_1=[b, a]; SET_2=[a, b]",
+            "SET_1=[d, c]; SET_2=[c, d]");
+  }
+
+  @Test public void testWithinGroupClause5() {
+    CalciteAssert
+        .that()
+        .query("select collect(array[X, Y])\n"
+            + " within group (order by Y desc) as \"SET\"\n"
+            + "from (values ('b', 'a'), ('a', 'b'), ('a', 'c'),\n"
+            + "             ('a', 'd')) AS t(X, Y)\n"
+            + "limit 10")
+        .returns("SET=[[a, d], [a, c], [a, b], [b, a]]\n");
+  }
+
+  @Test public void testWithinGroupClause6() {
+    final String sql = "select collect(\"commission\")"
+        + " within group (order by \"commission\")\n"
+        + "from \"hr\".\"emps\"";
+    CalciteAssert.that()
+        .with(CalciteAssert.Config.REGULAR)
+        .query(sql)
+        .explainContains("EnumerableAggregate(group=[{}], "
+            + "EXPR$0=[COLLECT($4) WITHIN GROUP ([4])])")
+        .returns("EXPR$0=[250, 500, 1000]\n");
+  }
+
   private static String sums(int n, boolean c) {
     final StringBuilder b = new StringBuilder();
     for (int i = 0; i < n; i++) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index ec94d94..0c216b3 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -1350,7 +1350,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
             ImmutableList.of(
                 AggregateCall.create(SqlStdOperatorTable.COUNT,
                     false, false, ImmutableIntList.of(),
-                    -1, 2, join, null, null)));
+                    -1, RelCollations.EMPTY, 2, join, null, null)));
     rowSize = mq.getAverageRowSize(aggregate);
     columnSizes = mq.getAverageColumnSizes(aggregate);
     assertThat(columnSizes.size(), equalTo(3));

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 92f98a8..94707fe 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -2862,6 +2862,34 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
     sql(sql).with(tester).ok();
   }
 
+  @Test public void testWithinGroup1() {
+    final String sql = "select deptno,\n"
+        + " collect(empno) within group (order by deptno, hiredate desc)\n"
+        + "from emp\n"
+        + "group by deptno";
+    sql(sql).ok();
+  }
+
+  @Test public void testWithinGroup2() {
+    final String sql = "select dept.deptno,\n"
+        + " collect(sal) within group (order by sal desc) as s,\n"
+        + " collect(sal) within group (order by 1)as s1,\n"
+        + " collect(sal) within group (order by sal)\n"
+        + "  filter (where sal > 2000) as s2\n"
+        + "from emp\n"
+        + "join dept using (deptno)\n"
+        + "group by dept.deptno";
+    sql(sql).ok();
+  }
+
+  @Test public void testWithinGroup3() {
+    final String sql = "select deptno,\n"
+        + " collect(empno) within group (order by empno not in (1, 2)), count(*)\n"
+        + "from emp\n"
+        + "group by deptno";
+    sql(sql).ok();
+  }
+
   /**
    * Visitor that checks that every {@link RelNode} in a tree is valid.
    *

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 abfdc57..a1d4233 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -7116,6 +7116,40 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         .fails("FILTER must not contain aggregate expression");
   }
 
+  @Test public void testWithinGroup() {
+    sql("select deptno,\n"
+        + " collect(empno) within group(order by 1)\n"
+        + "from emp\n"
+        + "group by deptno").ok();
+    sql("select collect(empno) within group(order by 1)\n"
+        + "from emp\n"
+        + "group by ()").ok();
+    sql("select deptno,\n"
+        + " collect(empno) within group(order by deptno)\n"
+        + "from emp\n"
+        + "group by deptno").ok();
+    sql("select deptno,\n"
+        + " collect(empno) within group(order by deptno, hiredate desc)\n"
+        + "from emp\n"
+        + "group by deptno").ok();
+    sql("select deptno,\n"
+        + " collect(empno) within group(\n"
+        + "  order by cast(deptno as varchar), hiredate desc)\n"
+        + "from emp\n"
+        + "group by deptno").ok();
+    sql("select collect(empno) within group(order by 1)\n"
+        + "from emp\n"
+        + "group by deptno").ok();
+    sql("select collect(empno) within group(order by 1)\n"
+        + "from emp").ok();
+    sql("select ^power(deptno, 1) within group(order by 1)^ from emp")
+        .fails("(?s).*WITHIN GROUP not allowed with POWER function.*");
+    sql("select ^collect(empno)^ within group(order by count(*))\n"
+        + "from emp\n"
+        + "group by deptno")
+        .fails("WITHIN GROUP must not contain aggregate expression");
+  }
+
   @Test public void testCorrelatingVariables() {
     // reference to unqualified correlating column
     check("select * from emp where exists (\n"
@@ -8645,6 +8679,7 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
         + "NEXT_VALUE -\n"
         + "PATTERN_EXCLUDE -\n"
         + "PATTERN_PERMUTE -\n"
+        + "WITHIN GROUP left\n"
         + "\n"
         + "PATTERN_QUANTIFIER -\n"
         + "\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
index e0ed1c1..3b68d16 100644
--- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
@@ -72,6 +72,7 @@ import org.apache.calcite.sql.util.ListSqlOperatorTable;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
 import org.apache.calcite.test.CalciteAssert;
+import org.apache.calcite.util.Optionality;
 import org.apache.calcite.util.Util;
 
 import com.google.common.base.Throwables;
@@ -1060,7 +1061,8 @@ public class PlannerTest {
   public static class MyCountAggFunction extends SqlAggFunction {
     public MyCountAggFunction() {
       super("MY_COUNT", null, SqlKind.OTHER_FUNCTION, ReturnTypes.BIGINT, null,
-          OperandTypes.ANY, SqlFunctionCategory.NUMERIC, false, false);
+          OperandTypes.ANY, SqlFunctionCategory.NUMERIC, false, false,
+          Optionality.FORBIDDEN);
     }
 
     @SuppressWarnings("deprecation")

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/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 35d69e5..db6d2a9 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -5296,4 +5296,42 @@ LogicalProject(ANYEMPNO=[$1])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testWithinGroup1">
+        <Resource name="sql">
+            <![CDATA[select deptno, collect(empno) within group (order by deptno, hiredate desc) from emp group by deptno]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalAggregate(group=[{0}], EXPR$1=[COLLECT($1) WITHIN GROUP ([1, 2 DESC])])
+  LogicalProject(DEPTNO=[$7], EMPNO=[$0], HIREDATE=[$4])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testWithinGroup2">
+        <Resource name="sql">
+            <![CDATA[select dept.deptno, collect(sal) within group (order by sal desc) as s, collect(sal) within group (order by 1)as s1, collect(sal) within group (order by sal) filter (where sal > 2000) as s2 from emp join dept using (deptno) group by dept.deptn]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalAggregate(group=[{0}], S=[COLLECT($1) WITHIN GROUP ([1 DESC])], S1=[COLLECT($1) WITHIN GROUP ([2])], S2=[COLLECT($1) WITHIN GROUP ([1]) FILTER $3])
+  LogicalProject(DEPTNO=[$9], SAL=[$5], $f2=[1], $f3=[>($5, 2000)])
+    LogicalJoin(condition=[=($7, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testWithinGroup3">
+        <Resource name="sql">
+            <![CDATA[select deptno, collect(empno) filter (where empno not in (1, 2)), count(*) from emp group by deptno]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalAggregate(group=[{0}], EXPR$1=[COLLECT($1) WITHIN GROUP ([2])], EXPR$2=[COUNT()])
+  LogicalProject(DEPTNO=[$7], EMPNO=[$0], $f2=[AND(<>($0, 1), <>($0, 2))])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
 </Root>

http://git-wip-us.apache.org/repos/asf/calcite/blob/7bc9f140/core/src/test/resources/sql/agg.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/agg.iq b/core/src/test/resources/sql/agg.iq
index 24b75b3..d093a8a 100755
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -2381,4 +2381,127 @@ group by MONTH(HIREDATE);
 
 !ok
 
+# [CALCITE-2224] WITHIN GROUP clause for aggregate functions
+select deptno, collect(empno) within group (order by empno asc) as empnos
+from "scott".emp
+group by deptno;
+
++--------+--------------------------------------+
+| DEPTNO | EMPNOS                               |
++--------+--------------------------------------+
+|     10 | [7782, 7839, 7934]                   |
+|     20 | [7369, 7566, 7788, 7876, 7902]       |
+|     30 | [7499, 7521, 7654, 7698, 7844, 7900] |
++--------+--------------------------------------+
+(3 rows)
+
+!ok
+EnumerableAggregate(group=[{7}], EMPNOS=[COLLECT($0) WITHIN GROUP ([0])])
+  EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+select deptno, collect(empno) within group (order by empno desc) as empnos
+from "scott".emp
+group by deptno;
+
++--------+--------------------------------------+
+| DEPTNO | EMPNOS                               |
++--------+--------------------------------------+
+|     10 | [7934, 7839, 7782]                   |
+|     20 | [7902, 7876, 7788, 7566, 7369]       |
+|     30 | [7900, 7844, 7698, 7654, 7521, 7499] |
++--------+--------------------------------------+
+(3 rows)
+
+!ok
+EnumerableAggregate(group=[{7}], EMPNOS=[COLLECT($0) WITHIN GROUP ([0 DESC])])
+  EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+select deptno, collect(empno) within group (order by empno desc)
+filter (where empno > 7500) as empnos
+from "scott".emp
+group by deptno;
+
++--------+--------------------------------+
+| DEPTNO | EMPNOS                         |
++--------+--------------------------------+
+|     10 | [7934, 7839, 7782]             |
+|     20 | [7902, 7876, 7788, 7566]       |
+|     30 | [7900, 7844, 7698, 7654, 7521] |
++--------+--------------------------------+
+(3 rows)
+
+!ok
+EnumerableAggregate(group=[{0}], EMPNOS=[COLLECT($1) WITHIN GROUP ([1 DESC]) FILTER $2])
+  EnumerableCalc(expr#0..7=[{inputs}], expr#8=[7500], expr#9=[>($t0, $t8)], DEPTNO=[$t7], EMPNO=[$t0], $f2=[$t9])
+    EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+select deptno, collect(empno) within group (order by empno desc) as empnos1,
+collect(empno) within group (order by empno asc) as empnos2
+from "scott".emp
+group by deptno;
+
++--------+--------------------------------------+--------------------------------------+
+| DEPTNO | EMPNOS1                              | EMPNOS2                              |
++--------+--------------------------------------+--------------------------------------+
+|     10 | [7934, 7839, 7782]                   | [7782, 7839, 7934]                   |
+|     20 | [7902, 7876, 7788, 7566, 7369]       | [7369, 7566, 7788, 7876, 7902]       |
+|     30 | [7900, 7844, 7698, 7654, 7521, 7499] | [7499, 7521, 7654, 7698, 7844, 7900] |
++--------+--------------------------------------+--------------------------------------+
+(3 rows)
+
+!ok
+EnumerableAggregate(group=[{7}], EMPNOS1=[COLLECT($0) WITHIN GROUP ([0 DESC])], EMPNOS2=[COLLECT($0) WITHIN GROUP ([0])])
+  EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Aggregate WITHIN GROUP with JOIN
+select dept.deptno,
+  collect(sal) within group (order by sal desc) as s,
+  collect(sal) within group (order by 1)as s1,
+  collect(sal) within group (order by sal) filter (where sal > 2000) as s2
+from "scott".emp
+join "scott".dept using (deptno)
+group by dept.deptno;
+
++--------+-------------------------------------------------------+-------------------------------------------------------+-----------------------------+
+| DEPTNO | S                                                     | S1                                                    | S2                          |
++--------+-------------------------------------------------------+-------------------------------------------------------+-----------------------------+
+|     10 | [5000.00, 2450.00, 1300.00]                           | [2450.00, 5000.00, 1300.00]                           | [2450.00, 5000.00]          |
+|     20 | [3000.00, 3000.00, 2975.00, 1100.00, 800.00]          | [800.00, 2975.00, 3000.00, 1100.00, 3000.00]          | [2975.00, 3000.00, 3000.00] |
+|     30 | [2850.00, 1600.00, 1500.00, 1250.00, 1250.00, 950.00] | [1600.00, 1250.00, 1250.00, 2850.00, 1500.00, 950.00] | [2850.00]                   |
++--------+-------------------------------------------------------+-------------------------------------------------------+-----------------------------+
+(3 rows)
+
+!ok
+EnumerableAggregate(group=[{0}], S=[COLLECT($1) WITHIN GROUP ([1 DESC])], S1=[COLLECT($1) WITHIN GROUP ([2])], S2=[COLLECT($1) WITHIN GROUP ([1]) FILTER $3])
+  EnumerableCalc(expr#0..3=[{inputs}], expr#4=[1], expr#5=[2000], expr#6=[>($t2, $t5)], expr#7=[IS TRUE($t6)], DEPTNO=[$t0], SAL=[$t2], $f2=[$t4], $f3=[$t7])
+    EnumerableJoin(condition=[=($0, $3)], joinType=[inner])
+      EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0])
+        EnumerableTableScan(table=[[scott, DEPT]])
+      EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+        EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+select deptno, collect(empno + 1) within group (order by 1) as empnos
+from "scott".emp
+group by deptno;
+
++--------+--------------------------------------+
+| DEPTNO | EMPNOS                               |
++--------+--------------------------------------+
+|     10 | [7783, 7840, 7935]                   |
+|     20 | [7370, 7567, 7789, 7877, 7903]       |
+|     30 | [7500, 7522, 7655, 7699, 7845, 7901] |
++--------+--------------------------------------+
+(3 rows)
+
+!ok
+EnumerableAggregate(group=[{0}], EMPNOS=[COLLECT($1) WITHIN GROUP ([2])])
+  EnumerableCalc(expr#0..7=[{inputs}], expr#8=[1], expr#9=[+($t0, $t8)], DEPTNO=[$t7], $f1=[$t9], $f2=[$t8])
+    EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
 # End agg.iq