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 ca8f93feb9f..bbe2743ac7c 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
@@ -225,6 +225,7 @@
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.HEX;
+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;
@@ -880,6 +881,7 @@ void populate1() {
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 dc4c6c25878..52ea3bbef5c 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -4319,6 +4319,18 @@ public static double degrees(double b0) {
return CombinatoricsUtils.factorial(b0);
}
+ /** SQL HYPOT operator applied to double values. */
+ public static double hypot(double a, double b) {
+ return Math.hypot(a, b);
+ }
+
+ /** SQL HYPOT operator applied to general numeric values. */
+ public static double hypot(Object a, Object b) {
+ final Number left = (Number) a;
+ final Number right = (Number) b;
+ return hypot(left.doubleValue(), right.doubleValue());
+ }
+
/** 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 f8f4e9273b8..0f631c2e2f5 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
@@ -64,6 +64,7 @@
import static org.apache.calcite.sql.fun.SqlLibrary.ALL;
import static org.apache.calcite.sql.fun.SqlLibrary.BIG_QUERY;
import static org.apache.calcite.sql.fun.SqlLibrary.CALCITE;
+import static org.apache.calcite.sql.fun.SqlLibrary.CLICKHOUSE;
import static org.apache.calcite.sql.fun.SqlLibrary.HIVE;
import static org.apache.calcite.sql.fun.SqlLibrary.MSSQL;
import static org.apache.calcite.sql.fun.SqlLibrary.MYSQL;
@@ -2561,6 +2562,15 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding
OperandTypes.NUMERIC,
SqlFunctionCategory.STRING);
+ /** The {@code HYPOT(numeric1, numeric2)} function; returns
+ * sqrt(numeric1^2 + numeric2^2) without intermediate overflow or underflow. */
+ @LibraryOperator(libraries = {SPARK, CLICKHOUSE})
+ public static final SqlFunction HYPOT =
+ SqlBasicFunction.create("HYPOT",
+ ReturnTypes.DOUBLE_NULLABLE,
+ OperandTypes.NUMERIC_NUMERIC,
+ SqlFunctionCategory.NUMERIC);
+
@LibraryOperator(libraries = {BIG_QUERY, MYSQL, POSTGRESQL, SPARK, HIVE})
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 38344096404..cfeb4fe77c4 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -598,6 +598,7 @@ public enum BuiltInMethod {
TAND(SqlFunctions.class, "tand", 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 617ce1a83be..0dee0dff6a8 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2936,6 +2936,7 @@ In the following:
| b | FORMAT_TIME(string, time) | Formats *time* according to the specified format *string*
| b | FORMAT_TIMESTAMP(string timestamp) | Formats *timestamp* according to the specified format *string*
| s | GETBIT(value, position) | Equivalent to `BIT_GET(value, position)`
+| s i | HYPOT(numeric1, numeric2) | Returns sqrt(*numeric1*^2 + *numeric2*^2) without intermediate overflow or underflow
| b o p r s h | GREATEST(expr [, expr ]*) | Returns the greatest of the expressions
| b h s | IF(condition, value1, value2) | Returns *value1* if *condition* is TRUE, *value2* otherwise
| b s | IFNULL(value1, value2) | Equivalent to `NVL(value1, value2)`
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 90839a1daa7..b27c188620a 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -10018,6 +10018,60 @@ void checkArrayReverseFunc(SqlOperatorFixture f0, SqlFunction function,
f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.SPARK), consumer);
}
+ /** Test case for
+ * [CALCITE-6066] Add HYPOT function (enabled in Spark library). */
+ @Test void testHypotFunc() {
+ final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.HYPOT);
+ f0.checkFails("^hypot(3, 4)^",
+ "No match found for function signature HYPOT\\(, \\)",
+ false);
+ final Consumer consumer = f -> {
+ 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.checkScalarApprox("hypot(cast(3 as float), cast(4 as real))", "DOUBLE NOT NULL",
+ isWithin(5.0000d, 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))");
+
+ // unsigned type
+ f.checkScalarApprox("hypot(cast(3 as integer unsigned), cast(4 as integer unsigned))",
+ "DOUBLE NOT NULL", isWithin(5.0000d, 0.0001d));
+ f.checkScalarApprox("hypot(cast(3 as bigint unsigned), cast(4 as integer unsigned))",
+ "DOUBLE NOT NULL", isWithin(5.0000d, 0.0001d));
+ f.checkScalarApprox("hypot(cast(3 as bigint unsigned), cast(4 as bigint unsigned))",
+ "DOUBLE NOT NULL", isWithin(5.0000d, 0.0001d));
+ f.checkScalarApprox("hypot(cast(3 as smallint unsigned), cast(4 as smallint unsigned))",
+ "DOUBLE NOT NULL", isWithin(5.0000d, 0.0001d));
+ f.checkScalarApprox("hypot(cast(3 as tinyint unsigned), cast(4 as tinyint unsigned))",
+ "DOUBLE NOT NULL", isWithin(5.0000d, 0.0001d));
+
+ // mixed type
+ f.checkScalarApprox("hypot(cast(3 as tinyint), cast(4 as tinyint unsigned))",
+ "DOUBLE NOT NULL", isWithin(5.0000d, 0.0001d));
+ f.checkScalarApprox("hypot(cast(3 as bigint), cast(4 as tinyint unsigned))",
+ "DOUBLE NOT NULL", isWithin(5.0000d, 0.0001d));
+ f.checkScalarApprox("hypot(cast(3 as double), cast(4 as tinyint unsigned))",
+ "DOUBLE NOT NULL", isWithin(5.0000d, 0.0001d));
+ };
+ f0.forEachLibrary(list(SqlLibrary.SPARK, SqlLibrary.CLICKHOUSE), consumer);
+ }
+
@Test void testInfinity() {
final SqlOperatorFixture f = fixture();
f.checkScalar("cast('Infinity' as double)", "Infinity",