From d49c99436622c1a334feb2336f2a6dad3225e51e Mon Sep 17 00:00:00 2001 From: caicancai <2356672992@qq.com> Date: Fri, 1 Mar 2024 16:33:22 +0800 Subject: [PATCH] [CALCITE-6325] Add LOG function (enabled in Mysql, Spark library) --- .../adapter/enumerable/RexImpTable.java | 48 +++++++++++- .../apache/calcite/runtime/SqlFunctions.java | 25 +++++-- .../calcite/sql/fun/SqlLibraryOperators.java | 8 ++ .../apache/calcite/util/BuiltInMethod.java | 2 +- site/_docs/reference.md | 1 + .../apache/calcite/test/SqlOperatorTest.java | 74 +++++++++++++++++++ 6 files changed, 149 insertions(+), 9 deletions(-) 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 bc76a59e8830..33e23ba4a98f 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 @@ -211,6 +211,7 @@ 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.LOG_MS; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LPAD; import static org.apache.calcite.sql.fun.SqlLibraryOperators.MAP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.MAP_CONCAT; @@ -638,12 +639,14 @@ 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()); map.put(LOG10, new LogImplementor()); + map.put(LOG_MS, new LogMSImplementor()); + map.put(LOG2, new LogMSImplementor()); + defineReflective(RAND, BuiltInMethod.RAND.method, BuiltInMethod.RAND_SEED.method); defineReflective(RAND_INTEGER, BuiltInMethod.RAND_INTEGER.method, @@ -4136,9 +4139,52 @@ private static List args(RexCall call, if (argValueList.size() == 2) { return list.append(argValueList.get(1)); } + if (argValueList.size() == 1) { + return list.append(Expressions.constant(Math.exp(1))); + } + // fall through + case "LN": + return list.append(Expressions.constant(Math.exp(1))); + case "LOG10": + return list.append(Expressions.constant(BigDecimal.TEN)); + default: + throw new AssertionError("Operator not found: " + call.getOperator()); + } + } + } + + /** Implementor for the {@code LN}, {@code LOG}, and {@code LOG10} operators. + * + *

Handles all logarithm functions using log rules to determine the + * appropriate base (i.e. base e for LN). + */ + private static class LogMSImplementor extends AbstractRexCallImplementor { + LogMSImplementor() { + super("logMS", NullPolicy.STRICT, true); + } + + @Override Expression implementSafe(final RexToLixTranslator translator, + final RexCall call, final List argValueList) { + return Expressions.call(BuiltInMethod.LOGMS.method, args(call, argValueList)); + } + + private static List args(RexCall call, + List argValueList) { + Expression operand0 = argValueList.get(0); + final Expressions.FluentList list = Expressions.list(operand0); + switch (call.getOperator().getName()) { + case "LOG": + if (argValueList.size() == 2) { + return list.append(argValueList.get(1)); + } + if (argValueList.size() == 1) { + return list.append(Expressions.constant(Math.exp(1))); + } // fall through case "LN": return list.append(Expressions.constant(Math.exp(1))); + case "LOG2": + return list.append(Expressions.constant(2)); case "LOG10": return list.append(Expressions.constant(BigDecimal.TEN)); default: 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 7ba962eacaf9..915bf7370055 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -2788,15 +2788,26 @@ 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 LOG(number, number2)} function applied to double values. */ + public static @Nullable Double logMS(double number, double number2) { + return (number <= 0) ? null : log(number, number2); + } + + /** SQL {@code LOGMS(number, number2)} function applied to + * double and BigDecimal values. */ + public static @Nullable Double logMS(double number, BigDecimal number2) { + return (number <= 0) ? null : log(number, number2); + } + + /** SQL {@code LOGMS(number, number2)} function applied to + * BigDecimal and double values. */ + public static @Nullable Double logMS(BigDecimal number, double number2) { + return (number.doubleValue() <= 0) ? null : log(number, number2); } - /** SQL {@code LOG2(number)} function applied to - * BigDecimal values. */ - public static @Nullable Double log2(BigDecimal number) { - return log2(number.doubleValue()); + /** SQL {@code LOGMS(number, number2)} function applied to double values. */ + public static @Nullable Double logMS(BigDecimal number, BigDecimal number2) { + return (number.doubleValue() <= 0) ? null : log(number, number2); } // MOD 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 86a72d2652f8..3193f6dcd43f 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 "LOG(numeric, numeric1)" function. Returns the base numeric1 logarithm of numeric. */ + @LibraryOperator(libraries = {MYSQL, SPARK}) + public static final SqlFunction LOG_MS = + SqlBasicFunction.create("LOG", + ReturnTypes.DOUBLE_FORCE_NULLABLE, + 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 = 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 b98cc91ea48a..4fdefa95e023 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -510,7 +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), + LOGMS(SqlFunctions.class, "logMS", long.class, 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 ed697a9a645f..cc1e3468dd86 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 | 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 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 0ca4b26f9e2b..5a400edec179 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,20 @@ void checkRegexpExtract(SqlOperatorFixture f0, FunctionAlias functionAlias) { f.checkNull("log(10, cast(null as real))"); } + @Test void testLogFuncByOneParameter() { + final SqlOperatorFixture f0 = fixture() + .setFor(SqlLibraryOperators.LOG, VmName.EXPAND); + final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY); + f0.checkFails("^log(100)^", + "No match found for function signature LOG\\(\\)", false); + f.checkScalarApprox("log(2.71828)", "DOUBLE NOT NULL", + isWithin(1.0, 0.000001)); + f.checkScalarApprox("log(2.71828)", "DOUBLE NOT NULL", + isWithin(0.999999327, 0.0000001)); + f.checkNull("log(cast(null as real), 10)"); + f.checkNull("log(10, cast(null as real))"); + } + /** Test case for * [CALCITE-6224] * Add LOG2 function (enabled in MYSQL, Spark library). */ @@ -6280,6 +6294,66 @@ void checkRegexpExtract(SqlOperatorFixture f0, FunctionAlias functionAlias) { f0.forEachLibrary(list(SqlLibrary.MYSQL, SqlLibrary.SPARK), consumer); } + /** Test case for + * [CALCITE-6259] + * Add LOG function (enabled in MYSQL, Spark library). */ + @Test void testLogMSFunc() { + final SqlOperatorFixture f0 = Fixtures.forOperators(true); + f0.checkFails("^log(100, 10)^", + "No match found for function signature LOG\\(, \\)", false); + f0.setFor(SqlLibraryOperators.LOG_MS); + final Consumer consumer = f -> { + f.checkScalarApprox("log(10, 10)", "DOUBLE", + isWithin(1.0, 0.000001)); + f.checkScalarApprox("log(64, 8)", "DOUBLE", + isWithin(2.0, 0.000001)); + f.checkScalarApprox("log(27,3)", "DOUBLE", + isWithin(3.0, 0.000001)); + f.checkScalarApprox("log(100, 10)", "DOUBLE", + isWithin(2.0, 0.000001)); + f.checkScalarApprox("log(10, 100)", "DOUBLE", + isWithin(0.5, 0.000001)); + f.checkScalarApprox("log(cast(10e6 as double), 10)", "DOUBLE", + isWithin(7.0, 0.000001)); + f.checkScalarApprox("log(cast(10e8 as float), 10)", "DOUBLE", + isWithin(9.0, 0.000001)); + f.checkScalarApprox("log(cast(10e-3 as real), 10)", "DOUBLE", + isWithin(-2.0, 0.000001)); + f.checkNull("log(cast(null as real), 10)"); + f.checkNull("log(10, cast(null as real))"); + f.checkNull("log(0, 2)"); + f.checkNull("log(0,-2)"); + f.checkNull("log(0, +0.0)"); + f.checkNull("log(0, 0.0)"); + f.checkNull("log(null)"); + f.checkNull("log(cast(null as real))"); + }; + f0.forEachLibrary(list(SqlLibrary.MYSQL, SqlLibrary.SPARK), consumer); + } + + /** Test case for + * [CALCITE-6259] + * Add LOG function (enabled in MYSQL, Spark library). */ + @Test void testLogMSFuncByOneparameter() { + final SqlOperatorFixture f0 = fixture(); + f0.checkFails("^log(100)^", + "No match found for function signature LOG\\(\\)", false); + f0.setFor(SqlLibraryOperators.LOG_MS); + final Consumer consumer = f -> { + f.checkScalarApprox("log(2.71828)", "DOUBLE", + isWithin(1.0, 0.000001)); + f.checkScalarApprox("log(2.71828)", "DOUBLE", + isWithin(0.999999327, 0.0000001)); + f.checkNull("log(cast(null as real))"); + f.checkNull("log(cast(null as real))"); + f.checkNull("log(0)"); + f.checkNull("log(+0.0)"); + f.checkNull("log(null)"); + f.checkNull("log(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);