You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ja...@apache.org on 2020/11/03 04:54:39 UTC

[incubator-pinot] branch master updated: scalar functions for array (#6105)

This is an automated email from the ASF dual-hosted git repository.

jackie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new e3b0bfc  scalar functions for array (#6105)
e3b0bfc is described below

commit e3b0bfcdbd796db91a606d591e492741799244b5
Author: SandishKumarHN <sa...@gmail.com>
AuthorDate: Mon Nov 2 20:54:21 2020 -0800

    scalar functions for array (#6105)
    
    Inbuilt scalar functions for array columns
    - array_reverse_int(multi_value_int_field)
    - array_reverse_string(multi_value_string_field)
    - array_sort_int(multi_value_int_field)
    - array_sort_string(multi_value_string_field)
    - array_index_of_int(multi_value_int_field, 2)
    - array_index_of_string(multi_value_string_field, 'foo')
    - array_contains_int(multi_value_int_field, 3)
    - array_contains_string(multi_value_string_field, 'bar')
---
 .../pinot/common/function/FunctionInvoker.java     |  19 +-
 .../pinot/common/function/FunctionUtils.java       |  32 +++
 .../common/function/scalar/ArrayFunctions.java     |  80 +++++++
 .../apache/pinot/common/utils/PinotDataType.java   |  32 ++-
 .../function/ScalarTransformFunctionWrapper.java   | 122 +++++++++-
 .../core/data/function/InbuiltFunctionsTest.java   |  67 ++++++
 .../function/BaseTransformFunctionTest.java        |  47 +++-
 .../ScalarTransformFunctionWrapperTest.java        | 248 ++++++++++++++++++---
 8 files changed, 602 insertions(+), 45 deletions(-)

diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionInvoker.java b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionInvoker.java
index b185d26..aec453c 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionInvoker.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionInvoker.java
@@ -23,6 +23,7 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.pinot.common.utils.PinotDataType;
 
 
@@ -108,7 +109,23 @@ public class FunctionInvoker {
       PinotDataType argumentType = FunctionUtils.getArgumentType(argumentClass);
       Preconditions.checkArgument(parameterType != null && argumentType != null,
           "Cannot convert value from class: %s to class: %s", argumentClass, parameterClass);
-      arguments[i] = parameterType.convert(argument, argumentType);
+      Object convertedArgument = parameterType.convert(argument, argumentType);
+      // For primitive array parameter, convert the argument from Object array to primitive array
+      switch (parameterType) {
+        case INTEGER_ARRAY:
+          convertedArgument = ArrayUtils.toPrimitive((Integer[]) convertedArgument);
+          break;
+        case LONG_ARRAY:
+          convertedArgument = ArrayUtils.toPrimitive((Long[]) convertedArgument);
+          break;
+        case FLOAT_ARRAY:
+          convertedArgument = ArrayUtils.toPrimitive((Float[]) convertedArgument);
+          break;
+        case DOUBLE_ARRAY:
+          convertedArgument = ArrayUtils.toPrimitive((Double[]) convertedArgument);
+          break;
+      }
+      arguments[i] = convertedArgument;
     }
   }
 
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionUtils.java
index 33da3cc..1b04ff7 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionUtils.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionUtils.java
@@ -42,6 +42,11 @@ public class FunctionUtils {
     put(Double.class, PinotDataType.DOUBLE);
     put(String.class, PinotDataType.STRING);
     put(byte[].class, PinotDataType.BYTES);
+    put(int[].class, PinotDataType.INTEGER_ARRAY);
+    put(long[].class, PinotDataType.LONG_ARRAY);
+    put(float[].class, PinotDataType.FLOAT_ARRAY);
+    put(double[].class, PinotDataType.DOUBLE_ARRAY);
+    put(String[].class, PinotDataType.STRING_ARRAY);
   }};
 
   // Types allowed as the function argument (actual value passed into the function) for type conversion
@@ -56,6 +61,15 @@ public class FunctionUtils {
     put(Double.class, PinotDataType.DOUBLE);
     put(String.class, PinotDataType.STRING);
     put(byte[].class, PinotDataType.BYTES);
+    put(int[].class, PinotDataType.INTEGER_ARRAY);
+    put(Integer[].class, PinotDataType.INTEGER_ARRAY);
+    put(long[].class, PinotDataType.LONG_ARRAY);
+    put(Long[].class, PinotDataType.LONG_ARRAY);
+    put(float[].class, PinotDataType.FLOAT_ARRAY);
+    put(Float[].class, PinotDataType.FLOAT_ARRAY);
+    put(double[].class, PinotDataType.DOUBLE_ARRAY);
+    put(Double[].class, PinotDataType.DOUBLE_ARRAY);
+    put(String[].class, PinotDataType.STRING_ARRAY);
   }};
 
   private static final Map<Class<?>, DataType> DATA_TYPE_MAP = new HashMap<Class<?>, DataType>() {{
@@ -69,6 +83,15 @@ public class FunctionUtils {
     put(Double.class, DataType.DOUBLE);
     put(String.class, DataType.STRING);
     put(byte[].class, DataType.BYTES);
+    put(int[].class, DataType.INT);
+    put(Integer[].class, DataType.INT);
+    put(long[].class, DataType.LONG);
+    put(Long[].class, DataType.LONG);
+    put(float[].class, DataType.FLOAT);
+    put(Float[].class, DataType.FLOAT);
+    put(double[].class, DataType.DOUBLE);
+    put(Double[].class, DataType.DOUBLE);
+    put(String[].class, DataType.STRING);
   }};
 
   private static final Map<Class<?>, ColumnDataType> COLUMN_DATA_TYPE_MAP = new HashMap<Class<?>, ColumnDataType>() {{
@@ -82,6 +105,15 @@ public class FunctionUtils {
     put(Double.class, ColumnDataType.DOUBLE);
     put(String.class, ColumnDataType.STRING);
     put(byte[].class, ColumnDataType.BYTES);
+    put(int[].class, ColumnDataType.INT_ARRAY);
+    put(Integer[].class, ColumnDataType.INT_ARRAY);
+    put(long[].class, ColumnDataType.LONG_ARRAY);
+    put(Long[].class, ColumnDataType.LONG_ARRAY);
+    put(float[].class, ColumnDataType.FLOAT_ARRAY);
+    put(Float[].class, ColumnDataType.FLOAT_ARRAY);
+    put(double[].class, ColumnDataType.DOUBLE_ARRAY);
+    put(Double[].class, ColumnDataType.DOUBLE_ARRAY);
+    put(String[].class, ColumnDataType.STRING_ARRAY);
   }};
 
   /**
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java
new file mode 100644
index 0000000..0976ab0
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java
@@ -0,0 +1,80 @@
+/**
+ * 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.pinot.common.function.scalar;
+
+import java.util.Arrays;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.pinot.spi.annotations.ScalarFunction;
+
+
+/**
+ * Inbuilt array scalar functions. See {@link ArrayUtils} for details.
+ */
+public class ArrayFunctions {
+  private ArrayFunctions() {
+  }
+
+  @ScalarFunction
+  public static int[] arrayReverseInt(int[] values) {
+    int[] clone = values.clone();
+    ArrayUtils.reverse(clone);
+    return clone;
+  }
+
+  @ScalarFunction
+  public static String[] arrayReverseString(String[] values) {
+    String[] clone = values.clone();
+    ArrayUtils.reverse(clone);
+    return clone;
+  }
+
+  @ScalarFunction
+  public static int[] arraySortInt(int[] values) {
+    int[] clone = values.clone();
+    Arrays.sort(clone);
+    return clone;
+  }
+
+  @ScalarFunction
+  public static String[] arraySortString(String[] values) {
+    String[] clone = values.clone();
+    Arrays.sort(clone);
+    return clone;
+  }
+
+  @ScalarFunction
+  public static int arrayIndexOfInt(int[] values, int valueToFind) {
+    return ArrayUtils.indexOf(values, valueToFind);
+  }
+
+  @ScalarFunction
+  public static int arrayIndexOfString(String[] values, String valueToFind) {
+    return ArrayUtils.indexOf(values, valueToFind);
+  }
+
+  @ScalarFunction
+  public static boolean arrayContainsInt(int[] values, int valueToFind) {
+    return ArrayUtils.contains(values, valueToFind);
+  }
+
+  @ScalarFunction
+  public static boolean arrayContainsString(String[] values, String valueToFind) {
+    return ArrayUtils.contains(values, valueToFind);
+  }
+}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/utils/PinotDataType.java b/pinot-common/src/main/java/org/apache/pinot/common/utils/PinotDataType.java
index 97c017e..068241b 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/utils/PinotDataType.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/utils/PinotDataType.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.common.utils;
 
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
 import org.apache.pinot.spi.data.FieldSpec;
 import org.apache.pinot.spi.utils.BytesUtils;
@@ -505,7 +506,7 @@ public enum PinotDataType {
     if (isSingleValue()) {
       return new Integer[]{toInteger(value)};
     } else {
-      Object[] valueArray = (Object[]) value;
+      Object[] valueArray = toObjectArray(value);
       int length = valueArray.length;
       Integer[] integerArray = new Integer[length];
       PinotDataType singleValueType = getSingleValueType();
@@ -520,7 +521,7 @@ public enum PinotDataType {
     if (isSingleValue()) {
       return new Long[]{toLong(value)};
     } else {
-      Object[] valueArray = (Object[]) value;
+      Object[] valueArray = toObjectArray(value);
       int length = valueArray.length;
       Long[] longArray = new Long[length];
       PinotDataType singleValueType = getSingleValueType();
@@ -535,7 +536,7 @@ public enum PinotDataType {
     if (isSingleValue()) {
       return new Float[]{toFloat(value)};
     } else {
-      Object[] valueArray = (Object[]) value;
+      Object[] valueArray = toObjectArray(value);
       int length = valueArray.length;
       Float[] floatArray = new Float[length];
       PinotDataType singleValueType = getSingleValueType();
@@ -550,7 +551,7 @@ public enum PinotDataType {
     if (isSingleValue()) {
       return new Double[]{toDouble(value)};
     } else {
-      Object[] valueArray = (Object[]) value;
+      Object[] valueArray = toObjectArray(value);
       int length = valueArray.length;
       Double[] doubleArray = new Double[length];
       PinotDataType singleValueType = getSingleValueType();
@@ -565,7 +566,7 @@ public enum PinotDataType {
     if (isSingleValue()) {
       return new String[]{toString(value)};
     } else {
-      Object[] valueArray = (Object[]) value;
+      Object[] valueArray = toObjectArray(value);
       int length = valueArray.length;
       String[] stringArray = new String[length];
       PinotDataType singleValueType = getSingleValueType();
@@ -576,6 +577,27 @@ public enum PinotDataType {
     }
   }
 
+  private static Object[] toObjectArray(Object array) {
+    Class<?> componentType = array.getClass().getComponentType();
+    if (componentType.isPrimitive()) {
+      if (componentType == int.class) {
+        return ArrayUtils.toObject((int[]) array);
+      }
+      if (componentType == long.class) {
+        return ArrayUtils.toObject((long[]) array);
+      }
+      if (componentType == float.class) {
+        return ArrayUtils.toObject((float[]) array);
+      }
+      if (componentType == double.class) {
+        return ArrayUtils.toObject((double[]) array);
+      }
+      throw new UnsupportedOperationException("Unsupported primitive array type: " + componentType);
+    } else {
+      return (Object[]) array;
+    }
+  }
+
   public Object convert(Object value, PinotDataType sourceType) {
     throw new UnsupportedOperationException("Cannot convert value form " + sourceType + " to " + this);
   }
diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapper.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapper.java
index 958b570..eca96f6 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapper.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapper.java
@@ -54,6 +54,12 @@ public class ScalarTransformFunctionWrapper extends BaseTransformFunction {
   private String[] _stringResults;
   private byte[][] _bytesResults;
 
+  private int[][] _intMVResults;
+  private long[][] _longMVResults;
+  private float[][] _floatMVResults;
+  private double[][] _doubleMVResults;
+  private String[][] _stringMVResults;
+
   public ScalarTransformFunctionWrapper(FunctionInfo functionInfo) {
     _name = functionInfo.getMethod().getName();
     _functionInvoker = new FunctionInvoker(functionInfo);
@@ -95,12 +101,14 @@ public class ScalarTransformFunctionWrapper extends BaseTransformFunction {
     }
     _nonLiteralValues = new Object[_numNonLiteralArguments][];
 
-    DataType resultDataType = FunctionUtils.getDataType(_functionInvoker.getResultClass());
+    Class<?> resultClass = _functionInvoker.getResultClass();
+    DataType resultDataType = FunctionUtils.getDataType(resultClass);
     // Handle unrecognized result class with STRING
     if (resultDataType == null) {
       resultDataType = DataType.STRING;
     }
-    _resultMetadata = new TransformResultMetadata(resultDataType, true, false);
+    boolean isSingleValue = !resultClass.isArray();
+    _resultMetadata = new TransformResultMetadata(resultDataType, isSingleValue, false);
   }
 
   @Override
@@ -222,6 +230,101 @@ public class ScalarTransformFunctionWrapper extends BaseTransformFunction {
     return _bytesResults;
   }
 
+  @Override
+  public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) {
+    if (_resultMetadata.getDataType() != DataType.INT) {
+      return super.transformToIntValuesMV(projectionBlock);
+    }
+    if (_intMVResults == null) {
+      _intMVResults = new int[DocIdSetPlanNode.MAX_DOC_PER_CALL][];
+    }
+    getNonLiteralValues(projectionBlock);
+    int length = projectionBlock.getNumDocs();
+    for (int i = 0; i < length; i++) {
+      for (int j = 0; j < _numNonLiteralArguments; j++) {
+        _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i];
+      }
+      _intMVResults[i] = (int[]) _functionInvoker.invoke(_arguments);
+    }
+    return _intMVResults;
+  }
+
+  @Override
+  public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) {
+    if (_resultMetadata.getDataType() != DataType.LONG) {
+      return super.transformToLongValuesMV(projectionBlock);
+    }
+    if (_longMVResults == null) {
+      _longMVResults = new long[DocIdSetPlanNode.MAX_DOC_PER_CALL][];
+    }
+    getNonLiteralValues(projectionBlock);
+    int length = projectionBlock.getNumDocs();
+    for (int i = 0; i < length; i++) {
+      for (int j = 0; j < _numNonLiteralArguments; j++) {
+        _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i];
+      }
+      _longMVResults[i] = (long[]) _functionInvoker.invoke(_arguments);
+    }
+    return _longMVResults;
+  }
+
+  @Override
+  public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) {
+    if (_resultMetadata.getDataType() != DataType.FLOAT) {
+      return super.transformToFloatValuesMV(projectionBlock);
+    }
+    if (_floatMVResults == null) {
+      _floatMVResults = new float[DocIdSetPlanNode.MAX_DOC_PER_CALL][];
+    }
+    getNonLiteralValues(projectionBlock);
+    int length = projectionBlock.getNumDocs();
+    for (int i = 0; i < length; i++) {
+      for (int j = 0; j < _numNonLiteralArguments; j++) {
+        _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i];
+      }
+      _floatMVResults[i] = (float[]) _functionInvoker.invoke(_arguments);
+    }
+    return _floatMVResults;
+  }
+
+  @Override
+  public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) {
+    if (_resultMetadata.getDataType() != DataType.DOUBLE) {
+      return super.transformToDoubleValuesMV(projectionBlock);
+    }
+    if (_doubleMVResults == null) {
+      _doubleMVResults = new double[DocIdSetPlanNode.MAX_DOC_PER_CALL][];
+    }
+    getNonLiteralValues(projectionBlock);
+    int length = projectionBlock.getNumDocs();
+    for (int i = 0; i < length; i++) {
+      for (int j = 0; j < _numNonLiteralArguments; j++) {
+        _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i];
+      }
+      _doubleMVResults[i] = (double[]) _functionInvoker.invoke(_arguments);
+    }
+    return _doubleMVResults;
+  }
+
+  @Override
+  public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) {
+    if (_resultMetadata.getDataType() != DataType.STRING) {
+      return super.transformToStringValuesMV(projectionBlock);
+    }
+    if (_stringMVResults == null) {
+      _stringMVResults = new String[DocIdSetPlanNode.MAX_DOC_PER_CALL][];
+    }
+    getNonLiteralValues(projectionBlock);
+    int length = projectionBlock.getNumDocs();
+    for (int i = 0; i < length; i++) {
+      for (int j = 0; j < _numNonLiteralArguments; j++) {
+        _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i];
+      }
+      _stringMVResults[i] = (String[]) _functionInvoker.invoke(_arguments);
+    }
+    return _stringMVResults;
+  }
+
   /**
    * Helper method to fetch values for the non-literal transform functions based on the parameter types.
    */
@@ -249,6 +352,21 @@ public class ScalarTransformFunctionWrapper extends BaseTransformFunction {
         case BYTES:
           _nonLiteralValues[i] = transformFunction.transformToBytesValuesSV(projectionBlock);
           break;
+        case INTEGER_ARRAY:
+          _nonLiteralValues[i] = transformFunction.transformToIntValuesMV(projectionBlock);
+          break;
+        case LONG_ARRAY:
+          _nonLiteralValues[i] = transformFunction.transformToLongValuesMV(projectionBlock);
+          break;
+        case FLOAT_ARRAY:
+          _nonLiteralValues[i] = transformFunction.transformToFloatValuesMV(projectionBlock);
+          break;
+        case DOUBLE_ARRAY:
+          _nonLiteralValues[i] = transformFunction.transformToDoubleValuesMV(projectionBlock);
+          break;
+        case STRING_ARRAY:
+          _nonLiteralValues[i] = transformFunction.transformToStringValuesMV(projectionBlock);
+          break;
         default:
           throw new IllegalStateException();
       }
diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/function/InbuiltFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/function/InbuiltFunctionsTest.java
index b1bd0ec..54809a6 100644
--- a/pinot-core/src/test/java/org/apache/pinot/core/data/function/InbuiltFunctionsTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/core/data/function/InbuiltFunctionsTest.java
@@ -374,4 +374,71 @@ public class InbuiltFunctionsTest {
 
     return inputs.toArray(new Object[0][]);
   }
+
+  @Test(dataProvider = "arrayFunctionsDataProvider")
+  public void testArrayFunctions(String functionExpression, List<String> expectedArguments, GenericRow row,
+      Object expectedResult) {
+    testFunction(functionExpression, expectedArguments, row, expectedResult);
+  }
+
+  @DataProvider(name = "arrayFunctionsDataProvider")
+  public Object[][] arrayFunctionsDataProvider() {
+    List<Object[]> inputs = new ArrayList<>();
+
+    GenericRow row = new GenericRow();
+    row.putValue("intArray", new int[]{3, 2, 10, 6, 1, 12});
+    row.putValue("integerArray", new Integer[]{3, 2, 10, 6, 1, 12});
+    row.putValue("stringArray", new String[]{"3", "2", "10", "6", "1", "12"});
+
+    inputs.add(new Object[]{"array_reverse_int(intArray)", Collections.singletonList(
+        "intArray"), row, new int[]{12, 1, 6, 10, 2, 3}});
+    inputs.add(new Object[]{"array_reverse_int(integerArray)", Collections.singletonList(
+        "integerArray"), row, new int[]{12, 1, 6, 10, 2, 3}});
+    inputs.add(new Object[]{"array_reverse_int(stringArray)", Collections.singletonList(
+        "stringArray"), row, new int[]{12, 1, 6, 10, 2, 3}});
+
+    inputs.add(new Object[]{"array_reverse_string(intArray)", Collections.singletonList(
+        "intArray"), row, new String[]{"12", "1", "6", "10", "2", "3"}});
+    inputs.add(new Object[]{"array_reverse_string(integerArray)", Collections.singletonList(
+        "integerArray"), row, new String[]{"12", "1", "6", "10", "2", "3"}});
+    inputs.add(new Object[]{"array_reverse_string(stringArray)", Collections.singletonList(
+        "stringArray"), row, new String[]{"12", "1", "6", "10", "2", "3"}});
+
+    inputs.add(new Object[]{"array_sort_int(intArray)", Collections.singletonList(
+        "intArray"), row, new int[]{1, 2, 3, 6, 10, 12}});
+    inputs.add(new Object[]{"array_sort_int(integerArray)", Collections.singletonList(
+        "integerArray"), row, new int[]{1, 2, 3, 6, 10, 12}});
+    inputs.add(new Object[]{"array_sort_int(stringArray)", Collections.singletonList(
+        "stringArray"), row, new int[]{1, 2, 3, 6, 10, 12}});
+
+    inputs.add(new Object[]{"array_sort_string(intArray)", Collections.singletonList(
+        "intArray"), row, new String[]{"1", "10", "12", "2", "3", "6"}});
+    inputs.add(new Object[]{"array_sort_string(integerArray)", Collections.singletonList(
+        "integerArray"), row, new String[]{"1", "10", "12", "2", "3", "6"}});
+    inputs.add(new Object[]{"array_sort_string(stringArray)", Collections.singletonList(
+        "stringArray"), row, new String[]{"1", "10", "12", "2", "3", "6"}});
+
+    inputs.add(new Object[]{"array_index_of_int(intArray, 2)", Collections.singletonList("intArray"), row, 1});
+    inputs.add(new Object[]{"array_index_of_int(integerArray, 2)", Collections.singletonList("integerArray"), row, 1});
+    inputs.add(new Object[]{"array_index_of_int(stringArray, 2)", Collections.singletonList("stringArray"), row, 1});
+
+    inputs.add(new Object[]{"array_index_of_string(intArray, '2')", Collections.singletonList("intArray"), row, 1});
+    inputs.add(
+        new Object[]{"array_index_of_string(integerArray, '2')", Collections.singletonList("integerArray"), row, 1});
+    inputs
+        .add(new Object[]{"array_index_of_string(stringArray, '2')", Collections.singletonList("stringArray"), row, 1});
+
+    inputs.add(new Object[]{"array_contains_int(intArray, 2)", Collections.singletonList("intArray"), row, true});
+    inputs
+        .add(new Object[]{"array_contains_int(integerArray, 2)", Collections.singletonList("integerArray"), row, true});
+    inputs.add(new Object[]{"array_contains_int(stringArray, 2)", Collections.singletonList("stringArray"), row, true});
+
+    inputs.add(new Object[]{"array_contains_string(intArray, '2')", Collections.singletonList("intArray"), row, true});
+    inputs.add(
+        new Object[]{"array_contains_string(integerArray, '2')", Collections.singletonList("integerArray"), row, true});
+    inputs.add(
+        new Object[]{"array_contains_string(stringArray, '2')", Collections.singletonList("stringArray"), row, true});
+
+    return inputs.toArray(new Object[0][]);
+  }
 }
diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunctionTest.java
index 8d67eb8..91eb467 100644
--- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunctionTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunctionTest.java
@@ -73,6 +73,11 @@ public abstract class BaseTransformFunctionTest {
   protected static final String BYTES_SV_COLUMN = "bytesSV";
   protected static final String STRING_ALPHANUM_SV_COLUMN = "stringAlphaNumSV";
   protected static final String INT_MV_COLUMN = "intMV";
+  protected static final String LONG_MV_COLUMN = "longMV";
+  protected static final String FLOAT_MV_COLUMN = "floatMV";
+  protected static final String DOUBLE_MV_COLUMN = "doubleMV";
+  protected static final String STRING_MV_COLUMN = "stringMV";
+  protected static final String STRING_ALPHANUM_MV_COLUMN = "stringAlphaNumMV";
   protected static final String TIME_COLUMN = "time";
   protected static final String JSON_COLUMN = "json";
   protected final int[] _intSVValues = new int[NUM_ROWS];
@@ -83,6 +88,11 @@ public abstract class BaseTransformFunctionTest {
   protected final String[] _stringAlphaNumericSVValues = new String[NUM_ROWS];
   protected final byte[][] _bytesSVValues = new byte[NUM_ROWS][];
   protected final int[][] _intMVValues = new int[NUM_ROWS][];
+  protected final long[][] _longMVValues = new long[NUM_ROWS][];
+  protected final float[][] _floatMVValues = new float[NUM_ROWS][];
+  protected final double[][] _doubleMVValues = new double[NUM_ROWS][];
+  protected final String[][] _stringMVValues = new String[NUM_ROWS][];
+  protected final String[][] _stringAlphaNumericMVValues = new String[NUM_ROWS][];
   protected final long[] _timeValues = new long[NUM_ROWS];
   protected final String[] _jsonValues = new String[NUM_ROWS];
 
@@ -107,8 +117,19 @@ public abstract class BaseTransformFunctionTest {
 
       int numValues = 1 + RANDOM.nextInt(MAX_NUM_MULTI_VALUES);
       _intMVValues[i] = new int[numValues];
+      _longMVValues[i] = new long[numValues];
+      _floatMVValues[i] = new float[numValues];
+      _doubleMVValues[i] = new double[numValues];
+      _stringMVValues[i] = new String[numValues];
+      _stringAlphaNumericMVValues[i] = new String[numValues];
+
       for (int j = 0; j < numValues; j++) {
         _intMVValues[i][j] = 1 + RANDOM.nextInt(MAX_MULTI_VALUE);
+        _longMVValues[i][j] = 1 + RANDOM.nextLong();
+        _floatMVValues[i][j] = 1 + RANDOM.nextFloat();
+        _doubleMVValues[i][j] = 1 + RANDOM.nextDouble();
+        _stringMVValues[i][j] = df.format(_intSVValues[i] * RANDOM.nextDouble());
+        _stringAlphaNumericMVValues[i][j] = RandomStringUtils.randomAlphanumeric(26);
       }
 
       // Time in the past year
@@ -126,6 +147,11 @@ public abstract class BaseTransformFunctionTest {
       map.put(STRING_ALPHANUM_SV_COLUMN, _stringAlphaNumericSVValues[i]);
       map.put(BYTES_SV_COLUMN, _bytesSVValues[i]);
       map.put(INT_MV_COLUMN, ArrayUtils.toObject(_intMVValues[i]));
+      map.put(LONG_MV_COLUMN, ArrayUtils.toObject(_longMVValues[i]));
+      map.put(FLOAT_MV_COLUMN, ArrayUtils.toObject(_floatMVValues[i]));
+      map.put(DOUBLE_MV_COLUMN, ArrayUtils.toObject(_doubleMVValues[i]));
+      map.put(STRING_MV_COLUMN, _stringMVValues[i]);
+      map.put(STRING_ALPHANUM_MV_COLUMN, _stringAlphaNumericMVValues[i]);
       map.put(TIME_COLUMN, _timeValues[i]);
       _jsonValues[i] = JsonUtils.objectToJsonNode(map).toString();
       map.put(JSON_COLUMN, _jsonValues[i]);
@@ -141,8 +167,13 @@ public abstract class BaseTransformFunctionTest {
         .addSingleValueDimension(STRING_SV_COLUMN, FieldSpec.DataType.STRING)
         .addSingleValueDimension(STRING_ALPHANUM_SV_COLUMN, FieldSpec.DataType.STRING)
         .addSingleValueDimension(BYTES_SV_COLUMN, FieldSpec.DataType.BYTES)
-        .addSingleValueDimension(JSON_COLUMN, FieldSpec.DataType.STRING)
+        .addSingleValueDimension(JSON_COLUMN, FieldSpec.DataType.STRING, Integer.MAX_VALUE, null)
         .addMultiValueDimension(INT_MV_COLUMN, FieldSpec.DataType.INT)
+        .addMultiValueDimension(LONG_MV_COLUMN, FieldSpec.DataType.LONG)
+        .addMultiValueDimension(FLOAT_MV_COLUMN, FieldSpec.DataType.FLOAT)
+        .addMultiValueDimension(DOUBLE_MV_COLUMN, FieldSpec.DataType.DOUBLE)
+        .addMultiValueDimension(STRING_MV_COLUMN, FieldSpec.DataType.STRING)
+        .addMultiValueDimension(STRING_ALPHANUM_MV_COLUMN, FieldSpec.DataType.STRING)
         .addTime(new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.MILLISECONDS, TIME_COLUMN), null).build();
     TableConfig tableConfig =
         new TableConfigBuilder(TableType.OFFLINE).setTableName("test").setTimeColumnName(TIME_COLUMN).build();
@@ -209,6 +240,20 @@ public abstract class BaseTransformFunctionTest {
     }
   }
 
+  protected void testTransformFunctionMV(TransformFunction transformFunction, int[][] expectedValues) {
+    int[][] intMVValues = transformFunction.transformToIntValuesMV(_projectionBlock);
+    for (int i = 0; i < NUM_ROWS; i++) {
+      Assert.assertEquals(intMVValues[i], expectedValues[i]);
+    }
+  }
+
+  protected void testTransformFunctionMV(TransformFunction transformFunction, String[][] expectedValues) {
+    String[][] stringMVValues = transformFunction.transformToStringValuesMV(_projectionBlock);
+    for (int i = 0; i < NUM_ROWS; i++) {
+      Assert.assertEquals(stringMVValues[i], expectedValues[i]);
+    }
+  }
+
   @AfterClass
   public void tearDown() {
     FileUtils.deleteQuietly(new File(INDEX_DIR_PATH));
diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapperTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapperTest.java
index 9402b8b..61a521b 100644
--- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapperTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapperTest.java
@@ -18,14 +18,20 @@
  */
 package org.apache.pinot.core.operator.transform.function;
 
+import java.util.Arrays;
 import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.pinot.common.function.FunctionRegistry;
 import org.apache.pinot.core.query.request.context.ExpressionContext;
 import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils;
-import org.testng.Assert;
+import org.apache.pinot.core.util.ArrayCopyUtils;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
 import org.testng.annotations.Test;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
 
 public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTest {
 
@@ -34,8 +40,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     ExpressionContext expression =
         QueryContextConverterUtils.getExpression(String.format("lower(%s)", STRING_ALPHANUM_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "lower");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "lower");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = _stringAlphaNumericSVValues[i].toLowerCase();
@@ -48,8 +54,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     ExpressionContext expression =
         QueryContextConverterUtils.getExpression(String.format("UPPER(%s)", STRING_ALPHANUM_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "upper");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "upper");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = _stringAlphaNumericSVValues[i].toUpperCase();
@@ -62,8 +68,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     ExpressionContext expression =
         QueryContextConverterUtils.getExpression(String.format("rEvErSe(%s)", STRING_ALPHANUM_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "reverse");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "reverse");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = new StringBuilder(_stringAlphaNumericSVValues[i]).reverse().toString();
@@ -76,8 +82,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     ExpressionContext expression =
         QueryContextConverterUtils.getExpression(String.format("sub_str(%s, 0, 2)", STRING_ALPHANUM_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "substr");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "substr");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = _stringAlphaNumericSVValues[i].substring(0, 2);
@@ -87,8 +93,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     expression =
         QueryContextConverterUtils.getExpression(String.format("substr(%s, '2', '-1')", STRING_ALPHANUM_SV_COLUMN));
     transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "substr");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "substr");
     expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = _stringAlphaNumericSVValues[i].substring(2);
@@ -101,8 +107,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     ExpressionContext expression = QueryContextConverterUtils
         .getExpression(String.format("concat(%s, %s, '-')", STRING_ALPHANUM_SV_COLUMN, STRING_ALPHANUM_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "concat");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "concat");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = _stringAlphaNumericSVValues[i] + "-" + _stringAlphaNumericSVValues[i];
@@ -115,8 +121,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     ExpressionContext expression =
         QueryContextConverterUtils.getExpression(String.format("replace(%s, 'A', 'B')", STRING_ALPHANUM_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "replace");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "replace");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = _stringAlphaNumericSVValues[i].replaceAll("A", "B");
@@ -131,8 +137,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     ExpressionContext expression = QueryContextConverterUtils
         .getExpression(String.format("lpad(%s, %d, '%s')", STRING_ALPHANUM_SV_COLUMN, padLength, padString));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "lpad");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "lpad");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = StringUtils.leftPad(_stringAlphaNumericSVValues[i], padLength, padString);
@@ -142,8 +148,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     expression = QueryContextConverterUtils
         .getExpression(String.format("rpad(%s, %d, '%s')", STRING_ALPHANUM_SV_COLUMN, padLength, padString));
     transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "rpad");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "rpad");
     expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = StringUtils.rightPad(_stringAlphaNumericSVValues[i], padLength, padString);
@@ -156,22 +162,22 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
     ExpressionContext expression =
         QueryContextConverterUtils.getExpression(String.format("ltrim(lpad(%s, 50, ' '))", STRING_ALPHANUM_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "ltrim");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "ltrim");
     testTransformFunction(transformFunction, _stringAlphaNumericSVValues);
 
     expression =
         QueryContextConverterUtils.getExpression(String.format("rtrim(rpad(%s, 50, ' '))", STRING_ALPHANUM_SV_COLUMN));
     transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "rtrim");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "rtrim");
     testTransformFunction(transformFunction, _stringAlphaNumericSVValues);
 
     expression = QueryContextConverterUtils
         .getExpression(String.format("trim(rpad(lpad(%s, 50, ' '), 100, ' '))", STRING_ALPHANUM_SV_COLUMN));
     transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "trim");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "trim");
     testTransformFunction(transformFunction, _stringAlphaNumericSVValues);
   }
 
@@ -179,8 +185,8 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
   public void testShaTransformFunction() {
     ExpressionContext expression = QueryContextConverterUtils.getExpression(String.format("sha(%s)", BYTES_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "sha");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "sha");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = DigestUtils.shaHex(_bytesSVValues[i]);
@@ -190,10 +196,11 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
 
   @Test
   public void testSha256TransformFunction() {
-    ExpressionContext expression = QueryContextConverterUtils.getExpression(String.format("sha256(%s)", BYTES_SV_COLUMN));
+    ExpressionContext expression =
+        QueryContextConverterUtils.getExpression(String.format("sha256(%s)", BYTES_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "sha256");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "sha256");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = DigestUtils.sha256Hex(_bytesSVValues[i]);
@@ -203,10 +210,11 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
 
   @Test
   public void testSha512TransformFunction() {
-    ExpressionContext expression = QueryContextConverterUtils.getExpression(String.format("sha512(%s)", BYTES_SV_COLUMN));
+    ExpressionContext expression =
+        QueryContextConverterUtils.getExpression(String.format("sha512(%s)", BYTES_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "sha512");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "sha512");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = DigestUtils.sha512Hex(_bytesSVValues[i]);
@@ -218,12 +226,180 @@ public class ScalarTransformFunctionWrapperTest extends BaseTransformFunctionTes
   public void testMd5TransformFunction() {
     ExpressionContext expression = QueryContextConverterUtils.getExpression(String.format("md5(%s)", BYTES_SV_COLUMN));
     TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
-    Assert.assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
-    Assert.assertEquals(transformFunction.getName(), "md5");
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "md5");
     String[] expectedValues = new String[NUM_ROWS];
     for (int i = 0; i < NUM_ROWS; i++) {
       expectedValues[i] = DigestUtils.md5Hex(_bytesSVValues[i]);
     }
     testTransformFunction(transformFunction, expectedValues);
   }
+
+  @Test
+  public void testArrayReverseIntTransformFunction() {
+    {
+      ExpressionContext expression =
+          QueryContextConverterUtils.getExpression(String.format("array_reverse_int(%s)", INT_MV_COLUMN));
+      TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+      assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+      assertEquals(transformFunction.getName(), "arrayReverseInt");
+      assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT);
+      assertFalse(transformFunction.getResultMetadata().isSingleValue());
+      int[][] expectedValues = new int[NUM_ROWS][];
+      for (int i = 0; i < NUM_ROWS; i++) {
+        expectedValues[i] = _intMVValues[i].clone();
+        ArrayUtils.reverse(expectedValues[i]);
+      }
+      testTransformFunctionMV(transformFunction, expectedValues);
+    }
+    {
+      ExpressionContext expression =
+          QueryContextConverterUtils.getExpression(String.format("array_reverse_int(%s)", LONG_MV_COLUMN));
+      TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+      assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+      assertEquals(transformFunction.getName(), "arrayReverseInt");
+      assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT);
+      assertFalse(transformFunction.getResultMetadata().isSingleValue());
+      int[][] expectedValues = new int[NUM_ROWS][];
+      for (int i = 0; i < NUM_ROWS; i++) {
+        expectedValues[i] = new int[_longMVValues[i].length];
+        ArrayCopyUtils.copy(_longMVValues[i], expectedValues[i], _longMVValues[i].length);
+        ArrayUtils.reverse(expectedValues[i]);
+      }
+      testTransformFunctionMV(transformFunction, expectedValues);
+    }
+  }
+
+  @Test
+  public void testArrayReverseStringTransformFunction() {
+    {
+      ExpressionContext expression =
+          QueryContextConverterUtils.getExpression(String.format("array_reverse_string(%s)", STRING_MV_COLUMN));
+      TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+      assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+      assertEquals(transformFunction.getName(), "arrayReverseString");
+      assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING);
+      assertFalse(transformFunction.getResultMetadata().isSingleValue());
+      String[][] expectedValues = new String[NUM_ROWS][];
+      for (int i = 0; i < NUM_ROWS; i++) {
+        expectedValues[i] = _stringMVValues[i].clone();
+        ArrayUtils.reverse(expectedValues[i]);
+      }
+      testTransformFunctionMV(transformFunction, expectedValues);
+    }
+    {
+      ExpressionContext expression =
+          QueryContextConverterUtils.getExpression(String.format("array_reverse_string(%s)", INT_MV_COLUMN));
+      TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+      assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+      assertEquals(transformFunction.getName(), "arrayReverseString");
+      assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING);
+      assertFalse(transformFunction.getResultMetadata().isSingleValue());
+      String[][] expectedValues = new String[NUM_ROWS][];
+      for (int i = 0; i < NUM_ROWS; i++) {
+        expectedValues[i] = new String[_intMVValues[i].length];
+        ArrayCopyUtils.copy(_intMVValues[i], expectedValues[i], _longMVValues[i].length);
+        ArrayUtils.reverse(expectedValues[i]);
+      }
+      testTransformFunctionMV(transformFunction, expectedValues);
+    }
+  }
+
+  @Test
+  public void testArraySortIntTransformFunction() {
+    ExpressionContext expression =
+        QueryContextConverterUtils.getExpression(String.format("array_sort_int(%s)", INT_MV_COLUMN));
+    TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "arraySortInt");
+    assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT);
+    assertFalse(transformFunction.getResultMetadata().isSingleValue());
+    int[][] expectedValues = new int[NUM_ROWS][];
+    for (int i = 0; i < NUM_ROWS; i++) {
+      expectedValues[i] = _intMVValues[i].clone();
+      Arrays.sort(expectedValues[i]);
+    }
+    testTransformFunctionMV(transformFunction, expectedValues);
+  }
+
+  @Test
+  public void testArraySortStringTransformFunction() {
+    ExpressionContext expression =
+        QueryContextConverterUtils.getExpression(String.format("array_sort_string(%s)", STRING_MV_COLUMN));
+    TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "arraySortString");
+    assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING);
+    assertFalse(transformFunction.getResultMetadata().isSingleValue());
+    String[][] expectedValues = new String[NUM_ROWS][];
+    for (int i = 0; i < NUM_ROWS; i++) {
+      expectedValues[i] = _stringMVValues[i].clone();
+      Arrays.sort(expectedValues[i]);
+    }
+    testTransformFunctionMV(transformFunction, expectedValues);
+  }
+
+  @Test
+  public void testArrayIndexOfIntTransformFunction() {
+    ExpressionContext expression =
+        QueryContextConverterUtils.getExpression(String.format("array_index_of_int(%s, 2)", INT_MV_COLUMN));
+    TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "arrayIndexOfInt");
+    assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT);
+    assertTrue(transformFunction.getResultMetadata().isSingleValue());
+    int[] expectedValues = new int[NUM_ROWS];
+    for (int i = 0; i < NUM_ROWS; i++) {
+      expectedValues[i] = ArrayUtils.indexOf(_intMVValues[i], 2);
+    }
+    testTransformFunction(transformFunction, expectedValues);
+  }
+
+  @Test
+  public void testArrayIndexOfStringTransformFunction() {
+    ExpressionContext expression =
+        QueryContextConverterUtils.getExpression(String.format("array_index_of_string(%s, 'a')", INT_MV_COLUMN));
+    TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "arrayIndexOfString");
+    assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT);
+    assertTrue(transformFunction.getResultMetadata().isSingleValue());
+    int[] expectedValues = new int[NUM_ROWS];
+    for (int i = 0; i < NUM_ROWS; i++) {
+      expectedValues[i] = ArrayUtils.indexOf(_intMVValues[i], 'a');
+    }
+    testTransformFunction(transformFunction, expectedValues);
+  }
+
+  @Test
+  public void testArrayContainsIntTransformFunction() {
+    ExpressionContext expression =
+        QueryContextConverterUtils.getExpression(String.format("array_contains_int(%s, 2)", INT_MV_COLUMN));
+    TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "arrayContainsInt");
+    assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING);
+    assertTrue(transformFunction.getResultMetadata().isSingleValue());
+    String[] expectedValues = new String[NUM_ROWS];
+    for (int i = 0; i < NUM_ROWS; i++) {
+      expectedValues[i] = Boolean.toString(ArrayUtils.contains(_intMVValues[i], 2));
+    }
+    testTransformFunction(transformFunction, expectedValues);
+  }
+
+  @Test
+  public void testArrayContainsStringTransformFunction() {
+    ExpressionContext expression =
+        QueryContextConverterUtils.getExpression(String.format("array_contains_string(%s, 'a')", INT_MV_COLUMN));
+    TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
+    assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper);
+    assertEquals(transformFunction.getName(), "arrayContainsString");
+    assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING);
+    assertTrue(transformFunction.getResultMetadata().isSingleValue());
+    String[] expectedValues = new String[NUM_ROWS];
+    for (int i = 0; i < NUM_ROWS; i++) {
+      expectedValues[i] = Boolean.toString(ArrayUtils.contains(_intMVValues[i], 'a'));
+    }
+    testTransformFunction(transformFunction, expectedValues);
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org