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 220d33f4250..bc76a59e883 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 @@ -208,6 +208,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.LEFT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LEVENSHTEIN; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LOG; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.LOG2; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LOGICAL_AND; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LOGICAL_OR; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LPAD; @@ -637,6 +638,7 @@ Builder populate() { defineMethod(EXP, BuiltInMethod.EXP.method, NullPolicy.STRICT); defineMethod(POWER, BuiltInMethod.POWER.method, NullPolicy.STRICT); defineMethod(ABS, BuiltInMethod.ABS.method, NullPolicy.STRICT); + defineMethod(LOG2, BuiltInMethod.LOG2.method, NullPolicy.STRICT); map.put(LN, new LogImplementor()); map.put(LOG, new LogImplementor()); 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 6e9a29463f9..7ba962eacaf 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -2764,7 +2764,7 @@ public static double power(BigDecimal b0, BigDecimal b1) { } - // LN, LOG, LOG10 + // LN, LOG, LOG10, LOG2 /** SQL {@code LOG(number, number2)} function applied to double values. */ public static double log(double d0, double d1) { @@ -2788,6 +2788,17 @@ public static double log(BigDecimal d0, BigDecimal d1) { return Math.log(d0.doubleValue()) / Math.log(d1.doubleValue()); } + /** SQL {@code LOG2(number)} function applied to double values. */ + public static @Nullable Double log2(double number) { + return (number <= 0) ? null : log(number, 2); + } + + /** SQL {@code LOG2(number)} function applied to + * BigDecimal values. */ + public static @Nullable Double log2(BigDecimal number) { + return log2(number.doubleValue()); + } + // MOD /** SQL MOD operator applied to byte values. */ 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 1fc6524a3f8..86a72d2652f 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 @@ -2149,6 +2149,14 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding OperandTypes.NUMERIC_OPTIONAL_NUMERIC, SqlFunctionCategory.NUMERIC); + /** The "LOG2(numeric)" function. Returns the base 2 logarithm of numeric. */ + @LibraryOperator(libraries = {MYSQL, SPARK}) + public static final SqlFunction LOG2 = + SqlBasicFunction.create("LOG2", + ReturnTypes.DOUBLE_FORCE_NULLABLE, + OperandTypes.NUMERIC, + SqlFunctionCategory.NUMERIC); + @LibraryOperator(libraries = {BIG_QUERY, SPARK}) public static final SqlFunction POW = SqlStdOperatorTable.POWER.withName("POW"); diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java index fd46977ca8b..13e68071c09 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java @@ -396,6 +396,13 @@ public static SqlCall stripSeparator(SqlCall call) { public static final SqlReturnTypeInference DOUBLE_NULLABLE = DOUBLE.andThen(SqlTypeTransforms.TO_NULLABLE); + /** + * Type-inference strategy whereby the result type of a call is a nullable + * Double. + */ + public static final SqlReturnTypeInference DOUBLE_FORCE_NULLABLE = + DOUBLE.andThen(SqlTypeTransforms.FORCE_NULLABLE); + /** * Type-inference strategy whereby the result type of a call is a Char. */ 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 4ac959680d5..b98cc91ea48 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -510,6 +510,7 @@ public enum BuiltInMethod { SAFE_MULTIPLY(SqlFunctions.class, "safeMultiply", double.class, double.class), SAFE_SUBTRACT(SqlFunctions.class, "safeSubtract", double.class, double.class), LOG(SqlFunctions.class, "log", long.class, long.class), + LOG2(SqlFunctions.class, "log2", long.class), SEC(SqlFunctions.class, "sec", double.class), SECH(SqlFunctions.class, "sech", double.class), SIGN(SqlFunctions.class, "sign", long.class), diff --git a/site/_docs/reference.md b/site/_docs/reference.md index 585ebd12778..ed697a9a645 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2783,6 +2783,7 @@ In the following: | b f s | LENGTH(string) | Equivalent to `CHAR_LENGTH(string)` | h s | LEVENSHTEIN(string1, string2) | Returns the Levenshtein distance between *string1* and *string2* | b | LOG(numeric1 [, numeric2 ]) | Returns the logarithm of *numeric1* to base *numeric2*, or base e if *numeric2* is not present +| m s | LOG2(numeric) | Returns the base 2 logarithm of *numeric* | b o s | LPAD(string, length [, pattern ]) | Returns a string or bytes value that consists of *string* prepended to *length* with *pattern* | b | TO_BASE32(string) | Converts the *string* to base-32 encoded form and returns an encoded string | b | FROM_BASE32(string) | Returns the decoded result of a base-32 *string* as a string 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 11cc7880196..0ca4b26f9e2 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -6243,6 +6243,43 @@ void checkRegexpExtract(SqlOperatorFixture f0, FunctionAlias functionAlias) { f.checkNull("log(10, cast(null as real))"); } + /** Test case for + * [CALCITE-6224] + * Add LOG2 function (enabled in MYSQL, Spark library). */ + @Test void testLog2Func() { + final SqlOperatorFixture f0 = fixture(); + f0.checkFails("^log2(4)^", + "No match found for function signature LOG2\\(\\)", false); + f0.setFor(SqlLibraryOperators.LOG2); + final Consumer consumer = f -> { + f.checkScalarApprox("log2(2)", "DOUBLE", + isWithin(1.0, 0.000001)); + f.checkScalarApprox("log2(4)", "DOUBLE", + isWithin(2.0, 0.000001)); + f.checkScalarApprox("log2(65536)", "DOUBLE", + isWithin(16.0, 0.000001)); + f.checkScalarApprox("log2(2.0/3)", "DOUBLE", + isWithin(-0.5849625007211561, 0.000001)); + f.checkScalarApprox("log2(4.0/3)", "DOUBLE", + isWithin(0.4150374992788435, 0.000001)); + f.checkScalarApprox("log2(0.5)", "DOUBLE", + isWithin(-1.0, 0.000001)); + f.checkScalarApprox("log2(cast(10e8 as double))", "DOUBLE", + isWithin(29.897352853986263, 0.000001)); + f.checkScalarApprox("log2(cast(10e8 as float))", "DOUBLE", + isWithin(29.897352853986263, 0.000001)); + f.checkScalarApprox("log2(1e+52)", "DOUBLE", + isWithin(172.74026093414284, 0.000001)); + f.checkNull("log2(0)"); + f.checkNull("log2(-2)"); + f.checkNull("log2(+0.0)"); + f.checkNull("log2(-0.0)"); + f.checkNull("log2(null)"); + f.checkNull("log2(cast(null as real))"); + }; + f0.forEachLibrary(list(SqlLibrary.MYSQL, SqlLibrary.SPARK), consumer); + } + @Test void testRandFunc() { final SqlOperatorFixture f = fixture(); f.setFor(SqlStdOperatorTable.RAND, VmName.EXPAND);