You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by xi...@apache.org on 2026/04/13 00:38:17 UTC
(pinot) branch master updated: Add polymorphic arithmetic scalar functions (#18171)
This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 598b628da7f Add polymorphic arithmetic scalar functions (#18171)
598b628da7f is described below
commit 598b628da7fd5ccdb20b1929fe4225aee52a8bf2
Author: Xiang Fu <xi...@apache.org>
AuthorDate: Sun Apr 12 17:38:11 2026 -0700
Add polymorphic arithmetic scalar functions (#18171)
---
.../function/scalar/ArithmeticFunctions.java | 49 ----
.../scalar/arithmetic/AbsScalarFunction.java | 94 +++++++
.../scalar/arithmetic/ArithmeticFunctionUtils.java | 113 ++++++++
...ava => BaseBinaryArithmeticScalarFunction.java} | 31 ++-
...java => BaseUnaryArithmeticScalarFunction.java} | 31 +--
.../scalar/arithmetic/GreatestScalarFunction.java | 97 +++++++
.../scalar/arithmetic/LeastScalarFunction.java | 97 +++++++
.../scalar/arithmetic/MinusScalarFunction.java | 2 +-
...sScalarFunction.java => ModScalarFunction.java} | 53 +++-
.../arithmetic/ModuloOrZeroScalarFunction.java | 97 +++++++
.../scalar/arithmetic/MultScalarFunction.java | 2 +-
.../scalar/arithmetic/NegateScalarFunction.java | 92 +++++++
.../scalar/arithmetic/PlusScalarFunction.java | 2 +-
.../arithmetic/PositiveModuloScalarFunction.java | 114 ++++++++
.../arithmetic/ArithmeticScalarFunctionTest.java | 42 +++
.../pinot/sql/parsers/CalciteSqlCompilerTest.java | 15 ++
.../pinot/core/function/FunctionRegistryTest.java | 80 ++++++
.../custom/ArithmeticFunctionsIntegrationTest.java | 292 +++++++++++++++++++++
.../resources/udf-test-results/all-functions.yaml | 18 +-
.../apache/pinot/query/QueryCompilationTest.java | 8 +
.../pinot/query/runtime/function/AbsUdf.java | 17 +-
21 files changed, 1241 insertions(+), 105 deletions(-)
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArithmeticFunctions.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArithmeticFunctions.java
index 70ff0dca5f6..2939aaa9acb 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArithmeticFunctions.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArithmeticFunctions.java
@@ -74,55 +74,6 @@ public class ArithmeticFunctions {
return Double.isNaN(value) ? 1 : 0;
}
- @ScalarFunction
- public static double mod(double a, double b) {
- return a % b;
- }
-
- @ScalarFunction
- public static double moduloOrZero(double a, double b) {
- //Same as mod but returns zero when dividing by zero or when dividing a minimal negative number by minus one.
- return (b == 0 || (a == Long.MIN_VALUE && b == -1)) ? 0 : mod(a, b);
- }
-
- @ScalarFunction
- public static double positiveModulo(double a, double b) {
- double result = a % b;
- return result >= 0 ? result : result + Math.abs(b);
- }
-
- @ScalarFunction
- public static double negate(double a) {
- return -a;
- }
-
- @ScalarFunction
- public static double least(double a, double b) {
- return Double.min(a, b);
- }
-
- @ScalarFunction
- public static double greatest(double a, double b) {
- return Double.max(a, b);
- }
-
- @Deprecated
- @ScalarFunction
- public static double min(double a, double b) {
- return least(a, b);
- }
-
- @Deprecated
- @ScalarFunction
- public static double max(double a, double b) {
- return greatest(a, b);
- }
-
- @ScalarFunction
- public static double abs(double a) {
- return Math.abs(a);
- }
-
@ScalarFunction(names = {"ceil", "ceiling"})
public static double ceil(double a) {
return Math.ceil(a);
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/AbsScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/AbsScalarFunction.java
new file mode 100644
index 00000000000..196d31819f3
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/AbsScalarFunction.java
@@ -0,0 +1,94 @@
+/**
+ * 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.arithmetic;
+
+import java.math.BigDecimal;
+import java.util.EnumMap;
+import java.util.Map;
+import org.apache.pinot.common.function.FunctionInfo;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.spi.annotations.ScalarFunction;
+
+
+/**
+ * Polymorphic absolute-value scalar function implementation.
+ *
+ * <p>Instances are immutable and thread-safe.
+ */
+@ScalarFunction
+public class AbsScalarFunction extends BaseUnaryArithmeticScalarFunction {
+
+ private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
+
+ static {
+ try {
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.INT,
+ new FunctionInfo(AbsScalarFunction.class.getMethod("intAbs", int.class), AbsScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.LONG,
+ new FunctionInfo(AbsScalarFunction.class.getMethod("longAbs", long.class), AbsScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.FLOAT,
+ new FunctionInfo(AbsScalarFunction.class.getMethod("floatAbs", float.class), AbsScalarFunction.class,
+ false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.DOUBLE,
+ new FunctionInfo(AbsScalarFunction.class.getMethod("doubleAbs", double.class), AbsScalarFunction.class,
+ false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.BIG_DECIMAL,
+ new FunctionInfo(AbsScalarFunction.class.getMethod("bigDecimalAbs", BigDecimal.class),
+ AbsScalarFunction.class, false));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected FunctionInfo functionInfoForType(ColumnDataType argumentType) {
+ return TYPE_FUNCTION_INFO_MAP.get(argumentType);
+ }
+
+ @Override
+ public String getName() {
+ return "abs";
+ }
+
+ public static int intAbs(int value) {
+ if (value == Integer.MIN_VALUE) {
+ throw new ArithmeticException("integer overflow");
+ }
+ return Math.abs(value);
+ }
+
+ public static long longAbs(long value) {
+ if (value == Long.MIN_VALUE) {
+ throw new ArithmeticException("long overflow");
+ }
+ return Math.abs(value);
+ }
+
+ public static float floatAbs(float value) {
+ return Math.abs(value);
+ }
+
+ public static double doubleAbs(double value) {
+ return Math.abs(value);
+ }
+
+ public static BigDecimal bigDecimalAbs(BigDecimal value) {
+ return value.abs();
+ }
+}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ArithmeticFunctionUtils.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ArithmeticFunctionUtils.java
new file mode 100644
index 00000000000..10c35f7fb7b
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ArithmeticFunctionUtils.java
@@ -0,0 +1,113 @@
+/**
+ * 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.arithmetic;
+
+import java.util.List;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlOperatorBinding;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.SqlTypeFamily;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.pinot.common.function.sql.PinotSqlFunction;
+
+
+/**
+ * Shared Calcite registration utilities for arithmetic scalar functions.
+ *
+ * <p>This helper is stateless and thread-safe.
+ */
+final class ArithmeticFunctionUtils {
+ private ArithmeticFunctionUtils() {
+ }
+
+ static PinotSqlFunction unaryArithmeticSqlFunction(String name) {
+ return new PinotSqlFunction(name, ArithmeticFunctionUtils::inferUnaryReturnType,
+ OperandTypes.family(List.of(SqlTypeFamily.ANY)));
+ }
+
+ static PinotSqlFunction binaryArithmeticSqlFunction(String name) {
+ return new PinotSqlFunction(name, ArithmeticFunctionUtils::inferBinaryReturnType,
+ OperandTypes.family(List.of(SqlTypeFamily.ANY, SqlTypeFamily.ANY)));
+ }
+
+ private static RelDataType inferUnaryReturnType(SqlOperatorBinding opBinding) {
+ SqlTypeName returnType =
+ SqlTypeUtil.isNumeric(opBinding.getOperandType(0)) ? normalizeNumericType(opBinding.getOperandType(0))
+ : SqlTypeName.DOUBLE;
+ return createTypeWithOperandNullability(opBinding, returnType);
+ }
+
+ private static RelDataType inferBinaryReturnType(SqlOperatorBinding opBinding) {
+ RelDataType leftType = opBinding.getOperandType(0);
+ RelDataType rightType = opBinding.getOperandType(1);
+ SqlTypeName returnType;
+ if (SqlTypeUtil.isNumeric(leftType) && SqlTypeUtil.isNumeric(rightType)) {
+ SqlTypeName leftSqlType = normalizeNumericType(leftType);
+ SqlTypeName rightSqlType = normalizeNumericType(rightType);
+ if (leftSqlType == rightSqlType) {
+ returnType = leftSqlType;
+ } else if (isIntegral(leftSqlType) && isIntegral(rightSqlType)) {
+ returnType = SqlTypeName.BIGINT;
+ } else if (leftSqlType == SqlTypeName.DECIMAL || rightSqlType == SqlTypeName.DECIMAL) {
+ returnType = SqlTypeName.DECIMAL;
+ } else if (leftSqlType == SqlTypeName.DOUBLE || rightSqlType == SqlTypeName.DOUBLE) {
+ returnType = SqlTypeName.DOUBLE;
+ } else {
+ returnType = SqlTypeName.REAL;
+ }
+ } else {
+ // Preserve legacy coercions (for example, STRING inputs) by validating these functions as DOUBLE-returning.
+ returnType = SqlTypeName.DOUBLE;
+ }
+ return createTypeWithOperandNullability(opBinding, returnType);
+ }
+
+ private static SqlTypeName normalizeNumericType(RelDataType relDataType) {
+ switch (relDataType.getSqlTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ return SqlTypeName.INTEGER;
+ case BIGINT:
+ return SqlTypeName.BIGINT;
+ case REAL:
+ case FLOAT:
+ return SqlTypeName.REAL;
+ case DOUBLE:
+ return SqlTypeName.DOUBLE;
+ case DECIMAL:
+ return SqlTypeName.DECIMAL;
+ default:
+ return SqlTypeName.DOUBLE;
+ }
+ }
+
+ private static boolean isIntegral(SqlTypeName sqlTypeName) {
+ return sqlTypeName == SqlTypeName.INTEGER || sqlTypeName == SqlTypeName.BIGINT;
+ }
+
+ private static RelDataType createTypeWithOperandNullability(SqlOperatorBinding opBinding, SqlTypeName sqlTypeName) {
+ RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
+ RelDataType type = typeFactory.createSqlType(sqlTypeName);
+ boolean nullable = opBinding.collectOperandTypes().stream().anyMatch(RelDataType::isNullable);
+ return typeFactory.createTypeWithNullability(type, nullable);
+ }
+}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PolymorphicBinaryArithmeticScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/BaseBinaryArithmeticScalarFunction.java
similarity index 65%
copy from pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PolymorphicBinaryArithmeticScalarFunction.java
copy to pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/BaseBinaryArithmeticScalarFunction.java
index 10167161f92..3b1bb8447e8 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PolymorphicBinaryArithmeticScalarFunction.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/BaseBinaryArithmeticScalarFunction.java
@@ -25,9 +25,11 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
/**
- * Base class for polymorphic binary arithmetic scalar functions
+ * Base class for binary arithmetic scalar functions.
+ *
+ * <p>Instances are immutable and thread-safe.
*/
-public abstract class PolymorphicBinaryArithmeticScalarFunction implements PinotScalarFunction {
+public abstract class BaseBinaryArithmeticScalarFunction implements PinotScalarFunction {
@Nullable
@Override
@@ -51,17 +53,32 @@ public abstract class PolymorphicBinaryArithmeticScalarFunction implements Pinot
}
private FunctionInfo functionInfoForTypes(ColumnDataType argumentType1, ColumnDataType argumentType2) {
- if ((argumentType1 == ColumnDataType.LONG || argumentType1 == ColumnDataType.INT) && (
- argumentType2 == ColumnDataType.LONG || argumentType2 == ColumnDataType.INT)) {
- return functionInfoForType(ColumnDataType.LONG);
+ if (argumentType1.isNumber() && argumentType2.isNumber()) {
+ if (argumentType1 == argumentType2) {
+ return functionInfoForType(argumentType1);
+ }
+ if (argumentType1.isWholeNumber() && argumentType2.isWholeNumber()) {
+ return functionInfoForType(ColumnDataType.LONG);
+ }
+ if (argumentType1 == ColumnDataType.BIG_DECIMAL || argumentType2 == ColumnDataType.BIG_DECIMAL) {
+ return functionInfoForType(ColumnDataType.BIG_DECIMAL);
+ }
+ if (argumentType1.ordinal() > argumentType2.ordinal()) {
+ return functionInfoForType(argumentType1);
+ }
+ return functionInfoForType(argumentType2);
}
- // Fall back to double based comparison by default
- return functionInfoForType(ColumnDataType.DOUBLE);
+ // Fall back to double based arithmetic by default so legacy coercions (e.g. STRING -> DOUBLE) still work.
+ return defaultFunctionInfo();
}
/**
* Get the binary arithmetic scalar function's {@link FunctionInfo} for the given argument type.
*/
protected abstract FunctionInfo functionInfoForType(ColumnDataType argumentType);
+
+ protected FunctionInfo defaultFunctionInfo() {
+ return functionInfoForType(ColumnDataType.DOUBLE);
+ }
}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PolymorphicBinaryArithmeticScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/BaseUnaryArithmeticScalarFunction.java
similarity index 63%
rename from pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PolymorphicBinaryArithmeticScalarFunction.java
rename to pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/BaseUnaryArithmeticScalarFunction.java
index 10167161f92..c0e67335a31 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PolymorphicBinaryArithmeticScalarFunction.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/BaseUnaryArithmeticScalarFunction.java
@@ -25,43 +25,40 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
/**
- * Base class for polymorphic binary arithmetic scalar functions
+ * Base class for unary arithmetic scalar functions.
+ *
+ * <p>Instances are immutable and thread-safe.
*/
-public abstract class PolymorphicBinaryArithmeticScalarFunction implements PinotScalarFunction {
+public abstract class BaseUnaryArithmeticScalarFunction implements PinotScalarFunction {
@Nullable
@Override
public FunctionInfo getFunctionInfo(ColumnDataType[] argumentTypes) {
- if (argumentTypes.length != 2) {
+ if (argumentTypes.length != 1) {
return null;
}
- return functionInfoForTypes(argumentTypes[0].getStoredType(), argumentTypes[1].getStoredType());
+ FunctionInfo functionInfo = functionInfoForType(argumentTypes[0].getStoredType());
+ return functionInfo != null ? functionInfo : defaultFunctionInfo();
}
@Nullable
@Override
public FunctionInfo getFunctionInfo(int numArguments) {
- if (numArguments != 2) {
+ if (numArguments != 1) {
return null;
}
// For backward compatibility
- return functionInfoForType(ColumnDataType.DOUBLE);
- }
-
- private FunctionInfo functionInfoForTypes(ColumnDataType argumentType1, ColumnDataType argumentType2) {
- if ((argumentType1 == ColumnDataType.LONG || argumentType1 == ColumnDataType.INT) && (
- argumentType2 == ColumnDataType.LONG || argumentType2 == ColumnDataType.INT)) {
- return functionInfoForType(ColumnDataType.LONG);
- }
-
- // Fall back to double based comparison by default
- return functionInfoForType(ColumnDataType.DOUBLE);
+ return defaultFunctionInfo();
}
/**
- * Get the binary arithmetic scalar function's {@link FunctionInfo} for the given argument type.
+ * Get the unary arithmetic scalar function's {@link FunctionInfo} for the given argument type.
*/
protected abstract FunctionInfo functionInfoForType(ColumnDataType argumentType);
+
+ protected FunctionInfo defaultFunctionInfo() {
+ return functionInfoForType(ColumnDataType.DOUBLE);
+ }
}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/GreatestScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/GreatestScalarFunction.java
new file mode 100644
index 00000000000..58d76b25b3a
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/GreatestScalarFunction.java
@@ -0,0 +1,97 @@
+/**
+ * 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.arithmetic;
+
+import java.math.BigDecimal;
+import java.util.EnumMap;
+import java.util.Map;
+import org.apache.pinot.common.function.FunctionInfo;
+import org.apache.pinot.common.function.sql.PinotSqlFunction;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.spi.annotations.ScalarFunction;
+
+
+/**
+ * Polymorphic greatest scalar function implementation.
+ *
+ * <p>Instances are immutable and thread-safe.
+ */
+@ScalarFunction
+public class GreatestScalarFunction extends BaseBinaryArithmeticScalarFunction {
+
+ private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
+
+ static {
+ try {
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.INT,
+ new FunctionInfo(GreatestScalarFunction.class.getMethod("intGreatest", int.class, int.class),
+ GreatestScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.LONG,
+ new FunctionInfo(GreatestScalarFunction.class.getMethod("longGreatest", long.class, long.class),
+ GreatestScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.FLOAT,
+ new FunctionInfo(GreatestScalarFunction.class.getMethod("floatGreatest", float.class, float.class),
+ GreatestScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.DOUBLE,
+ new FunctionInfo(GreatestScalarFunction.class.getMethod("doubleGreatest", double.class, double.class),
+ GreatestScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.BIG_DECIMAL, new FunctionInfo(
+ GreatestScalarFunction.class.getMethod("bigDecimalGreatest", BigDecimal.class, BigDecimal.class),
+ GreatestScalarFunction.class, false));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected FunctionInfo functionInfoForType(ColumnDataType argumentType) {
+ FunctionInfo functionInfo = TYPE_FUNCTION_INFO_MAP.get(argumentType);
+ return functionInfo != null ? functionInfo : defaultFunctionInfo();
+ }
+
+ @Override
+ public String getName() {
+ return "greatest";
+ }
+
+ @Override
+ public PinotSqlFunction toPinotSqlFunction() {
+ return ArithmeticFunctionUtils.binaryArithmeticSqlFunction(getName());
+ }
+
+ public static int intGreatest(int a, int b) {
+ return Math.max(a, b);
+ }
+
+ public static long longGreatest(long a, long b) {
+ return Math.max(a, b);
+ }
+
+ public static float floatGreatest(float a, float b) {
+ return Math.max(a, b);
+ }
+
+ public static double doubleGreatest(double a, double b) {
+ return Double.max(a, b);
+ }
+
+ public static BigDecimal bigDecimalGreatest(BigDecimal a, BigDecimal b) {
+ return a.compareTo(b) >= 0 ? a : b;
+ }
+}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/LeastScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/LeastScalarFunction.java
new file mode 100644
index 00000000000..db0705f88be
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/LeastScalarFunction.java
@@ -0,0 +1,97 @@
+/**
+ * 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.arithmetic;
+
+import java.math.BigDecimal;
+import java.util.EnumMap;
+import java.util.Map;
+import org.apache.pinot.common.function.FunctionInfo;
+import org.apache.pinot.common.function.sql.PinotSqlFunction;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.spi.annotations.ScalarFunction;
+
+
+/**
+ * Polymorphic least scalar function implementation.
+ *
+ * <p>Instances are immutable and thread-safe.
+ */
+@ScalarFunction
+public class LeastScalarFunction extends BaseBinaryArithmeticScalarFunction {
+
+ private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
+
+ static {
+ try {
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.INT,
+ new FunctionInfo(LeastScalarFunction.class.getMethod("intLeast", int.class, int.class),
+ LeastScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.LONG,
+ new FunctionInfo(LeastScalarFunction.class.getMethod("longLeast", long.class, long.class),
+ LeastScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.FLOAT,
+ new FunctionInfo(LeastScalarFunction.class.getMethod("floatLeast", float.class, float.class),
+ LeastScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.DOUBLE,
+ new FunctionInfo(LeastScalarFunction.class.getMethod("doubleLeast", double.class, double.class),
+ LeastScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.BIG_DECIMAL,
+ new FunctionInfo(LeastScalarFunction.class.getMethod("bigDecimalLeast", BigDecimal.class, BigDecimal.class),
+ LeastScalarFunction.class, false));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected FunctionInfo functionInfoForType(ColumnDataType argumentType) {
+ FunctionInfo functionInfo = TYPE_FUNCTION_INFO_MAP.get(argumentType);
+ return functionInfo != null ? functionInfo : defaultFunctionInfo();
+ }
+
+ @Override
+ public String getName() {
+ return "least";
+ }
+
+ @Override
+ public PinotSqlFunction toPinotSqlFunction() {
+ return ArithmeticFunctionUtils.binaryArithmeticSqlFunction(getName());
+ }
+
+ public static int intLeast(int a, int b) {
+ return Math.min(a, b);
+ }
+
+ public static long longLeast(long a, long b) {
+ return Math.min(a, b);
+ }
+
+ public static float floatLeast(float a, float b) {
+ return Math.min(a, b);
+ }
+
+ public static double doubleLeast(double a, double b) {
+ return Double.min(a, b);
+ }
+
+ public static BigDecimal bigDecimalLeast(BigDecimal a, BigDecimal b) {
+ return a.compareTo(b) <= 0 ? a : b;
+ }
+}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MinusScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MinusScalarFunction.java
index 61488e58e7c..6772a665d5b 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MinusScalarFunction.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MinusScalarFunction.java
@@ -26,7 +26,7 @@ import org.apache.pinot.spi.annotations.ScalarFunction;
@ScalarFunction(names = {"sub", "minus"})
-public class MinusScalarFunction extends PolymorphicBinaryArithmeticScalarFunction {
+public class MinusScalarFunction extends BaseBinaryArithmeticScalarFunction {
private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MinusScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ModScalarFunction.java
similarity index 50%
copy from pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MinusScalarFunction.java
copy to pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ModScalarFunction.java
index 61488e58e7c..54a9634a395 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MinusScalarFunction.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ModScalarFunction.java
@@ -18,6 +18,7 @@
*/
package org.apache.pinot.common.function.scalar.arithmetic;
+import java.math.BigDecimal;
import java.util.EnumMap;
import java.util.Map;
import org.apache.pinot.common.function.FunctionInfo;
@@ -25,19 +26,33 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
import org.apache.pinot.spi.annotations.ScalarFunction;
-@ScalarFunction(names = {"sub", "minus"})
-public class MinusScalarFunction extends PolymorphicBinaryArithmeticScalarFunction {
+/**
+ * Polymorphic modulo scalar function implementation.
+ *
+ * <p>Instances are immutable and thread-safe.
+ */
+@ScalarFunction
+public class ModScalarFunction extends BaseBinaryArithmeticScalarFunction {
private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
static {
try {
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.INT,
+ new FunctionInfo(ModScalarFunction.class.getMethod("intMod", int.class, int.class), ModScalarFunction.class,
+ false));
TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.LONG,
- new FunctionInfo(MinusScalarFunction.class.getMethod("longMinus", long.class, long.class),
- MinusScalarFunction.class, false));
+ new FunctionInfo(ModScalarFunction.class.getMethod("longMod", long.class, long.class),
+ ModScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.FLOAT,
+ new FunctionInfo(ModScalarFunction.class.getMethod("floatMod", float.class, float.class),
+ ModScalarFunction.class, false));
TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.DOUBLE,
- new FunctionInfo(MinusScalarFunction.class.getMethod("doubleMinus", double.class, double.class),
- MinusScalarFunction.class, false));
+ new FunctionInfo(ModScalarFunction.class.getMethod("doubleMod", double.class, double.class),
+ ModScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.BIG_DECIMAL,
+ new FunctionInfo(ModScalarFunction.class.getMethod("bigDecimalMod", BigDecimal.class, BigDecimal.class),
+ ModScalarFunction.class, false));
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
@@ -46,21 +61,31 @@ public class MinusScalarFunction extends PolymorphicBinaryArithmeticScalarFuncti
@Override
protected FunctionInfo functionInfoForType(ColumnDataType argumentType) {
FunctionInfo functionInfo = TYPE_FUNCTION_INFO_MAP.get(argumentType);
-
- // Fall back to double based comparison by default
- return functionInfo != null ? functionInfo : TYPE_FUNCTION_INFO_MAP.get(ColumnDataType.DOUBLE);
+ return functionInfo != null ? functionInfo : defaultFunctionInfo();
}
@Override
public String getName() {
- return "minus";
+ return "mod";
+ }
+
+ public static int intMod(int a, int b) {
+ return a % b;
+ }
+
+ public static long longMod(long a, long b) {
+ return a % b;
+ }
+
+ public static float floatMod(float a, float b) {
+ return a % b;
}
- public static long longMinus(long a, long b) {
- return a - b;
+ public static double doubleMod(double a, double b) {
+ return a % b;
}
- public static double doubleMinus(double a, double b) {
- return a - b;
+ public static BigDecimal bigDecimalMod(BigDecimal a, BigDecimal b) {
+ return a.remainder(b);
}
}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ModuloOrZeroScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ModuloOrZeroScalarFunction.java
new file mode 100644
index 00000000000..13a818b1714
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/ModuloOrZeroScalarFunction.java
@@ -0,0 +1,97 @@
+/**
+ * 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.arithmetic;
+
+import java.math.BigDecimal;
+import java.util.EnumMap;
+import java.util.Map;
+import org.apache.pinot.common.function.FunctionInfo;
+import org.apache.pinot.common.function.sql.PinotSqlFunction;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.spi.annotations.ScalarFunction;
+
+
+/**
+ * Polymorphic modulo-or-zero scalar function implementation.
+ *
+ * <p>Instances are immutable and thread-safe.
+ */
+@ScalarFunction
+public class ModuloOrZeroScalarFunction extends BaseBinaryArithmeticScalarFunction {
+
+ private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
+
+ static {
+ try {
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.INT, new FunctionInfo(
+ ModuloOrZeroScalarFunction.class.getMethod("intModuloOrZero", int.class, int.class),
+ ModuloOrZeroScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.LONG, new FunctionInfo(
+ ModuloOrZeroScalarFunction.class.getMethod("longModuloOrZero", long.class, long.class),
+ ModuloOrZeroScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.FLOAT, new FunctionInfo(
+ ModuloOrZeroScalarFunction.class.getMethod("floatModuloOrZero", float.class, float.class),
+ ModuloOrZeroScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.DOUBLE, new FunctionInfo(
+ ModuloOrZeroScalarFunction.class.getMethod("doubleModuloOrZero", double.class, double.class),
+ ModuloOrZeroScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.BIG_DECIMAL, new FunctionInfo(
+ ModuloOrZeroScalarFunction.class.getMethod("bigDecimalModuloOrZero", BigDecimal.class, BigDecimal.class),
+ ModuloOrZeroScalarFunction.class, false));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected FunctionInfo functionInfoForType(ColumnDataType argumentType) {
+ FunctionInfo functionInfo = TYPE_FUNCTION_INFO_MAP.get(argumentType);
+ return functionInfo != null ? functionInfo : defaultFunctionInfo();
+ }
+
+ @Override
+ public String getName() {
+ return "moduloOrZero";
+ }
+
+ @Override
+ public PinotSqlFunction toPinotSqlFunction() {
+ return ArithmeticFunctionUtils.binaryArithmeticSqlFunction(getName());
+ }
+
+ public static int intModuloOrZero(int a, int b) {
+ return b == 0 ? 0 : a % b;
+ }
+
+ public static long longModuloOrZero(long a, long b) {
+ return b == 0 ? 0L : a % b;
+ }
+
+ public static float floatModuloOrZero(float a, float b) {
+ return b == 0 ? 0F : a % b;
+ }
+
+ public static double doubleModuloOrZero(double a, double b) {
+ return b == 0 ? 0D : a % b;
+ }
+
+ public static BigDecimal bigDecimalModuloOrZero(BigDecimal a, BigDecimal b) {
+ return b.signum() == 0 ? BigDecimal.ZERO : a.remainder(b);
+ }
+}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MultScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MultScalarFunction.java
index a7370453939..bf0dacfe809 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MultScalarFunction.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/MultScalarFunction.java
@@ -26,7 +26,7 @@ import org.apache.pinot.spi.annotations.ScalarFunction;
@ScalarFunction(names = {"mult", "times"})
-public class MultScalarFunction extends PolymorphicBinaryArithmeticScalarFunction {
+public class MultScalarFunction extends BaseBinaryArithmeticScalarFunction {
private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/NegateScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/NegateScalarFunction.java
new file mode 100644
index 00000000000..8dca5edbb99
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/NegateScalarFunction.java
@@ -0,0 +1,92 @@
+/**
+ * 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.arithmetic;
+
+import java.math.BigDecimal;
+import java.util.EnumMap;
+import java.util.Map;
+import org.apache.pinot.common.function.FunctionInfo;
+import org.apache.pinot.common.function.sql.PinotSqlFunction;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.spi.annotations.ScalarFunction;
+
+
+/**
+ * Polymorphic negation scalar function implementation.
+ *
+ * <p>Instances are immutable and thread-safe.
+ */
+@ScalarFunction
+public class NegateScalarFunction extends BaseUnaryArithmeticScalarFunction {
+
+ private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
+
+ static {
+ try {
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.INT, new FunctionInfo(
+ NegateScalarFunction.class.getMethod("intNegate", int.class), NegateScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.LONG, new FunctionInfo(
+ NegateScalarFunction.class.getMethod("longNegate", long.class), NegateScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.FLOAT, new FunctionInfo(
+ NegateScalarFunction.class.getMethod("floatNegate", float.class), NegateScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.DOUBLE, new FunctionInfo(
+ NegateScalarFunction.class.getMethod("doubleNegate", double.class), NegateScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.BIG_DECIMAL, new FunctionInfo(
+ NegateScalarFunction.class.getMethod("bigDecimalNegate", BigDecimal.class), NegateScalarFunction.class,
+ false));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected FunctionInfo functionInfoForType(ColumnDataType argumentType) {
+ return TYPE_FUNCTION_INFO_MAP.get(argumentType);
+ }
+
+ @Override
+ public String getName() {
+ return "negate";
+ }
+
+ @Override
+ public PinotSqlFunction toPinotSqlFunction() {
+ return ArithmeticFunctionUtils.unaryArithmeticSqlFunction(getName());
+ }
+
+ public static int intNegate(int value) {
+ return Math.negateExact(value);
+ }
+
+ public static long longNegate(long value) {
+ return Math.negateExact(value);
+ }
+
+ public static float floatNegate(float value) {
+ return -value;
+ }
+
+ public static double doubleNegate(double value) {
+ return -value;
+ }
+
+ public static BigDecimal bigDecimalNegate(BigDecimal value) {
+ return value.negate();
+ }
+}
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PlusScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PlusScalarFunction.java
index 5951afa5271..ab16bf590c3 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PlusScalarFunction.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PlusScalarFunction.java
@@ -26,7 +26,7 @@ import org.apache.pinot.spi.annotations.ScalarFunction;
@ScalarFunction(names = {"add", "plus"})
-public class PlusScalarFunction extends PolymorphicBinaryArithmeticScalarFunction {
+public class PlusScalarFunction extends BaseBinaryArithmeticScalarFunction {
private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
diff --git a/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PositiveModuloScalarFunction.java b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PositiveModuloScalarFunction.java
new file mode 100644
index 00000000000..d8363470e35
--- /dev/null
+++ b/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/arithmetic/PositiveModuloScalarFunction.java
@@ -0,0 +1,114 @@
+/**
+ * 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.arithmetic;
+
+import java.math.BigDecimal;
+import java.util.EnumMap;
+import java.util.Map;
+import org.apache.pinot.common.function.FunctionInfo;
+import org.apache.pinot.common.function.sql.PinotSqlFunction;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.spi.annotations.ScalarFunction;
+
+
+/**
+ * Polymorphic positive-modulo scalar function implementation.
+ *
+ * <p>Instances are immutable and thread-safe.
+ */
+@ScalarFunction
+public class PositiveModuloScalarFunction extends BaseBinaryArithmeticScalarFunction {
+
+ private static final Map<ColumnDataType, FunctionInfo> TYPE_FUNCTION_INFO_MAP = new EnumMap<>(ColumnDataType.class);
+
+ static {
+ try {
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.INT, new FunctionInfo(
+ PositiveModuloScalarFunction.class.getMethod("intPositiveModulo", int.class, int.class),
+ PositiveModuloScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.LONG, new FunctionInfo(
+ PositiveModuloScalarFunction.class.getMethod("longPositiveModulo", long.class, long.class),
+ PositiveModuloScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.FLOAT, new FunctionInfo(
+ PositiveModuloScalarFunction.class.getMethod("floatPositiveModulo", float.class, float.class),
+ PositiveModuloScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.DOUBLE, new FunctionInfo(
+ PositiveModuloScalarFunction.class.getMethod("doublePositiveModulo", double.class, double.class),
+ PositiveModuloScalarFunction.class, false));
+ TYPE_FUNCTION_INFO_MAP.put(ColumnDataType.BIG_DECIMAL, new FunctionInfo(
+ PositiveModuloScalarFunction.class.getMethod("bigDecimalPositiveModulo", BigDecimal.class, BigDecimal.class),
+ PositiveModuloScalarFunction.class, false));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected FunctionInfo functionInfoForType(ColumnDataType argumentType) {
+ FunctionInfo functionInfo = TYPE_FUNCTION_INFO_MAP.get(argumentType);
+ return functionInfo != null ? functionInfo : defaultFunctionInfo();
+ }
+
+ @Override
+ public String getName() {
+ return "positiveModulo";
+ }
+
+ @Override
+ public PinotSqlFunction toPinotSqlFunction() {
+ return ArithmeticFunctionUtils.binaryArithmeticSqlFunction(getName());
+ }
+
+ public static int intPositiveModulo(int a, int b) {
+ int result = a % b;
+ if (result >= 0) {
+ return result;
+ }
+ if (b == Integer.MIN_VALUE) {
+ return Integer.MAX_VALUE + result + 1;
+ }
+ return result + Math.abs(b);
+ }
+
+ public static long longPositiveModulo(long a, long b) {
+ long result = a % b;
+ if (result >= 0) {
+ return result;
+ }
+ if (b == Long.MIN_VALUE) {
+ return Long.MAX_VALUE + result + 1;
+ }
+ return result + Math.abs(b);
+ }
+
+ public static float floatPositiveModulo(float a, float b) {
+ float result = a % b;
+ return result >= 0 ? result : result + Math.abs(b);
+ }
+
+ public static double doublePositiveModulo(double a, double b) {
+ double result = a % b;
+ return result >= 0 ? result : result + Math.abs(b);
+ }
+
+ public static BigDecimal bigDecimalPositiveModulo(BigDecimal a, BigDecimal b) {
+ BigDecimal result = a.remainder(b);
+ return result.signum() >= 0 ? result : result.add(b.abs());
+ }
+}
diff --git a/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/arithmetic/ArithmeticScalarFunctionTest.java b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/arithmetic/ArithmeticScalarFunctionTest.java
new file mode 100644
index 00000000000..6f9fedef585
--- /dev/null
+++ b/pinot-common/src/test/java/org/apache/pinot/common/function/scalar/arithmetic/ArithmeticScalarFunctionTest.java
@@ -0,0 +1,42 @@
+/**
+ * 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.arithmetic;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertThrows;
+
+
+/**
+ * Tests for arithmetic scalar function edge cases.
+ */
+public class ArithmeticScalarFunctionTest {
+
+ @Test
+ public void testAbsIntegralOverflowFailsFast() {
+ assertThrows(ArithmeticException.class, () -> AbsScalarFunction.intAbs(Integer.MIN_VALUE));
+ assertThrows(ArithmeticException.class, () -> AbsScalarFunction.longAbs(Long.MIN_VALUE));
+ }
+
+ @Test
+ public void testNegateIntegralOverflowFailsFast() {
+ assertThrows(ArithmeticException.class, () -> NegateScalarFunction.intNegate(Integer.MIN_VALUE));
+ assertThrows(ArithmeticException.class, () -> NegateScalarFunction.longNegate(Long.MIN_VALUE));
+ }
+}
diff --git a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
index f3a2bf5d6bb..46bb7b09408 100644
--- a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
+++ b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
@@ -1086,6 +1086,21 @@ public class CalciteSqlCompilerTest {
pinotQuery.getFilterExpression().getFunctionCall().getOperands().get(1).getLiteral().getIntValue(), 1394323200);
}
+ @Test
+ public void testPolymorphicArithmeticScalarFunctionsCompile() {
+ PinotQuery pinotQuery = compileToPinotQuery("SELECT least(col1, col2), greatest(col3, col4), negate(col5), "
+ + "positiveModulo(col6, col7), moduloOrZero(col8, col9) FROM myTable WHERE least(col1, col2) = 1 "
+ + "AND greatest(col3, col4) = 2 AND negate(col5) = -1 AND positiveModulo(col6, col7) = 0 "
+ + "AND moduloOrZero(col8, col9) = 0");
+
+ List<Expression> selectList = pinotQuery.getSelectList();
+ Assert.assertEquals(selectList.get(0).getFunctionCall().getOperator(), "least");
+ Assert.assertEquals(selectList.get(1).getFunctionCall().getOperator(), "greatest");
+ Assert.assertEquals(selectList.get(2).getFunctionCall().getOperator(), "negate");
+ Assert.assertEquals(selectList.get(3).getFunctionCall().getOperator(), "positivemodulo");
+ Assert.assertEquals(selectList.get(4).getFunctionCall().getOperator(), "moduloorzero");
+ }
+
@Test
public void testSelectionTransformFunction() {
PinotQuery pinotQuery =
diff --git a/pinot-core/src/test/java/org/apache/pinot/core/function/FunctionRegistryTest.java b/pinot-core/src/test/java/org/apache/pinot/core/function/FunctionRegistryTest.java
index 5071d9dca05..52be2353a5a 100644
--- a/pinot-core/src/test/java/org/apache/pinot/core/function/FunctionRegistryTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/core/function/FunctionRegistryTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.pinot.core.function;
+import java.math.BigDecimal;
import java.util.EnumSet;
import org.apache.pinot.common.function.FunctionInfo;
import org.apache.pinot.common.function.FunctionRegistry;
@@ -144,4 +145,83 @@ public class FunctionRegistryTest {
assertNotNull(bitextractByArity);
assertEquals(bitextractByArity.getMethod().getName(), "longBitExtract");
}
+
+ @Test
+ public void testArithmeticFunctionPolymorphism() {
+ FunctionInfo intAbs = FunctionRegistry.lookupFunctionInfo("abs", new ColumnDataType[]{ColumnDataType.INT});
+ assertNotNull(intAbs);
+ assertEquals(intAbs.getMethod().getName(), "intAbs");
+ assertEquals(intAbs.getMethod().getReturnType(), int.class);
+
+ FunctionInfo longAbs = FunctionRegistry.lookupFunctionInfo("abs", new ColumnDataType[]{ColumnDataType.LONG});
+ assertNotNull(longAbs);
+ assertEquals(longAbs.getMethod().getName(), "longAbs");
+ assertEquals(longAbs.getMethod().getReturnType(), long.class);
+
+ FunctionInfo floatAbs = FunctionRegistry.lookupFunctionInfo("abs", new ColumnDataType[]{ColumnDataType.FLOAT});
+ assertNotNull(floatAbs);
+ assertEquals(floatAbs.getMethod().getName(), "floatAbs");
+ assertEquals(floatAbs.getMethod().getReturnType(), float.class);
+
+ FunctionInfo bigDecimalAbs =
+ FunctionRegistry.lookupFunctionInfo("abs", new ColumnDataType[]{ColumnDataType.BIG_DECIMAL});
+ assertNotNull(bigDecimalAbs);
+ assertEquals(bigDecimalAbs.getMethod().getName(), "bigDecimalAbs");
+ assertEquals(bigDecimalAbs.getMethod().getReturnType(), BigDecimal.class);
+
+ FunctionInfo stringAbs = FunctionRegistry.lookupFunctionInfo("abs", new ColumnDataType[]{ColumnDataType.STRING});
+ assertNotNull(stringAbs);
+ assertEquals(stringAbs.getMethod().getName(), "doubleAbs");
+ assertEquals(stringAbs.getMethod().getReturnType(), double.class);
+
+ FunctionInfo absByArity = FunctionRegistry.lookupFunctionInfo("abs", 1);
+ assertNotNull(absByArity);
+ assertEquals(absByArity.getMethod().getName(), "doubleAbs");
+
+ FunctionInfo intMod =
+ FunctionRegistry.lookupFunctionInfo("mod", new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.INT});
+ assertNotNull(intMod);
+ assertEquals(intMod.getMethod().getName(), "intMod");
+ assertEquals(intMod.getMethod().getReturnType(), int.class);
+
+ FunctionInfo longMod =
+ FunctionRegistry.lookupFunctionInfo("mod", new ColumnDataType[]{ColumnDataType.LONG, ColumnDataType.INT});
+ assertNotNull(longMod);
+ assertEquals(longMod.getMethod().getName(), "longMod");
+ assertEquals(longMod.getMethod().getReturnType(), long.class);
+
+ FunctionInfo floatMod =
+ FunctionRegistry.lookupFunctionInfo("mod", new ColumnDataType[]{ColumnDataType.FLOAT, ColumnDataType.INT});
+ assertNotNull(floatMod);
+ assertEquals(floatMod.getMethod().getName(), "floatMod");
+ assertEquals(floatMod.getMethod().getReturnType(), float.class);
+
+ FunctionInfo bigDecimalMod = FunctionRegistry.lookupFunctionInfo("mod",
+ new ColumnDataType[]{ColumnDataType.BIG_DECIMAL, ColumnDataType.INT});
+ assertNotNull(bigDecimalMod);
+ assertEquals(bigDecimalMod.getMethod().getName(), "bigDecimalMod");
+ assertEquals(bigDecimalMod.getMethod().getReturnType(), BigDecimal.class);
+
+ FunctionInfo stringMod =
+ FunctionRegistry.lookupFunctionInfo("mod", new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT});
+ assertNotNull(stringMod);
+ assertEquals(stringMod.getMethod().getName(), "doubleMod");
+ assertEquals(stringMod.getMethod().getReturnType(), double.class);
+
+ FunctionInfo modByArity = FunctionRegistry.lookupFunctionInfo("mod", 2);
+ assertNotNull(modByArity);
+ assertEquals(modByArity.getMethod().getName(), "doubleMod");
+
+ FunctionInfo negateFloat =
+ FunctionRegistry.lookupFunctionInfo("negate", new ColumnDataType[]{ColumnDataType.FLOAT});
+ assertNotNull(negateFloat);
+ assertEquals(negateFloat.getMethod().getName(), "floatNegate");
+ assertEquals(negateFloat.getMethod().getReturnType(), float.class);
+
+ FunctionInfo leastBigDecimal = FunctionRegistry.lookupFunctionInfo("least",
+ new ColumnDataType[]{ColumnDataType.BIG_DECIMAL, ColumnDataType.DOUBLE});
+ assertNotNull(leastBigDecimal);
+ assertEquals(leastBigDecimal.getMethod().getName(), "bigDecimalLeast");
+ assertEquals(leastBigDecimal.getMethod().getReturnType(), BigDecimal.class);
+ }
}
diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/ArithmeticFunctionsIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/ArithmeticFunctionsIntegrationTest.java
new file mode 100644
index 00000000000..08808abbaad
--- /dev/null
+++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/ArithmeticFunctionsIntegrationTest.java
@@ -0,0 +1,292 @@
+/**
+ * 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.integration.tests.custom;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.List;
+import org.apache.avro.file.DataFileWriter;
+import org.apache.avro.generic.GenericData;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.Schema;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+
+/**
+ * End-to-end integration tests for polymorphic arithmetic scalar functions over a custom Pinot cluster.
+ *
+ * <p>This test validates broker/server query execution and result typing for arithmetic scalar functions that now
+ * support multiple numeric types, including BIG_DECIMAL. Test methods mutate query-engine mode and are not
+ * thread-safe.
+ */
+@Test(suiteName = "CustomClusterIntegrationTest")
+public class ArithmeticFunctionsIntegrationTest extends CustomDataQueryClusterIntegrationTest {
+ private static final String DEFAULT_TABLE_NAME = "ArithmeticFunctionsIntegrationTest";
+ private static final String ID_COLUMN = "id";
+ private static final String INT_VALUE_COLUMN = "intValue";
+ private static final String INT_DIVISOR_COLUMN = "intDivisor";
+ private static final String NEGATIVE_INT_DIVISOR_COLUMN = "negativeIntDivisor";
+ private static final String ZERO_INT_DIVISOR_COLUMN = "zeroIntDivisor";
+ private static final String LONG_VALUE_COLUMN = "longValue";
+ private static final String FLOAT_VALUE_COLUMN = "floatValue";
+ private static final String FLOAT_DIVISOR_COLUMN = "floatDivisor";
+ private static final String DOUBLE_VALUE_COLUMN = "doubleValue";
+ private static final String DOUBLE_DIVISOR_COLUMN = "doubleDivisor";
+ private static final String BIG_DECIMAL_VALUE_COLUMN = "bigDecimalValue";
+ private static final String BIG_DECIMAL_DIVISOR_COLUMN = "bigDecimalDivisor";
+ private static final String NEGATIVE_BIG_DECIMAL_DIVISOR_COLUMN = "negativeBigDecimalDivisor";
+ private static final String ZERO_BIG_DECIMAL_DIVISOR_COLUMN = "zeroBigDecimalDivisor";
+
+ private static final List<RowData> ROWS = List.of(
+ new RowData(0, -9, 5, -5, 0, -9L, -9.5F, 5F, -9.5D, -5D, new BigDecimal("-9.0"), new BigDecimal("5.0"),
+ new BigDecimal("-5.0"), BigDecimal.ZERO),
+ new RowData(1, 9, 5, -5, 0, 9L, 9.5F, 5F, 9.5D, 5D, new BigDecimal("9.0"), new BigDecimal("5.0"),
+ new BigDecimal("-5.0"), BigDecimal.ZERO)
+ );
+
+ @Override
+ public String getTableName() {
+ return DEFAULT_TABLE_NAME;
+ }
+
+ @Override
+ protected long getCountStarResult() {
+ return ROWS.size();
+ }
+
+ @Override
+ public Schema createSchema() {
+ return new Schema.SchemaBuilder().setSchemaName(getTableName())
+ .addSingleValueDimension(ID_COLUMN, FieldSpec.DataType.INT)
+ .addMetric(INT_VALUE_COLUMN, FieldSpec.DataType.INT)
+ .addMetric(INT_DIVISOR_COLUMN, FieldSpec.DataType.INT)
+ .addMetric(NEGATIVE_INT_DIVISOR_COLUMN, FieldSpec.DataType.INT)
+ .addMetric(ZERO_INT_DIVISOR_COLUMN, FieldSpec.DataType.INT)
+ .addMetric(LONG_VALUE_COLUMN, FieldSpec.DataType.LONG)
+ .addMetric(FLOAT_VALUE_COLUMN, FieldSpec.DataType.FLOAT)
+ .addMetric(FLOAT_DIVISOR_COLUMN, FieldSpec.DataType.FLOAT)
+ .addMetric(DOUBLE_VALUE_COLUMN, FieldSpec.DataType.DOUBLE)
+ .addMetric(DOUBLE_DIVISOR_COLUMN, FieldSpec.DataType.DOUBLE)
+ .addMetric(BIG_DECIMAL_VALUE_COLUMN, FieldSpec.DataType.BIG_DECIMAL)
+ .addMetric(BIG_DECIMAL_DIVISOR_COLUMN, FieldSpec.DataType.BIG_DECIMAL)
+ .addMetric(NEGATIVE_BIG_DECIMAL_DIVISOR_COLUMN, FieldSpec.DataType.BIG_DECIMAL)
+ .addMetric(ZERO_BIG_DECIMAL_DIVISOR_COLUMN, FieldSpec.DataType.BIG_DECIMAL)
+ .build();
+ }
+
+ @Override
+ public List<File> createAvroFiles()
+ throws Exception {
+ org.apache.avro.Schema avroSchema = org.apache.avro.Schema.createRecord("arithmeticRecord", null, null, false);
+ avroSchema.setFields(List.of(
+ new org.apache.avro.Schema.Field(ID_COLUMN, org.apache.avro.Schema.create(org.apache.avro.Schema.Type.INT),
+ null, null),
+ new org.apache.avro.Schema.Field(INT_VALUE_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.INT), null, null),
+ new org.apache.avro.Schema.Field(INT_DIVISOR_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.INT), null, null),
+ new org.apache.avro.Schema.Field(NEGATIVE_INT_DIVISOR_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.INT), null, null),
+ new org.apache.avro.Schema.Field(ZERO_INT_DIVISOR_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.INT), null, null),
+ new org.apache.avro.Schema.Field(LONG_VALUE_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.LONG), null, null),
+ // Use DOUBLE in Avro for FLOAT Pinot columns to avoid Avro float precision loss.
+ new org.apache.avro.Schema.Field(FLOAT_VALUE_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.DOUBLE), null, null),
+ new org.apache.avro.Schema.Field(FLOAT_DIVISOR_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.DOUBLE), null, null),
+ new org.apache.avro.Schema.Field(DOUBLE_VALUE_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.DOUBLE), null, null),
+ new org.apache.avro.Schema.Field(DOUBLE_DIVISOR_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.DOUBLE), null, null),
+ new org.apache.avro.Schema.Field(BIG_DECIMAL_VALUE_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.STRING), null, null),
+ new org.apache.avro.Schema.Field(BIG_DECIMAL_DIVISOR_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.STRING), null, null),
+ new org.apache.avro.Schema.Field(NEGATIVE_BIG_DECIMAL_DIVISOR_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.STRING), null, null),
+ new org.apache.avro.Schema.Field(ZERO_BIG_DECIMAL_DIVISOR_COLUMN,
+ org.apache.avro.Schema.create(org.apache.avro.Schema.Type.STRING), null, null)
+ ));
+
+ try (AvroFilesAndWriters avroFilesAndWriters = createAvroFilesAndWriters(avroSchema)) {
+ List<DataFileWriter<GenericData.Record>> writers = avroFilesAndWriters.getWriters();
+ for (int i = 0; i < ROWS.size(); i++) {
+ RowData rowData = ROWS.get(i);
+ GenericData.Record record = new GenericData.Record(avroSchema);
+ record.put(ID_COLUMN, rowData._id);
+ record.put(INT_VALUE_COLUMN, rowData._intValue);
+ record.put(INT_DIVISOR_COLUMN, rowData._intDivisor);
+ record.put(NEGATIVE_INT_DIVISOR_COLUMN, rowData._negativeIntDivisor);
+ record.put(ZERO_INT_DIVISOR_COLUMN, rowData._zeroIntDivisor);
+ record.put(LONG_VALUE_COLUMN, rowData._longValue);
+ record.put(FLOAT_VALUE_COLUMN, (double) rowData._floatValue);
+ record.put(FLOAT_DIVISOR_COLUMN, (double) rowData._floatDivisor);
+ record.put(DOUBLE_VALUE_COLUMN, rowData._doubleValue);
+ record.put(DOUBLE_DIVISOR_COLUMN, rowData._doubleDivisor);
+ record.put(BIG_DECIMAL_VALUE_COLUMN, rowData._bigDecimalValue.toPlainString());
+ record.put(BIG_DECIMAL_DIVISOR_COLUMN, rowData._bigDecimalDivisor.toPlainString());
+ record.put(NEGATIVE_BIG_DECIMAL_DIVISOR_COLUMN, rowData._negativeBigDecimalDivisor.toPlainString());
+ record.put(ZERO_BIG_DECIMAL_DIVISOR_COLUMN, rowData._zeroBigDecimalDivisor.toPlainString());
+ writers.get(i % getNumAvroFiles()).append(record);
+ }
+ return avroFilesAndWriters.getAvroFiles();
+ }
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testUnaryArithmeticFunctions(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ String query = String.format("SELECT COUNT(*) FROM %s WHERE %s = 0 "
+ + "AND abs(%s) = 9 "
+ + "AND abs(%s) = 9 "
+ + "AND abs(%s) = 9.5 "
+ + "AND abs(%s) = 9.5 "
+ + "AND abs(%s) = 9.0 "
+ + "AND negate(%s) = 9 "
+ + "AND negate(%s) = 9 "
+ + "AND negate(%s) = 9.5 "
+ + "AND negate(%s) = 9.5 "
+ + "AND negate(%s) = 9.0",
+ getTableName(), ID_COLUMN,
+ INT_VALUE_COLUMN,
+ LONG_VALUE_COLUMN,
+ FLOAT_VALUE_COLUMN,
+ DOUBLE_VALUE_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN,
+ INT_VALUE_COLUMN,
+ LONG_VALUE_COLUMN,
+ FLOAT_VALUE_COLUMN,
+ DOUBLE_VALUE_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN);
+ JsonNode response = postQuery(query);
+ assertCount(response, 1L);
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testModuloAndPositiveModuloFunctions(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ String query = String.format("SELECT COUNT(*) FROM %s WHERE %s = 0 "
+ + "AND mod(%s, %s) = -4 "
+ + "AND mod(%s, %s) = -4 "
+ + "AND mod(%s, %s) = -4.5 "
+ + "AND mod(%s, %s) = -4.5 "
+ + "AND mod(%s, %s) = -4.0 "
+ + "AND positiveModulo(%s, %s) = 1 "
+ + "AND positiveModulo(%s, %s) = 1 "
+ + "AND positiveModulo(%s, %s) = 0.5 "
+ + "AND positiveModulo(%s, %s) = 1.0",
+ getTableName(), ID_COLUMN,
+ INT_VALUE_COLUMN, INT_DIVISOR_COLUMN,
+ LONG_VALUE_COLUMN, INT_DIVISOR_COLUMN,
+ FLOAT_VALUE_COLUMN, FLOAT_DIVISOR_COLUMN,
+ DOUBLE_VALUE_COLUMN, DOUBLE_DIVISOR_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN, BIG_DECIMAL_DIVISOR_COLUMN,
+ INT_VALUE_COLUMN, INT_DIVISOR_COLUMN,
+ INT_VALUE_COLUMN, NEGATIVE_INT_DIVISOR_COLUMN,
+ FLOAT_VALUE_COLUMN, FLOAT_DIVISOR_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN, NEGATIVE_BIG_DECIMAL_DIVISOR_COLUMN);
+ JsonNode response = postQuery(query);
+ assertCount(response, 1L);
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testModuloOrZeroFunctions(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ String query = String.format("SELECT COUNT(*) FROM %s WHERE %s = 0 "
+ + "AND moduloOrZero(%s, %s) = 0 "
+ + "AND moduloOrZero(%s, %s) = 0",
+ getTableName(), ID_COLUMN,
+ INT_VALUE_COLUMN, ZERO_INT_DIVISOR_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN, ZERO_BIG_DECIMAL_DIVISOR_COLUMN);
+ JsonNode response = postQuery(query);
+ assertCount(response, 1L);
+ }
+
+ @Test(dataProvider = "useBothQueryEngines")
+ public void testLeastGreatestFunctions(boolean useMultiStageQueryEngine)
+ throws Exception {
+ setUseMultiStageQueryEngine(useMultiStageQueryEngine);
+ String query = String.format("SELECT COUNT(*) FROM %s WHERE %s = 0 "
+ + "AND least(%s, %s) = %s "
+ + "AND greatest(%s, %s) = %s "
+ + "AND least(%s, %s) = %s "
+ + "AND greatest(%s, %s) = %s "
+ + "AND least(%s, %s) = %s "
+ + "AND greatest(%s, %s) = %s",
+ getTableName(), ID_COLUMN,
+ LONG_VALUE_COLUMN, INT_DIVISOR_COLUMN, LONG_VALUE_COLUMN,
+ FLOAT_VALUE_COLUMN, INT_DIVISOR_COLUMN, INT_DIVISOR_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, DOUBLE_VALUE_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BIG_DECIMAL_VALUE_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN, BIG_DECIMAL_DIVISOR_COLUMN, BIG_DECIMAL_VALUE_COLUMN,
+ BIG_DECIMAL_VALUE_COLUMN, BIG_DECIMAL_DIVISOR_COLUMN, BIG_DECIMAL_DIVISOR_COLUMN);
+ JsonNode response = postQuery(query);
+ assertCount(response, 1L);
+ }
+
+ private void assertCount(JsonNode response, long expectedCount) {
+ assertEquals(response.path("exceptions").size(), 0, response.toPrettyString());
+ assertEquals(getType(response, 0), "LONG");
+ assertEquals(getLongCellValue(response, 0, 0), expectedCount);
+ }
+
+ private static final class RowData {
+ private final int _id;
+ private final int _intValue;
+ private final int _intDivisor;
+ private final int _negativeIntDivisor;
+ private final int _zeroIntDivisor;
+ private final long _longValue;
+ private final float _floatValue;
+ private final float _floatDivisor;
+ private final double _doubleValue;
+ private final double _doubleDivisor;
+ private final BigDecimal _bigDecimalValue;
+ private final BigDecimal _bigDecimalDivisor;
+ private final BigDecimal _negativeBigDecimalDivisor;
+ private final BigDecimal _zeroBigDecimalDivisor;
+
+ private RowData(int id, int intValue, int intDivisor, int negativeIntDivisor, int zeroIntDivisor, long longValue,
+ float floatValue, float floatDivisor, double doubleValue, double doubleDivisor, BigDecimal bigDecimalValue,
+ BigDecimal bigDecimalDivisor, BigDecimal negativeBigDecimalDivisor, BigDecimal zeroBigDecimalDivisor) {
+ _id = id;
+ _intValue = intValue;
+ _intDivisor = intDivisor;
+ _negativeIntDivisor = negativeIntDivisor;
+ _zeroIntDivisor = zeroIntDivisor;
+ _longValue = longValue;
+ _floatValue = floatValue;
+ _floatDivisor = floatDivisor;
+ _doubleValue = doubleValue;
+ _doubleDivisor = doubleDivisor;
+ _bigDecimalValue = bigDecimalValue;
+ _bigDecimalDivisor = bigDecimalDivisor;
+ _negativeBigDecimalDivisor = negativeBigDecimalDivisor;
+ _zeroBigDecimalDivisor = zeroBigDecimalDivisor;
+ }
+ }
+}
diff --git a/pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml b/pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml
index 0d8b950ef74..27d9e7226d1 100644
--- a/pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml
+++ b/pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml
@@ -27,7 +27,7 @@
---
abs:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.abs}"
+ scalar: "org.apache.pinot.common.function.scalar.arithmetic.AbsScalarFunction"
transform: "org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction.AbsTransformFunction"
udf: "org.apache.pinot.query.runtime.function.AbsUdf"
acos:
@@ -808,7 +808,7 @@ greaterthanorequal:
transform: "org.apache.pinot.core.operator.transform.function.GreaterThanOrEqualTransformFunction"
udf: null
greatest:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.greatest}"
+ scalar: "org.apache.pinot.common.function.scalar.arithmetic.GreatestScalarFunction"
transform: "org.apache.pinot.core.operator.transform.function.GreatestTransformFunction"
udf: null
griddisk:
@@ -1042,7 +1042,7 @@ lcm:
transform: null
udf: null
least:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.least}"
+ scalar: "org.apache.pinot.common.function.scalar.arithmetic.LeastScalarFunction"
transform: "org.apache.pinot.core.operator.transform.function.LeastTransformFunction"
udf: null
left:
@@ -1114,7 +1114,7 @@ mapvalue:
transform: "org.apache.pinot.core.operator.transform.function.MapValueTransformFunction"
udf: null
max:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.max}"
+ scalar: null
transform: null
udf: null
md2:
@@ -1136,7 +1136,7 @@ millisecondmv:
transform: null
udf: null
min:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.min}"
+ scalar: null
transform: null
udf: null
minus:
@@ -1154,11 +1154,11 @@ minutemv:
transform: null
udf: null
mod:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.mod}"
+ scalar: "org.apache.pinot.common.function.scalar.arithmetic.ModScalarFunction"
transform: "org.apache.pinot.core.operator.transform.function.ModuloTransformFunction"
udf: null
moduloorzero:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.moduloOrZero}"
+ scalar: "org.apache.pinot.common.function.scalar.arithmetic.ModuloOrZeroScalarFunction"
transform: null
udf: null
month:
@@ -1223,7 +1223,7 @@ murmurhash3x64bit64:
transform: null
udf: null
negate:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.negate}"
+ scalar: "org.apache.pinot.common.function.scalar.arithmetic.NegateScalarFunction"
transform: null
udf: null
normalize:
@@ -1260,7 +1260,7 @@ plus:
transform: "org.apache.pinot.core.operator.transform.function.AdditionTransformFunction"
udf: "org.apache.pinot.query.runtime.function.PlusUdf"
positivemodulo:
- scalar: "ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.ArithmeticFunctions.positiveModulo}"
+ scalar: "org.apache.pinot.common.function.scalar.arithmetic.PositiveModuloScalarFunction"
transform: null
udf: null
pow:
diff --git a/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java b/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
index 38aa9ec9556..842bbf97c15 100644
--- a/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
+++ b/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryCompilationTest.java
@@ -65,6 +65,14 @@ public class QueryCompilationTest extends QueryEnvironmentTestBase {
assertNotNull(dispatchableSubPlan);
}
+ @Test
+ public void testPolymorphicArithmeticScalarFunctionsPlanQuery() {
+ DispatchableSubPlan dispatchableSubPlan = _queryEnvironment.planQuery(
+ "SELECT abs(col3), negate(col7), least(col4, col4), greatest(col4, col4), positiveModulo(col3, col6), "
+ + "moduloOrZero(col4, col4) FROM a");
+ assertNotNull(dispatchableSubPlan);
+ }
+
@Test(dataProvider = "testQueryExceptionDataProvider")
public void testQueryWithException(String query, String exceptionSnippet) {
try {
diff --git a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/function/AbsUdf.java b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/function/AbsUdf.java
index ddf09a68c8d..acfe32fb8de 100644
--- a/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/function/AbsUdf.java
+++ b/pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/function/AbsUdf.java
@@ -22,8 +22,9 @@ import com.google.auto.service.AutoService;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
+import org.apache.pinot.common.function.PinotScalarFunction;
import org.apache.pinot.common.function.TransformFunctionType;
-import org.apache.pinot.common.function.scalar.ArithmeticFunctions;
+import org.apache.pinot.common.function.scalar.arithmetic.AbsScalarFunction;
import org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction;
import org.apache.pinot.core.operator.transform.function.TransformFunction;
import org.apache.pinot.core.udf.Udf;
@@ -33,11 +34,10 @@ import org.apache.pinot.core.udf.UdfSignature;
@AutoService(Udf.class)
-public class AbsUdf extends Udf.FromAnnotatedMethod {
-
- public AbsUdf()
- throws NoSuchMethodException {
- super(ArithmeticFunctions.class.getMethod("abs", double.class));
+public class AbsUdf extends Udf {
+ @Override
+ public String getMainName() {
+ return "abs";
}
@Override
@@ -60,4 +60,9 @@ public class AbsUdf extends Udf.FromAnnotatedMethod {
public Pair<TransformFunctionType, Class<? extends TransformFunction>> getTransformFunction() {
return Pair.of(TransformFunctionType.ABS, SingleParamMathTransformFunction.AbsTransformFunction.class);
}
+
+ @Override
+ public PinotScalarFunction getScalarFunction() {
+ return new AbsScalarFunction();
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org