xiangfu0 commented on code in PR #17404:
URL: https://github.com/apache/pinot/pull/17404#discussion_r2659287919


##########
pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java:
##########
@@ -415,4 +417,589 @@ public static String arrayToString(String[] values, 
String delimiter, String nul
             .map(s -> s == null || s.equals(NullValuePlaceHolder.STRING) ? 
nullString : s)
             .toArray(String[]::new));
   }
+
+  /**
+   * Returns the first element of an array. If the array is empty, returns 
null.
+   * This is safer than using subscript operator which would fail on empty 
arrays.
+   */
+  @ScalarFunction
+  public static int arrayFirstInt(int[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.INT : arr[0];
+  }
+
+  @ScalarFunction
+  public static long arrayFirstLong(long[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.LONG : arr[0];
+  }
+
+  @ScalarFunction
+  public static float arrayFirstFloat(float[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.FLOAT : 
arr[0];
+  }
+
+  @ScalarFunction
+  public static double arrayFirstDouble(double[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.DOUBLE : 
arr[0];
+  }
+
+  @ScalarFunction
+  public static String arrayFirstString(String[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.STRING : 
arr[0];
+  }
+
+  /**
+   * Returns the last element of an array. If the array is empty, returns null.
+   * This is safer than using subscript operator which would fail on empty 
arrays.
+   */
+  @ScalarFunction
+  public static int arrayLastInt(int[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.INT : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static long arrayLastLong(long[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.LONG : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static float arrayLastFloat(float[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.FLOAT : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static double arrayLastDouble(double[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.DOUBLE : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static String arrayLastString(String[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.STRING : 
arr[arr.length - 1];
+  }
+
+  /**
+   * Returns the position of the first occurrence of the element in array 
(1-based indexing).
+   * Returns 0 if not found. This follows Trino's array_position function 
behavior.
+   */
+  @ScalarFunction
+  public static long arrayPositionInt(int[] arr, int element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == element) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionLong(long[] arr, long element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == element) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionFloat(float[] arr, float element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Float.compare(arr[i], element) == 0) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionDouble(double[] arr, double element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Double.compare(arr[i], element) == 0) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionString(String[] arr, String element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Objects.equals(arr[i], element)) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Returns the maximum value in the array.
+   */
+  @ScalarFunction
+  public static int arrayMaxInt(int[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.INT;
+    }
+    int max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] > max) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static long arrayMaxLong(long[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.LONG;
+    }
+    long max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] > max) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static float arrayMaxFloat(float[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.FLOAT;
+    }
+    float max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Float.compare(arr[i], max) > 0) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static double arrayMaxDouble(double[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.DOUBLE;
+    }
+    double max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Double.compare(arr[i], max) > 0) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static String arrayMaxString(String[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    String max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] != null && (max == null || arr[i].compareTo(max) > 0)) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  /**
+   * Returns the minimum value in the array.
+   */
+  @ScalarFunction
+  public static int arrayMinInt(int[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.INT;
+    }
+    int min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] < min) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static long arrayMinLong(long[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.LONG;
+    }
+    long min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] < min) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static float arrayMinFloat(float[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.FLOAT;
+    }
+    float min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Float.compare(arr[i], min) < 0) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static double arrayMinDouble(double[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.DOUBLE;
+    }
+    double min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Double.compare(arr[i], min) < 0) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static String arrayMinString(String[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    String min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] != null && (min == null || arr[i].compareTo(min) < 0)) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  /**
+   * Returns the cardinality (size) of the array. This is the standard SQL 
function name.
+   */
+  @ScalarFunction
+  public static long cardinalityInt(int[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  @ScalarFunction
+  public static long cardinalityLong(long[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  @ScalarFunction
+  public static long cardinalityFloat(float[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  @ScalarFunction
+  public static long cardinalityDouble(double[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  @ScalarFunction
+  public static long cardinalityString(String[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  /**
+   * Concatenates the elements of the given array using the delimiter. Null 
elements are omitted.
+   * This follows Trino's array_join function behavior.
+   */
+  @ScalarFunction
+  public static String arrayJoinInt(int[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .filter(x -> x != NullValuePlaceHolder.INT)
+        .mapToObj(String::valueOf)
+        .collect(Collectors.joining(delimiter));
+  }
+
+  @ScalarFunction
+  public static String arrayJoinLong(long[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .filter(x -> x != NullValuePlaceHolder.LONG)
+        .mapToObj(String::valueOf)
+        .collect(Collectors.joining(delimiter));
+  }
+
+  @ScalarFunction
+  public static String arrayJoinFloat(float[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    StringBuilder sb = new StringBuilder();
+    boolean first = true;
+    for (float value : arr) {
+      if (value != NullValuePlaceHolder.FLOAT) {
+        if (!first) {
+          sb.append(delimiter);
+        }
+        sb.append(value);
+        first = false;
+      }
+    }
+    return sb.toString();
+  }
+
+  @ScalarFunction
+  public static String arrayJoinDouble(double[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    StringBuilder sb = new StringBuilder();
+    boolean first = true;
+    for (double value : arr) {
+      if (value != NullValuePlaceHolder.DOUBLE) {
+        if (!first) {
+          sb.append(delimiter);
+        }
+        sb.append(value);
+        first = false;
+      }
+    }
+    return sb.toString();
+  }
+
+  @ScalarFunction
+  public static String arrayJoinString(String[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .filter(s -> s != null && !s.equals(NullValuePlaceHolder.STRING))
+        .collect(Collectors.joining(delimiter));
+  }
+
+  /**
+   * Concatenates the elements of the given array using the delimiter and null 
replacement.
+   * This is the overloaded version of array_join that handles nulls 
explicitly.
+   */
+  @ScalarFunction
+  public static String arrayJoinInt(int[] arr, String delimiter, String 
nullReplacement) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .mapToObj(x -> x == NullValuePlaceHolder.INT ? nullReplacement : 
String.valueOf(x))
+        .collect(Collectors.joining(delimiter));
+  }
+
+  @ScalarFunction
+  public static String arrayJoinLong(long[] arr, String delimiter, String 
nullReplacement) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .mapToObj(x -> x == NullValuePlaceHolder.LONG ? nullReplacement : 
String.valueOf(x))
+        .collect(Collectors.joining(delimiter));
+  }
+
+  @ScalarFunction
+  public static String arrayJoinString(String[] arr, String delimiter, String 
nullReplacement) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .map(s -> s == null || s.equals(NullValuePlaceHolder.STRING) ? 
nullReplacement : s)
+        .collect(Collectors.joining(delimiter));
+  }
+
+  /**
+   * Standard contains function - same as arrayContains but with standard SQL 
name.
+   */
+  @ScalarFunction
+  public static boolean containsInt(int[] arr, int element) {
+    return arrayContainsInt(arr, element);
+  }
+
+  @ScalarFunction
+  public static boolean containsLong(long[] arr, long element) {
+    return ArrayUtils.contains(arr, element);
+  }
+
+  @ScalarFunction
+  public static boolean containsFloat(float[] arr, float element) {
+    return ArrayUtils.contains(arr, element);
+  }
+
+  @ScalarFunction
+  public static boolean containsDouble(double[] arr, double element) {
+    return ArrayUtils.contains(arr, element);
+  }
+
+  @ScalarFunction
+  public static boolean containsString(String[] arr, String element) {
+    return arrayContainsString(arr, element);
+  }
+
+  /**
+   * Returns element of array at given index. If index > 0, provides same 
functionality as
+   * subscript operator, but returns null for out-of-bounds instead of failing.
+   * If index < 0, accesses elements from the last to the first.
+   */
+  @ScalarFunction
+  public static int elementAtInt(int[] arr, int index) {

Review Comment:
   use alias for arrayElementAtInt etc 



##########
pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java:
##########
@@ -415,4 +417,589 @@ public static String arrayToString(String[] values, 
String delimiter, String nul
             .map(s -> s == null || s.equals(NullValuePlaceHolder.STRING) ? 
nullString : s)
             .toArray(String[]::new));
   }
+
+  /**
+   * Returns the first element of an array. If the array is empty, returns 
null.
+   * This is safer than using subscript operator which would fail on empty 
arrays.
+   */
+  @ScalarFunction
+  public static int arrayFirstInt(int[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.INT : arr[0];
+  }
+
+  @ScalarFunction
+  public static long arrayFirstLong(long[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.LONG : arr[0];
+  }
+
+  @ScalarFunction
+  public static float arrayFirstFloat(float[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.FLOAT : 
arr[0];
+  }
+
+  @ScalarFunction
+  public static double arrayFirstDouble(double[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.DOUBLE : 
arr[0];
+  }
+
+  @ScalarFunction
+  public static String arrayFirstString(String[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.STRING : 
arr[0];
+  }
+
+  /**
+   * Returns the last element of an array. If the array is empty, returns null.
+   * This is safer than using subscript operator which would fail on empty 
arrays.
+   */
+  @ScalarFunction
+  public static int arrayLastInt(int[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.INT : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static long arrayLastLong(long[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.LONG : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static float arrayLastFloat(float[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.FLOAT : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static double arrayLastDouble(double[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.DOUBLE : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static String arrayLastString(String[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.STRING : 
arr[arr.length - 1];
+  }
+
+  /**
+   * Returns the position of the first occurrence of the element in array 
(1-based indexing).
+   * Returns 0 if not found. This follows Trino's array_position function 
behavior.
+   */
+  @ScalarFunction
+  public static long arrayPositionInt(int[] arr, int element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == element) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionLong(long[] arr, long element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == element) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionFloat(float[] arr, float element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Float.compare(arr[i], element) == 0) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionDouble(double[] arr, double element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Double.compare(arr[i], element) == 0) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionString(String[] arr, String element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Objects.equals(arr[i], element)) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Returns the maximum value in the array.
+   */
+  @ScalarFunction
+  public static int arrayMaxInt(int[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.INT;
+    }
+    int max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] > max) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static long arrayMaxLong(long[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.LONG;
+    }
+    long max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] > max) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static float arrayMaxFloat(float[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.FLOAT;
+    }
+    float max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Float.compare(arr[i], max) > 0) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static double arrayMaxDouble(double[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.DOUBLE;
+    }
+    double max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Double.compare(arr[i], max) > 0) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static String arrayMaxString(String[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    String max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] != null && (max == null || arr[i].compareTo(max) > 0)) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  /**
+   * Returns the minimum value in the array.
+   */
+  @ScalarFunction
+  public static int arrayMinInt(int[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.INT;
+    }
+    int min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] < min) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static long arrayMinLong(long[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.LONG;
+    }
+    long min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] < min) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static float arrayMinFloat(float[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.FLOAT;
+    }
+    float min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Float.compare(arr[i], min) < 0) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static double arrayMinDouble(double[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.DOUBLE;
+    }
+    double min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Double.compare(arr[i], min) < 0) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static String arrayMinString(String[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    String min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] != null && (min == null || arr[i].compareTo(min) < 0)) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  /**
+   * Returns the cardinality (size) of the array. This is the standard SQL 
function name.
+   */
+  @ScalarFunction
+  public static long cardinalityInt(int[] arr) {

Review Comment:
   cardinality should be unique value count?



##########
pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java:
##########
@@ -415,4 +417,589 @@ public static String arrayToString(String[] values, 
String delimiter, String nul
             .map(s -> s == null || s.equals(NullValuePlaceHolder.STRING) ? 
nullString : s)
             .toArray(String[]::new));
   }
+
+  /**
+   * Returns the first element of an array. If the array is empty, returns 
null.
+   * This is safer than using subscript operator which would fail on empty 
arrays.
+   */
+  @ScalarFunction
+  public static int arrayFirstInt(int[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.INT : arr[0];
+  }
+
+  @ScalarFunction
+  public static long arrayFirstLong(long[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.LONG : arr[0];
+  }
+
+  @ScalarFunction
+  public static float arrayFirstFloat(float[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.FLOAT : 
arr[0];
+  }
+
+  @ScalarFunction
+  public static double arrayFirstDouble(double[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.DOUBLE : 
arr[0];
+  }
+
+  @ScalarFunction
+  public static String arrayFirstString(String[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.STRING : 
arr[0];
+  }
+
+  /**
+   * Returns the last element of an array. If the array is empty, returns null.
+   * This is safer than using subscript operator which would fail on empty 
arrays.
+   */
+  @ScalarFunction
+  public static int arrayLastInt(int[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.INT : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static long arrayLastLong(long[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.LONG : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static float arrayLastFloat(float[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.FLOAT : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static double arrayLastDouble(double[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.DOUBLE : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static String arrayLastString(String[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.STRING : 
arr[arr.length - 1];
+  }
+
+  /**
+   * Returns the position of the first occurrence of the element in array 
(1-based indexing).
+   * Returns 0 if not found. This follows Trino's array_position function 
behavior.
+   */
+  @ScalarFunction
+  public static long arrayPositionInt(int[] arr, int element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == element) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionLong(long[] arr, long element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == element) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionFloat(float[] arr, float element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Float.compare(arr[i], element) == 0) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionDouble(double[] arr, double element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Double.compare(arr[i], element) == 0) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionString(String[] arr, String element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Objects.equals(arr[i], element)) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Returns the maximum value in the array.
+   */
+  @ScalarFunction
+  public static int arrayMaxInt(int[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.INT;
+    }
+    int max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] > max) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static long arrayMaxLong(long[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.LONG;
+    }
+    long max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] > max) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static float arrayMaxFloat(float[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.FLOAT;
+    }
+    float max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Float.compare(arr[i], max) > 0) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static double arrayMaxDouble(double[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.DOUBLE;
+    }
+    double max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Double.compare(arr[i], max) > 0) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static String arrayMaxString(String[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    String max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] != null && (max == null || arr[i].compareTo(max) > 0)) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  /**
+   * Returns the minimum value in the array.
+   */
+  @ScalarFunction
+  public static int arrayMinInt(int[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.INT;
+    }
+    int min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] < min) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static long arrayMinLong(long[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.LONG;
+    }
+    long min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] < min) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static float arrayMinFloat(float[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.FLOAT;
+    }
+    float min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Float.compare(arr[i], min) < 0) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static double arrayMinDouble(double[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.DOUBLE;
+    }
+    double min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Double.compare(arr[i], min) < 0) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static String arrayMinString(String[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    String min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] != null && (min == null || arr[i].compareTo(min) < 0)) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  /**
+   * Returns the cardinality (size) of the array. This is the standard SQL 
function name.
+   */
+  @ScalarFunction
+  public static long cardinalityInt(int[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  @ScalarFunction
+  public static long cardinalityLong(long[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  @ScalarFunction
+  public static long cardinalityFloat(float[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  @ScalarFunction
+  public static long cardinalityDouble(double[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  @ScalarFunction
+  public static long cardinalityString(String[] arr) {
+    return arr == null ? 0 : arr.length;
+  }
+
+  /**
+   * Concatenates the elements of the given array using the delimiter. Null 
elements are omitted.
+   * This follows Trino's array_join function behavior.
+   */
+  @ScalarFunction
+  public static String arrayJoinInt(int[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .filter(x -> x != NullValuePlaceHolder.INT)
+        .mapToObj(String::valueOf)
+        .collect(Collectors.joining(delimiter));
+  }
+
+  @ScalarFunction
+  public static String arrayJoinLong(long[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .filter(x -> x != NullValuePlaceHolder.LONG)
+        .mapToObj(String::valueOf)
+        .collect(Collectors.joining(delimiter));
+  }
+
+  @ScalarFunction
+  public static String arrayJoinFloat(float[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    StringBuilder sb = new StringBuilder();
+    boolean first = true;
+    for (float value : arr) {
+      if (value != NullValuePlaceHolder.FLOAT) {
+        if (!first) {
+          sb.append(delimiter);
+        }
+        sb.append(value);
+        first = false;
+      }
+    }
+    return sb.toString();
+  }
+
+  @ScalarFunction
+  public static String arrayJoinDouble(double[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    StringBuilder sb = new StringBuilder();
+    boolean first = true;
+    for (double value : arr) {
+      if (value != NullValuePlaceHolder.DOUBLE) {
+        if (!first) {
+          sb.append(delimiter);
+        }
+        sb.append(value);
+        first = false;
+      }
+    }
+    return sb.toString();
+  }
+
+  @ScalarFunction
+  public static String arrayJoinString(String[] arr, String delimiter) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .filter(s -> s != null && !s.equals(NullValuePlaceHolder.STRING))
+        .collect(Collectors.joining(delimiter));
+  }
+
+  /**
+   * Concatenates the elements of the given array using the delimiter and null 
replacement.
+   * This is the overloaded version of array_join that handles nulls 
explicitly.
+   */
+  @ScalarFunction
+  public static String arrayJoinInt(int[] arr, String delimiter, String 
nullReplacement) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .mapToObj(x -> x == NullValuePlaceHolder.INT ? nullReplacement : 
String.valueOf(x))
+        .collect(Collectors.joining(delimiter));
+  }
+
+  @ScalarFunction
+  public static String arrayJoinLong(long[] arr, String delimiter, String 
nullReplacement) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .mapToObj(x -> x == NullValuePlaceHolder.LONG ? nullReplacement : 
String.valueOf(x))
+        .collect(Collectors.joining(delimiter));
+  }
+
+  @ScalarFunction
+  public static String arrayJoinString(String[] arr, String delimiter, String 
nullReplacement) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    return Arrays.stream(arr)
+        .map(s -> s == null || s.equals(NullValuePlaceHolder.STRING) ? 
nullReplacement : s)
+        .collect(Collectors.joining(delimiter));
+  }
+
+  /**
+   * Standard contains function - same as arrayContains but with standard SQL 
name.
+   */
+  @ScalarFunction
+  public static boolean containsInt(int[] arr, int element) {
+    return arrayContainsInt(arr, element);
+  }
+
+  @ScalarFunction
+  public static boolean containsLong(long[] arr, long element) {
+    return ArrayUtils.contains(arr, element);
+  }
+
+  @ScalarFunction
+  public static boolean containsFloat(float[] arr, float element) {
+    return ArrayUtils.contains(arr, element);
+  }
+
+  @ScalarFunction
+  public static boolean containsDouble(double[] arr, double element) {
+    return ArrayUtils.contains(arr, element);
+  }
+
+  @ScalarFunction
+  public static boolean containsString(String[] arr, String element) {
+    return arrayContainsString(arr, element);
+  }
+
+  /**
+   * Returns element of array at given index. If index > 0, provides same 
functionality as
+   * subscript operator, but returns null for out-of-bounds instead of failing.
+   * If index < 0, accesses elements from the last to the first.
+   */
+  @ScalarFunction
+  public static int elementAtInt(int[] arr, int index) {

Review Comment:
   use alias for arrayElementAtInt etc 



##########
pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java:
##########
@@ -415,4 +417,589 @@ public static String arrayToString(String[] values, 
String delimiter, String nul
             .map(s -> s == null || s.equals(NullValuePlaceHolder.STRING) ? 
nullString : s)
             .toArray(String[]::new));
   }
+
+  /**
+   * Returns the first element of an array. If the array is empty, returns 
null.
+   * This is safer than using subscript operator which would fail on empty 
arrays.
+   */
+  @ScalarFunction
+  public static int arrayFirstInt(int[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.INT : arr[0];
+  }
+
+  @ScalarFunction
+  public static long arrayFirstLong(long[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.LONG : arr[0];
+  }
+
+  @ScalarFunction
+  public static float arrayFirstFloat(float[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.FLOAT : 
arr[0];
+  }
+
+  @ScalarFunction
+  public static double arrayFirstDouble(double[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.DOUBLE : 
arr[0];
+  }
+
+  @ScalarFunction
+  public static String arrayFirstString(String[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.STRING : 
arr[0];
+  }
+
+  /**
+   * Returns the last element of an array. If the array is empty, returns null.
+   * This is safer than using subscript operator which would fail on empty 
arrays.
+   */
+  @ScalarFunction
+  public static int arrayLastInt(int[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.INT : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static long arrayLastLong(long[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.LONG : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static float arrayLastFloat(float[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.FLOAT : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static double arrayLastDouble(double[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.DOUBLE : 
arr[arr.length - 1];
+  }
+
+  @ScalarFunction
+  public static String arrayLastString(String[] arr) {
+    return arr == null || arr.length == 0 ? NullValuePlaceHolder.STRING : 
arr[arr.length - 1];
+  }
+
+  /**
+   * Returns the position of the first occurrence of the element in array 
(1-based indexing).
+   * Returns 0 if not found. This follows Trino's array_position function 
behavior.
+   */
+  @ScalarFunction
+  public static long arrayPositionInt(int[] arr, int element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == element) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionLong(long[] arr, long element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == element) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionFloat(float[] arr, float element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Float.compare(arr[i], element) == 0) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionDouble(double[] arr, double element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Double.compare(arr[i], element) == 0) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  @ScalarFunction
+  public static long arrayPositionString(String[] arr, String element) {
+    if (arr == null) {
+      return 0;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (Objects.equals(arr[i], element)) {
+        return i + 1; // 1-based indexing
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Returns the maximum value in the array.
+   */
+  @ScalarFunction
+  public static int arrayMaxInt(int[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.INT;
+    }
+    int max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] > max) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static long arrayMaxLong(long[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.LONG;
+    }
+    long max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] > max) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static float arrayMaxFloat(float[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.FLOAT;
+    }
+    float max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Float.compare(arr[i], max) > 0) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static double arrayMaxDouble(double[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.DOUBLE;
+    }
+    double max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Double.compare(arr[i], max) > 0) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  @ScalarFunction
+  public static String arrayMaxString(String[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    String max = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] != null && (max == null || arr[i].compareTo(max) > 0)) {
+        max = arr[i];
+      }
+    }
+    return max;
+  }
+
+  /**
+   * Returns the minimum value in the array.
+   */
+  @ScalarFunction
+  public static int arrayMinInt(int[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.INT;
+    }
+    int min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] < min) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static long arrayMinLong(long[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.LONG;
+    }
+    long min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] < min) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static float arrayMinFloat(float[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.FLOAT;
+    }
+    float min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Float.compare(arr[i], min) < 0) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static double arrayMinDouble(double[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.DOUBLE;
+    }
+    double min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (Double.compare(arr[i], min) < 0) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  @ScalarFunction
+  public static String arrayMinString(String[] arr) {
+    if (arr == null || arr.length == 0) {
+      return NullValuePlaceHolder.STRING;
+    }
+    String min = arr[0];
+    for (int i = 1; i < arr.length; i++) {
+      if (arr[i] != null && (min == null || arr[i].compareTo(min) < 0)) {
+        min = arr[i];
+      }
+    }
+    return min;
+  }
+
+  /**
+   * Returns the cardinality (size) of the array. This is the standard SQL 
function name.
+   */
+  @ScalarFunction
+  public static long cardinalityInt(int[] arr) {

Review Comment:
   cardinality should be unique value count?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to