calcite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jh...@apache.org
Subject [3/7] incubator-calcite git commit: [CALCITE-879] COLLECT aggregate function
Date Wed, 07 Oct 2015 19:15:26 GMT
[CALCITE-879] COLLECT aggregate function

Allow multisets and maps to be instances of RexLiteral

Add RelBuilder.values


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

Branch: refs/heads/master
Commit: 8f2b32cda8ae95e66490a83c8ef17411e66e1095
Parents: d618cd9
Author: Julian Hyde <jhyde@apache.org>
Authored: Tue Oct 6 13:54:23 2015 -0700
Committer: Julian Hyde <jhyde@apache.org>
Committed: Tue Oct 6 17:08:06 2015 -0700

----------------------------------------------------------------------
 .../calcite/adapter/enumerable/RexImpTable.java | 24 +++++
 .../java/org/apache/calcite/rex/RexBuilder.java | 33 ++++++-
 .../java/org/apache/calcite/rex/RexLiteral.java | 18 ++++
 .../calcite/sql/fun/SqlStdOperatorTable.java    | 10 +--
 .../apache/calcite/sql/type/ReturnTypes.java    | 10 +++
 .../calcite/sql/type/SqlTypeTransforms.java     | 15 ++++
 .../org/apache/calcite/tools/RelBuilder.java    | 95 +++++++++++++++++---
 .../calcite/sql/test/SqlOperatorBaseTest.java   | 29 +++++-
 .../apache/calcite/test/SqlValidatorTest.java   |  5 +-
 core/src/test/resources/sql/agg.oq              | 48 ++++++++++
 site/_docs/reference.md                         |  1 +
 11 files changed, 262 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index 29c107f..5209fe3 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -96,6 +96,7 @@ import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CAST;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CEIL;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHARACTER_LENGTH;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHAR_LENGTH;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.COLLECT;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CONCAT;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.COUNT;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_DATE;
@@ -323,6 +324,7 @@ public class RexImpTable {
     aggMap.put(MIN, minMax);
     aggMap.put(MAX, minMax);
     aggMap.put(SINGLE_VALUE, constructorSupplier(SingleValueImplementor.class));
+    aggMap.put(COLLECT, constructorSupplier(CollectImplementor.class));
     winAggMap.put(RANK, constructorSupplier(RankImplementor.class));
     winAggMap.put(DENSE_RANK, constructorSupplier(DenseRankImplementor.class));
     winAggMap.put(ROW_NUMBER, constructorSupplier(RowNumberImplementor.class));
@@ -1071,6 +1073,28 @@ public class RexImpTable {
     }
   }
 
+  /** Implementor for the {@code COLLECT} aggregate function. */
+  static class CollectImplementor extends StrictAggImplementor {
+    @Override protected void implementNotNullReset(AggContext info,
+        AggResetContext reset) {
+      // acc[0] = new ArrayList();
+      reset.currentBlock().add(
+          Expressions.statement(
+              Expressions.assign(reset.accumulator().get(0),
+                  Expressions.new_(ArrayList.class))));
+    }
+
+    @Override public void implementNotNullAdd(AggContext info,
+        AggAddContext add) {
+      // acc[0].add(arg);
+      add.currentBlock().add(
+          Expressions.statement(
+              Expressions.call(add.accumulator().get(0),
+                  BuiltInMethod.COLLECTION_ADD.method,
+                  add.arguments().get(0))));
+    }
+  }
+
   /** Implementor for user-defined aggregate functions. */
   public static class UserDefinedAggReflectiveImplementor
       extends StrictAggImplementor {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/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 36b27d6..b8c64e3 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -26,6 +26,7 @@ import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.SqlFunctions;
 import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlIntervalQualifier;
@@ -38,6 +39,7 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.type.ArraySqlType;
 import org.apache.calcite.sql.type.IntervalSqlType;
 import org.apache.calcite.sql.type.MapSqlType;
+import org.apache.calcite.sql.type.MultisetSqlType;
 import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
@@ -1249,7 +1251,7 @@ public class RexBuilder {
       final MapSqlType mapType = (MapSqlType) type;
       @SuppressWarnings("unchecked")
       final Map<Object, Object> map = (Map) value;
-      operands = new ArrayList<RexNode>();
+      operands = new ArrayList<>();
       for (Map.Entry<Object, Object> entry : map.entrySet()) {
         operands.add(
             makeLiteral(entry.getKey(), mapType.getKeyType(), allowCast));
@@ -1261,12 +1263,39 @@ public class RexBuilder {
       final ArraySqlType arrayType = (ArraySqlType) type;
       @SuppressWarnings("unchecked")
       final List<Object> listValue = (List) value;
-      operands = new ArrayList<RexNode>();
+      operands = new ArrayList<>();
       for (Object entry : listValue) {
         operands.add(
             makeLiteral(entry, arrayType.getComponentType(), allowCast));
       }
       return makeCall(SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR, operands);
+    case MULTISET:
+      final MultisetSqlType multisetType = (MultisetSqlType) type;
+      operands = new ArrayList<>();
+      for (Object entry : (List) value) {
+        final RexNode e = entry instanceof RexLiteral
+            ? (RexNode) entry
+            : makeLiteral(entry, multisetType.getComponentType(), allowCast);
+        operands.add(e);
+      }
+      if (allowCast) {
+        return makeCall(SqlStdOperatorTable.MULTISET_VALUE, operands);
+      } else {
+        return new RexLiteral((Comparable) FlatLists.of(operands), type,
+            type.getSqlTypeName());
+      }
+    case ROW:
+      operands = new ArrayList<>();
+      //noinspection unchecked
+      for (Pair<RelDataTypeField, Object> pair
+          : Pair.zip(type.getFieldList(), (List<Object>) value)) {
+        final RexNode e = pair.right instanceof RexLiteral
+            ? (RexNode) pair.right
+            : makeLiteral(pair.right, pair.left.getType(), allowCast);
+        operands.add(e);
+      }
+      return new RexLiteral((Comparable) FlatLists.of(operands), type,
+          type.getSqlTypeName());
     case ANY:
       return makeLiteral(value, guessType(value), allowCast);
     default:

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
index 0f8dceb..bd07072 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -40,6 +40,7 @@ import java.io.StringWriter;
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.util.AbstractList;
 import java.util.Calendar;
 import java.util.List;
 import java.util.Map;
@@ -236,6 +237,9 @@ public class RexLiteral extends RexNode {
           && (((NlsString) value).getCollation() != null);
     case SYMBOL:
       return value instanceof Enum;
+    case ROW:
+    case MULTISET:
+      return value instanceof List;
     case ANY:
       // Literal of type ANY is not legal. "CAST(2 AS ANY)" remains
       // an integer literal surrounded by a cast function.
@@ -387,6 +391,20 @@ public class RexLiteral extends RexNode {
         pw.print("null");
       }
       break;
+    case MULTISET:
+    case ROW:
+      @SuppressWarnings("unchecked") final List<RexLiteral> list = (List) value;
+      pw.print(
+          new AbstractList<String>() {
+            public String get(int index) {
+              return list.get(index).digest;
+            }
+
+            public int size() {
+              return list.size();
+            }
+          });
+      break;
     default:
       assert valueMatchesType(value, typeName, true);
       throw Util.needToImplement(typeName);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/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 fa5fda5..8aff020 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
@@ -1494,14 +1494,14 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable
{
   /**
    * The COLLECT operator. Multiset aggregator function.
    */
-  public static final SqlFunction COLLECT =
-      new SqlFunction(
-          "COLLECT",
+  public static final SqlAggFunction COLLECT =
+      new SqlAggFunction("COLLECT",
           SqlKind.OTHER_FUNCTION,
-          ReturnTypes.ARG0,
+          ReturnTypes.TO_MULTISET,
           null,
           OperandTypes.ANY,
-          SqlFunctionCategory.SYSTEM);
+          SqlFunctionCategory.SYSTEM) {
+      };
 
   /**
    * The FUSION operator. Multiset aggregator function.

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
index 7095ac6..0a755c6 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
@@ -310,6 +310,16 @@ public abstract class ReturnTypes {
               -1);
         }
       };
+
+  /**
+   * Returns a multiset type.
+   *
+   * <p>For example, given <code>INTEGER</code>, returns
+   * <code>INTEGER MULTISET</code>.
+   */
+  public static final SqlReturnTypeInference TO_MULTISET =
+      cascade(ARG0, SqlTypeTransforms.TO_MULTISET);
+
   /**
    * Returns the element type of a multiset
    */

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
index 89ab33b..2f4668f 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeTransforms.java
@@ -151,6 +151,21 @@ public abstract class SqlTypeTransforms {
       };
 
   /**
+   * Parameter type-inference transform strategy that wraps a given type
+   * in a multiset.
+   *
+   * @see org.apache.calcite.rel.type.RelDataTypeFactory#createMultisetType(RelDataType,
long)
+   */
+  public static final SqlTypeTransform TO_MULTISET =
+      new SqlTypeTransform() {
+        public RelDataType transformType(SqlOperatorBinding opBinding,
+            RelDataType typeToTransform) {
+          return opBinding.getTypeFactory().createMultisetType(typeToTransform,
+              -1);
+        }
+      };
+
+  /**
    * Parameter type-inference transform strategy where a derived type must be
    * a struct type with precisely one field and the returned type is the type
    * of that field.

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/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 ca74f31..a403c6a 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -73,6 +73,7 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -170,6 +171,11 @@ public class RelBuilder {
     return cluster.getTypeFactory();
   }
 
+  /** Returns the builder for {@link RexNode} expressions. */
+  public RexBuilder getRexBuilder() {
+    return cluster.getRexBuilder();
+  }
+
   /** Creates a {@link RelBuilderFactory}, a partially-created RelBuilder.
    * Just add a {@link RelOptCluster} and a {@link RelOptSchema} */
   public static RelBuilderFactory proto(final Context context) {
@@ -401,6 +407,18 @@ public class RelBuilder {
     return fields(Mappings.asList(mapping));
   }
 
+  /** Creates an access to a field by name. */
+  public RexNode dot(RexNode node, String fieldName) {
+    final RexBuilder builder = cluster.getRexBuilder();
+    return builder.makeFieldAccess(node, fieldName, true);
+  }
+
+  /** Creates an access to a field by ordinal. */
+  public RexNode dot(RexNode node, int fieldOrdinal) {
+    final RexBuilder builder = cluster.getRexBuilder();
+    return builder.makeFieldAccess(node, fieldOrdinal);
+  }
+
   /** Creates a call to a scalar operator. */
   public RexNode call(SqlOperator operator, RexNode... operands) {
     final RexBuilder builder = cluster.getRexBuilder();
@@ -521,7 +539,7 @@ public class RelBuilder {
 
   /** Creates a group key. */
   public GroupKey groupKey(Iterable<? extends RexNode> nodes) {
-    return new GroupKeyImpl(ImmutableList.copyOf(nodes), null);
+    return new GroupKeyImpl(ImmutableList.copyOf(nodes), null, null);
   }
 
   /** Creates a group key with grouping sets. */
@@ -532,7 +550,7 @@ public class RelBuilder {
     for (Iterable<? extends RexNode> nodeList : nodeLists) {
       builder.add(ImmutableList.copyOf(nodeList));
     }
-    return new GroupKeyImpl(ImmutableList.copyOf(nodes), builder.build());
+    return new GroupKeyImpl(ImmutableList.copyOf(nodes), builder.build(), null);
   }
 
   /** Creates a group key of fields identified by ordinal. */
@@ -1013,7 +1031,7 @@ public class RelBuilder {
    *
    * <p>If there are zero rows, or if all values of a any column are
    * null, this method cannot deduce the type of columns. For these cases,
-   * call {@link #values(RelDataType, Iterable)}.
+   * call {@link #values(Iterable, RelDataType)}.
    *
    * @param fieldNames Field names
    * @param values Values
@@ -1053,7 +1071,7 @@ public class RelBuilder {
       rowTypeBuilder.add(name, type);
     }
     final RelDataType rowType = rowTypeBuilder.build();
-    return values(rowType, tupleList);
+    return values(tupleList, rowType);
   }
 
   private ImmutableList<ImmutableList<RexLiteral>> tupleList(int columnCount,
@@ -1106,13 +1124,13 @@ public class RelBuilder {
    * cannot, such as all values of a column being null, or there being zero
    * rows.
    *
-   * @param rowType Row type
    * @param tupleList Tuple list
+   * @param rowType Row type
    */
-  protected RelBuilder values(RelDataType rowType,
-      Iterable<ImmutableList<RexLiteral>> tupleList) {
-    RelNode values = valuesFactory.createValues(cluster, rowType,
-        ImmutableList.copyOf(tupleList));
+  public RelBuilder values(Iterable<? extends List<RexLiteral>> tupleList,
+      RelDataType rowType) {
+    RelNode values =
+        valuesFactory.createValues(cluster, rowType, copy(tupleList));
     push(values);
     return this;
   }
@@ -1123,7 +1141,30 @@ public class RelBuilder {
    * @param rowType Row type
    */
   public RelBuilder values(RelDataType rowType) {
-    return values(rowType, ImmutableList.<ImmutableList<RexLiteral>>of());
+    return values(ImmutableList.<ImmutableList<RexLiteral>>of(), rowType);
+  }
+
+  /** Converts an iterable of lists into an immutable list of immutable lists
+   * with the same contents. Returns the same object if possible. */
+  private static <E> ImmutableList<ImmutableList<E>>
+  copy(Iterable<? extends List<E>> tupleList) {
+    final ImmutableList.Builder<ImmutableList<E>> builder =
+        ImmutableList.builder();
+    int changeCount = 0;
+    for (List<E> literals : tupleList) {
+      final ImmutableList<E> literals2 =
+          ImmutableList.copyOf(literals);
+      builder.add(literals2);
+      if (literals != literals2) {
+        ++changeCount;
+      }
+    }
+    if (changeCount == 0) {
+      // don't make a copy if we don't have to
+      //noinspection unchecked
+      return (ImmutableList<ImmutableList<E>>) tupleList;
+    }
+    return builder.build();
   }
 
   /** Creates a limit without a sort. */
@@ -1288,6 +1329,20 @@ public class RelBuilder {
             }));
   }
 
+  /** Clears the stack.
+   *
+   * <p>The builder's state is now the same as when it was created. */
+  public void clear() {
+    stack.clear();
+  }
+
+  protected String getAlias() {
+    final Frame frame = Stacks.peek(stack);
+    return frame.right.size() == 1
+        ? frame.right.get(0).left
+        : null;
+  }
+
   /** Information necessary to create a call to an aggregate function.
    *
    * @see RelBuilder#aggregateCall */
@@ -1298,17 +1353,33 @@ public class RelBuilder {
    *
    * @see RelBuilder#groupKey */
   public interface GroupKey {
+    /** Assigns an alias to this group key.
+     *
+     * <p>Used to assign field names in the {@code group} operation. */
+    GroupKey alias(String alias);
   }
 
   /** Implementation of {@link RelBuilder.GroupKey}. */
-  private static class GroupKeyImpl implements GroupKey {
+  protected static class GroupKeyImpl implements GroupKey {
     final ImmutableList<RexNode> nodes;
     final ImmutableList<ImmutableList<RexNode>> nodeLists;
+    final String alias;
 
     GroupKeyImpl(ImmutableList<RexNode> nodes,
-        ImmutableList<ImmutableList<RexNode>> nodeLists) {
+        ImmutableList<ImmutableList<RexNode>> nodeLists, String alias) {
       this.nodes = Preconditions.checkNotNull(nodes);
       this.nodeLists = nodeLists;
+      this.alias = alias;
+    }
+
+    @Override public String toString() {
+      return alias == null ? nodes.toString() : nodes + " as " + alias;
+    }
+
+    public GroupKey alias(String alias) {
+      return Objects.equals(this.alias, alias)
+          ? this
+          : new GroupKeyImpl(nodes, nodeLists, alias);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/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 82041cc..47161b3 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
@@ -4107,10 +4107,31 @@ public abstract class SqlOperatorBaseTest {
   }
 
   @Test public void testCollectFunc() {
-    tester.setFor(
-        SqlStdOperatorTable.COLLECT,
-        VM_FENNEL,
-        VM_JAVA);
+    tester.setFor(SqlStdOperatorTable.COLLECT, VM_FENNEL, VM_JAVA);
+    tester.checkFails("collect(^*^)", "Unknown identifier '\\*'", false);
+    checkAggType(tester, "collect(1)", "INTEGER NOT NULL MULTISET NOT NULL");
+    checkAggType(tester,
+        "collect(1.2)", "DECIMAL(2, 1) NOT NULL MULTISET NOT NULL");
+    checkAggType(tester,
+        "collect(DISTINCT 1.5)", "DECIMAL(2, 1) NOT NULL MULTISET NOT NULL");
+    tester.checkFails("^collect()^",
+        "Invalid number of arguments to function 'COLLECT'. Was expecting 1 arguments",
+        false);
+    tester.checkFails("^collect(1, 2)^",
+        "Invalid number of arguments to function 'COLLECT'. Was expecting 1 arguments",
+        false);
+    final String[] values = {"0", "CAST(null AS INTEGER)", "2", "2"};
+    tester.checkAgg("collect(x)", values, Arrays.asList("[0, 2, 2]"), (double) 0);
+    Object result1 = -3;
+    if (!enable) {
+      return;
+    }
+    tester.checkAgg("collect(CASE x WHEN 0 THEN NULL ELSE -1 END)", values,
+        result1, (double) 0);
+    Object result = -1;
+    tester.checkAgg("collect(DISTINCT CASE x WHEN 0 THEN NULL ELSE -1 END)",
+        values, result, (double) 0);
+    tester.checkAgg("collect(DISTINCT x)", values, 2, (double) 0);
   }
 
   @Test public void testFusionFunc() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/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 a319f43..026f41e 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -6532,9 +6532,8 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
   @Test public void testCollect() {
     check("select collect(deptno) from emp");
     check("select collect(multiset[3]) from emp");
-    // todo. COLLECT is an aggregate function. test that validator only can
-    // take set operators in its select list once aggregation support is
-    // complete
+    sql("select collect(multiset[3]), ^deptno^ from emp")
+        .fails("Expression 'DEPTNO' is not being grouped");
   }
 
   @Test public void testFusion() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/core/src/test/resources/sql/agg.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/agg.oq b/core/src/test/resources/sql/agg.oq
index 17610b9..3787ac2 100644
--- a/core/src/test/resources/sql/agg.oq
+++ b/core/src/test/resources/sql/agg.oq
@@ -588,6 +588,54 @@ from (values cast(-86.4 as float), cast(-100 as float)) as t(v);
 
 !ok
 
+# COLLECT
+select deptno, collect(empno) 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
+
+# COLLECT DISTINCT
+# Disabled in JDK 1.7 because order of values is different
+!if (jdk18) {
+select deptno, collect(distinct job) as jobs
+from "scott".emp
+group by deptno;
++--------+-----------------------------+
+| DEPTNO | JOBS                        |
++--------+-----------------------------+
+|     10 | [MANAGER, CLERK, PRESIDENT] |
+|     20 | [CLERK, ANALYST, MANAGER]   |
+|     30 | [SALESMAN, MANAGER, CLERK]  |
++--------+-----------------------------+
+(3 rows)
+
+!ok
+!}
+
+# COLLECT ... FILTER
+select deptno, collect(empno) filter (where empno < 7550) as empnos
+from "scott".emp
+group by deptno;
++--------+--------------+
+| DEPTNO | EMPNOS       |
++--------+--------------+
+|     10 | []           |
+|     20 | [7369]       |
+|     30 | [7499, 7521] |
++--------+--------------+
+(3 rows)
+
+!ok
+
 # Aggregate FILTER
 select deptno,
   sum(sal) filter (where job = 'CLERK') c_sal,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/8f2b32cd/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 5d2e51f..7df7926 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -533,6 +533,7 @@ passed to the aggregate function.
 
 | Operator syntax                    | Description
 |:---------------------------------- |:-----------
+| COLLECT( [ DISTINCT ] value)       | Returns a multiset of the values
 | COUNT( [ DISTINCT ] value [, value]* ) | Returns the number of input rows for which *value*
is not null (wholly not null if *value* is composite)
 | COUNT(*)                           | Returns the number of input rows
 | AVG( [ DISTINCT ] numeric)         | Returns the average (arithmetic mean) of *numeric*
across all input values


Mime
View raw message