inputType)
+ {
+ CQL3Type elementsType = elementsType(inputType).asCQL3Type();
+ NativeAggregateFunction function = getAvgFunction((CQL3Type.Native) elementsType);
+ return new CollectionAggregationFunction(name, inputType, function);
+ }
+
+ private static NativeAggregateFunction getAvgFunction(CQL3Type.Native type)
+ {
+ switch (type)
+ {
+ case TINYINT:
+ return AggregateFcts.avgFunctionForByte;
+ case SMALLINT:
+ return AggregateFcts.avgFunctionForShort;
+ case INT:
+ return AggregateFcts.avgFunctionForInt32;
+ case BIGINT:
+ return AggregateFcts.avgFunctionForLong;
+ case FLOAT:
+ return AggregateFcts.avgFunctionForFloat;
+ case DOUBLE:
+ return AggregateFcts.avgFunctionForDouble;
+ case VARINT:
+ return AggregateFcts.avgFunctionForVarint;
+ case DECIMAL:
+ return AggregateFcts.avgFunctionForDecimal;
+ default:
+ throw new AssertionError("Expected numeric collection but found " + type);
+ }
+ }
+
+ /**
+ * @return the type of the elements of the specified collection type.
+ */
+ private static AbstractType> elementsType(CollectionType> type)
+ {
+ if (type.kind == CollectionType.Kind.LIST)
+ {
+ return ((ListType>) type).getElementsType();
+ }
+
+ if (type.kind == CollectionType.Kind.SET)
+ {
+ return ((SetType>) type).getElementsType();
+ }
+
+ throw new AssertionError("Cannot get the element type of: " + type);
+ }
+
+ /**
+ * A {@link NativeScalarFunction} for aggregating the elements of a collection according to the aggregator of
+ * a certain {@link NativeAggregateFunction}.
+ *
+ * {@link NativeAggregateFunction} is meant to be used for aggregating values accross rows, but here we use that
+ * function to aggregate the elements of a single collection value. That way, functions such as {@code avg} and
+ * {@code collection_avg} should have the same behaviour when applied to row columns or collection elements.
+ */
+ private static class CollectionAggregationFunction extends NativeScalarFunction
+ {
+ private final CollectionType> inputType;
+ private final NativeAggregateFunction aggregateFunction;
+
+ public CollectionAggregationFunction(String name,
+ CollectionType> inputType,
+ NativeAggregateFunction aggregateFunction)
+ {
+ super(name, aggregateFunction.returnType, inputType);
+ this.inputType = inputType;
+ this.aggregateFunction = aggregateFunction;
+ }
+
+ @Override
+ public ByteBuffer execute(ProtocolVersion version, List parameters)
+ {
+ ByteBuffer value = parameters.get(0);
+ if (value == null)
+ return null;
+
+ AggregateFunction.Aggregate aggregate = aggregateFunction.newAggregate();
+ inputType.forEach(value, version, element -> aggregate.addInput(version, Collections.singletonList(element)));
+ return aggregate.compute(version);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionParameter.java b/src/java/org/apache/cassandra/cql3/functions/FunctionParameter.java
index 0d5a0c4a12..78ad7a33e3 100644
--- a/src/java/org/apache/cassandra/cql3/functions/FunctionParameter.java
+++ b/src/java/org/apache/cassandra/cql3/functions/FunctionParameter.java
@@ -22,6 +22,11 @@ import javax.annotation.Nullable;
import org.apache.cassandra.cql3.AssignmentTestable;
import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CollectionType;
+import org.apache.cassandra.db.marshal.ListType;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.db.marshal.NumberType;
+import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import static java.lang.String.format;
@@ -42,7 +47,10 @@ public interface FunctionParameter
* @return the inferred data type of the parameter, or {@link null} it isn't possible to infer it
*/
@Nullable
- AbstractType> inferType(String keyspace, AssignmentTestable arg, @Nullable AbstractType> receiverType);
+ default AbstractType> inferType(String keyspace, AssignmentTestable arg, @Nullable AbstractType> receiverType)
+ {
+ return arg.getCompatibleTypeIfKnown(keyspace);
+ }
void validateType(FunctionName name, AssignmentTestable arg, AbstractType> argType);
@@ -106,4 +114,123 @@ public interface FunctionParameter
}
};
}
+
+ /**
+ * @return a function parameter definition that accepts values of type {@link CollectionType}, independently of the
+ * types of its elements.
+ */
+ public static FunctionParameter anyCollection()
+ {
+ return new FunctionParameter()
+ {
+ @Override
+ public void validateType(FunctionName name, AssignmentTestable arg, AbstractType> argType)
+ {
+ if (!argType.isCollection())
+ throw new InvalidRequestException(format("Function %s requires a collection argument, " +
+ "but found argument %s of type %s",
+ name, arg, argType.asCQL3Type()));
+ }
+
+ @Override
+ public String toString()
+ {
+ return "collection";
+ }
+ };
+ }
+
+ /**
+ * @return a function parameter definition that accepts values of type {@link SetType} or {@link ListType}.
+ */
+ public static FunctionParameter setOrList()
+ {
+ return new FunctionParameter()
+ {
+ @Override
+ public void validateType(FunctionName name, AssignmentTestable arg, AbstractType> argType)
+ {
+ if (argType.isCollection())
+ {
+ CollectionType.Kind kind = ((CollectionType>) argType).kind;
+ if (kind == CollectionType.Kind.SET || kind == CollectionType.Kind.LIST)
+ return;
+ }
+
+ throw new InvalidRequestException(format("Function %s requires a set or list argument, " +
+ "but found argument %s of type %s",
+ name, arg, argType.asCQL3Type()));
+ }
+
+ @Override
+ public String toString()
+ {
+ return "numeric_set_or_list";
+ }
+ };
+ }
+
+ /**
+ * @return a function parameter definition that accepts values of type {@link SetType} or {@link ListType},
+ * provided that its elements are numeric.
+ */
+ public static FunctionParameter numericSetOrList()
+ {
+ return new FunctionParameter()
+ {
+ @Override
+ public void validateType(FunctionName name, AssignmentTestable arg, AbstractType> argType)
+ {
+ AbstractType> elementType = null;
+ if (argType.isCollection())
+ {
+ CollectionType> collectionType = (CollectionType>) argType;
+ if (collectionType.kind == CollectionType.Kind.SET)
+ {
+ elementType = ((SetType>) argType).getElementsType();
+ }
+ else if (collectionType.kind == CollectionType.Kind.LIST)
+ {
+ elementType = ((ListType>) argType).getElementsType();
+ }
+ }
+
+ if (!(elementType instanceof NumberType))
+ throw new InvalidRequestException(format("Function %s requires a numeric set/list argument, " +
+ "but found argument %s of type %s",
+ name, arg, argType.asCQL3Type()));
+ }
+
+ @Override
+ public String toString()
+ {
+ return "numeric_set_or_list";
+ }
+ };
+ }
+
+ /**
+ * @return a function parameter definition that accepts values of type {@link MapType}, independently of the types
+ * of the map keys and values.
+ */
+ public static FunctionParameter anyMap()
+ {
+ return new FunctionParameter()
+ {
+ @Override
+ public void validateType(FunctionName name, AssignmentTestable arg, AbstractType> argType)
+ {
+ if (!argType.isUDT() && !(argType instanceof MapType))
+ throw new InvalidRequestException(format("Function %s requires a map argument, " +
+ "but found argument %s of type %s",
+ name, arg, argType.asCQL3Type()));
+ }
+
+ @Override
+ public String toString()
+ {
+ return "map";
+ }
+ };
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/functions/NativeFunctions.java b/src/java/org/apache/cassandra/cql3/functions/NativeFunctions.java
index 6ea5e81ed4..551662e3e5 100644
--- a/src/java/org/apache/cassandra/cql3/functions/NativeFunctions.java
+++ b/src/java/org/apache/cassandra/cql3/functions/NativeFunctions.java
@@ -40,6 +40,7 @@ public class NativeFunctions
FromJsonFct.addFunctionsTo(this);
OperationFcts.addFunctionsTo(this);
AggregateFcts.addFunctionsTo(this);
+ CollectionFcts.addFunctionsTo(this);
BytesConversionFcts.addFunctionsTo(this);
MathFcts.addFunctionsTo(this);
}
diff --git a/src/java/org/apache/cassandra/db/marshal/CollectionType.java b/src/java/org/apache/cassandra/db/marshal/CollectionType.java
index 5e9916e7e4..a8cc9d9231 100644
--- a/src/java/org/apache/cassandra/db/marshal/CollectionType.java
+++ b/src/java/org/apache/cassandra/db/marshal/CollectionType.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
+import java.util.function.Consumer;
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.cql3.ColumnSpecification;
@@ -357,4 +358,11 @@ public abstract class CollectionType extends AbstractType
ByteBufferUtil.skipWithVIntLength(in);
}
}
+
+ public int size(ByteBuffer buffer)
+ {
+ return CollectionSerializer.readCollectionSize(buffer.duplicate(), ByteBufferAccessor.instance, ProtocolVersion.V3);
+ }
+
+ public abstract void forEach(ByteBuffer input, ProtocolVersion version, Consumer action);
}
diff --git a/src/java/org/apache/cassandra/db/marshal/ListType.java b/src/java/org/apache/cassandra/db/marshal/ListType.java
index f795def3a7..73290d5b6c 100644
--- a/src/java/org/apache/cassandra/db/marshal/ListType.java
+++ b/src/java/org/apache/cassandra/db/marshal/ListType.java
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
import org.apache.cassandra.cql3.Json;
import org.apache.cassandra.cql3.Lists;
@@ -243,4 +244,10 @@ public class ListType extends CollectionType>
{
return setOrListToJsonString(buffer, elements, protocolVersion);
}
+
+ @Override
+ public void forEach(ByteBuffer input, ProtocolVersion version, Consumer action)
+ {
+ serializer.forEach(input, version, action);
+ }
}
diff --git a/src/java/org/apache/cassandra/db/marshal/MapType.java b/src/java/org/apache/cassandra/db/marshal/MapType.java
index be74ff1626..d9bfc8040c 100644
--- a/src/java/org/apache/cassandra/db/marshal/MapType.java
+++ b/src/java/org/apache/cassandra/db/marshal/MapType.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
import org.apache.cassandra.cql3.Json;
import org.apache.cassandra.cql3.Maps;
@@ -377,4 +378,10 @@ public class MapType extends CollectionType