diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/NullPolicy.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/NullPolicy.java index 1d426ea43b1..5e09815a954 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/NullPolicy.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/NullPolicy.java @@ -28,7 +28,7 @@ public enum NullPolicy { /** Returns null if and only if all of the arguments are null; * If all of the arguments are false return false otherwise true. */ ALL, - /** Returns null if and only if one of the arguments are null. */ + /** Returns null if and only if at least one of the arguments is null. */ STRICT, /** Returns null if one of the arguments is null, and possibly other times. */ SEMI_STRICT, diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java index eaf12af1580..2c7353592e9 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java @@ -188,6 +188,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_BASE64; import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_HEX; import static org.apache.calcite.sql.fun.SqlLibraryOperators.GETBIT; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.HYPOT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ILIKE; import static org.apache.calcite.sql.fun.SqlLibraryOperators.IS_INF; import static org.apache.calcite.sql.fun.SqlLibraryOperators.IS_NAN; @@ -657,6 +658,7 @@ Builder populate() { defineMethod(CSCH, BuiltInMethod.CSCH.method, NullPolicy.STRICT); defineMethod(DEGREES, BuiltInMethod.DEGREES.method, NullPolicy.STRICT); defineMethod(FACTORIAL, BuiltInMethod.FACTORIAL.method, NullPolicy.STRICT); + defineMethod(HYPOT, BuiltInMethod.HYPOT.method, NullPolicy.STRICT); defineMethod(IS_INF, BuiltInMethod.IS_INF.method, NullPolicy.STRICT); defineMethod(IS_NAN, BuiltInMethod.IS_NAN.method, NullPolicy.STRICT); defineMethod(POW, BuiltInMethod.POWER.method, NullPolicy.STRICT); diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index a92e1a95a93..cf51a72adaa 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -3114,6 +3114,16 @@ public static double degrees(double b0) { return CombinatoricsUtils.factorial(b0); } + /** SQL HYPOT operator applied to BigDecimal values. */ + public static double hypot(BigDecimal a, BigDecimal b) { + return hypot(a.doubleValue(), b.doubleValue()); + } + + /** SQL HYPOT operator applied to double values. */ + public static double hypot(double a, double b) { + return Math.hypot(a, b); + } + /** SQL IS_INF operator applied to BigDecimal values. */ public static boolean isInf(BigDecimal b0) { return Double.isInfinite(b0.doubleValue()); diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java index e05c4fd1c4d..c2330c98d11 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java @@ -2046,6 +2046,15 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding OperandTypes.INTEGER, SqlFunctionCategory.NUMERIC); + /** The {@code HYPOT(numeric1, numeric2)} function; returns + * sqrt(numeric1^2 + numeric2^2) without intermediate overflow or underflow. */ + @LibraryOperator(libraries = {SPARK}) + public static final SqlFunction HYPOT = + SqlBasicFunction.create("HYPOT", + ReturnTypes.DOUBLE_NULLABLE, + OperandTypes.NUMERIC_NUMERIC, + SqlFunctionCategory.NUMERIC); + @LibraryOperator(libraries = {BIG_QUERY, MYSQL, POSTGRESQL}) public static final SqlFunction MD5 = SqlBasicFunction.create("MD5", diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java index a5fc4b03e23..179d25c1848 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -515,6 +515,7 @@ public enum BuiltInMethod { TAN(SqlFunctions.class, "tan", double.class), TANH(SqlFunctions.class, "tanh", long.class), SINH(SqlFunctions.class, "sinh", long.class), + HYPOT(SqlFunctions.class, "hypot", double.class, double.class), TRUNCATE(SqlFunctions.class, "truncate", String.class, int.class), TRUNCATE_OR_PAD(SqlFunctions.class, "truncateOrPad", String.class, int.class), TRIM(SqlFunctions.class, "trim", boolean.class, boolean.class, String.class, diff --git a/site/_docs/reference.md b/site/_docs/reference.md index d087340d532..4da519eae60 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2740,6 +2740,7 @@ BigQuery's type system uses confusingly different names for types and functions: | b | FORMAT_TIMESTAMP(string timestamp) | Formats *timestamp* according to the specified format *string* | s | GETBIT(value, position) | Equivalent to `BIT_GET(value, position)` | b o | GREATEST(expr [, expr ]*) | Returns the greatest of the expressions +| s | HYPOT(numeric1, numeric2) | Returns sqrt(*numeric1*^2 + *numeric2*^2) without intermediate overflow or underflow | b h s | IF(condition, value1, value2) | Returns *value1* if *condition* is TRUE, *value2* otherwise | b | IFNULL(value1, value2) | Equivalent to `NVL(value1, value2)` | p | string1 ILIKE string2 [ ESCAPE string3 ] | Whether *string1* matches pattern *string2*, ignoring case (similar to `LIKE`) diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index b0338bc759f..97d73a3f0ba 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -7659,6 +7659,34 @@ private static void checkIf(SqlOperatorFixture f) { f.checkNull("pow(cast(null as integer), 2)"); } + @Test void testHypotFunc() { + final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.HYPOT); + f0.checkFails("^hypot(3, 4)^", + "No match found for function signature HYPOT\\(, \\)", + false); + final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.SPARK); + f.checkScalarApprox("hypot(3, 4)", "DOUBLE NOT NULL", + isWithin(5.0000d, 0.0001d)); + f.checkScalarApprox("hypot(3.0, cast(4 as bigint))", "DOUBLE NOT NULL", + isWithin(5.0000d, 0.0001d)); + f.checkScalarApprox("hypot(cast(-2 as bigint), cast(-4 as bigint))", + "DOUBLE NOT NULL", + isWithin(4.4721d, 0.0001d)); + f.checkScalarApprox("hypot(cast(3.0 as double), cast(4.0 as double))", + "DOUBLE NOT NULL", + isWithin(5.0000d, 0.0001d)); + f.checkScalarApprox("hypot(-2.5, cast(-4.5 as double))", "DOUBLE NOT NULL", + isWithin(5.1478d, 0.0001d)); + f.checkScalarApprox("hypot(-2.5, -4.5)", "DOUBLE NOT NULL", + isWithin(5.1478d, 0.0001d)); + f.checkType("hypot(cast(null as bigint), 1)", "DOUBLE"); + f.checkNull("hypot(cast(null as bigint), 1)"); + f.checkNull("hypot(1, cast(null as bigint))"); + f.checkNull("hypot(cast(null as bigint), cast(null as bigint))"); + f.checkNull("hypot(cast(null as double), cast(null as double))"); + f.checkNull("hypot(cast(null as decimal), cast(null as decimal))"); + } + @Test void testInfinity() { final SqlOperatorFixture f = fixture(); f.checkScalar("cast('Infinity' as double)", "Infinity",