Copilot commented on code in PR #17404:
URL: https://github.com/apache/pinot/pull/17404#discussion_r2659281657
##########
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)
Review Comment:
Filtering with != for NullValuePlaceHolder.INT will not correctly handle
null values. The placeholder is a specific sentinel value, but this comparison
treats it as a direct equality check. If the array contains actual null
elements (not just the placeholder), they won't be filtered correctly.
##########
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) {
Review Comment:
Using != comparison for floating-point values with
NullValuePlaceHolder.FLOAT is problematic. Floating-point equality checks
should use Float.compare() to handle NaN and precision issues correctly,
similar to how arrayPositionFloat uses Float.compare.
```suggestion
if (Float.compare(value, NullValuePlaceHolder.FLOAT) != 0) {
```
##########
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)
Review Comment:
Filtering with != for NullValuePlaceHolder.LONG will not correctly handle
null values. The placeholder is a specific sentinel value, but this comparison
treats it as a direct equality check. If the array contains actual null
elements (not just the placeholder), they won't be filtered correctly.
##########
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);
Review Comment:
The containsInt function delegates to arrayContainsInt, but the other
contains functions for long, float, and double use ArrayUtils.contains
directly. For consistency, either all contains functions should delegate to
their arrayContains equivalents, or all should use ArrayUtils.contains directly.
##########
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) {
Review Comment:
Using != comparison for double values with NullValuePlaceHolder.DOUBLE is
problematic. Floating-point equality checks should use Double.compare() to
handle NaN and precision issues correctly, similar to how arrayPositionDouble
uses Double.compare.
```suggestion
if (Double.compare(value, NullValuePlaceHolder.DOUBLE) != 0) {
```
--
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]