diff --git a/babel/src/test/resources/sql/big-query.iq b/babel/src/test/resources/sql/big-query.iq index 82586abaf1c..0df376e76ac 100755 --- a/babel/src/test/resources/sql/big-query.iq +++ b/babel/src/test/resources/sql/big-query.iq @@ -730,6 +730,68 @@ SELECT SPLIT(x'abc2') as result; Call to function 'SPLIT' with argument of type 'BINARY(2)' requires extra delimiter argument !error +##################################################################### +# LN +# +# LN(x) +# +# Computes the natural logarithm of x. Generates an error if x is less than or +# equal to zero. + +SELECT LN(100) as result; ++-------------------+ +| result | ++-------------------+ +| 4.605170185988092 | ++-------------------+ +(1 row) + +!ok + +##################################################################### +# LOG +# +# LOG(x, y) +# +# If only x is present, LOG is a synonym of LN. If y is also +# present, LOG computes the logarithm of x to base y. +SELECT LOG(64, 8) as result; ++--------+ +| result | ++--------+ +| 2.0 | ++--------+ +(1 row) + +!ok + +SELECT LOG(100) as result; ++-------------------+ +| result | ++-------------------+ +| 4.605170185988092 | ++-------------------+ +(1 row) + +!ok + +##################################################################### +# LOG10 +# +# LOG10(x) +# +# Similar to LOG, but computes logarithm to base 10. + +SELECT LOG10(100) as result; ++--------+ +| result | ++--------+ +| 2.0 | ++--------+ +(1 row) + +!ok + ##################################################################### # STRING # 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 c2cb550cca0..b6832169722 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 @@ -153,6 +153,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_STORAGE_SIZE; import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_TYPE; import static org.apache.calcite.sql.fun.SqlLibraryOperators.LEFT; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.LOG; 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; @@ -490,10 +491,12 @@ Builder populate() { defineMethod(MOD, "mod", NullPolicy.STRICT); defineMethod(EXP, "exp", NullPolicy.STRICT); defineMethod(POWER, "power", NullPolicy.STRICT); - defineMethod(LN, "ln", NullPolicy.STRICT); - defineMethod(LOG10, "log10", NullPolicy.STRICT); defineMethod(ABS, "abs", NullPolicy.STRICT); + map.put(LN, new LogImplementor()); + map.put(LOG, new LogImplementor()); + map.put(LOG10, new LogImplementor()); + map.put(RAND, new RandImplementor()); map.put(RAND_INTEGER, new RandIntegerImplementor()); @@ -3684,6 +3687,41 @@ private static class LogicalNotImplementor extends AbstractRexCallImplementor { } } + /** 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 LogImplementor extends AbstractRexCallImplementor { + LogImplementor() { + super("log", NullPolicy.STRICT, true); + } + + @Override Expression implementSafe(final RexToLixTranslator translator, + final RexCall call, final List argValueList) { + return Expressions.call(BuiltInMethod.LOG.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)); + } + // 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()); + } + } + } + /** * Implementation that calls a given {@link java.lang.reflect.Method}. * 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 fcd88bac1eb..d2939dc3144 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -1567,28 +1567,29 @@ public static double power(BigDecimal b0, BigDecimal b1) { return Math.pow(b0.doubleValue(), b1.doubleValue()); } - // LN - /** SQL {@code LN(number)} function applied to double values. */ - public static double ln(double d) { - return Math.log(d); - } + // LN, LOG, LOG10 - /** SQL {@code LN(number)} function applied to BigDecimal values. */ - public static double ln(BigDecimal d) { - return Math.log(d.doubleValue()); + /** SQL {@code LOG(number, number2)} function applied to double values. */ + public static double log(double d0, double d1) { + return Math.log(d0) / Math.log(d1); } - // LOG10 + /** SQL {@code LOG(number, number2)} function applied to + * double and BigDecimal values. */ + public static double log(double d0, BigDecimal d1) { + return Math.log(d0) / Math.log(d1.doubleValue()); + } - /** SQL LOG10(numeric) operator applied to double values. */ - public static double log10(double b0) { - return Math.log10(b0); + /** SQL {@code LOG(number, number2)} function applied to + * BigDecimal and double values. */ + public static double log(BigDecimal d0, double d1) { + return Math.log(d0.doubleValue()) / Math.log(d1); } - /** SQL {@code LOG10(number)} function applied to BigDecimal values. */ - public static double log10(BigDecimal d) { - return Math.log10(d.doubleValue()); + /** SQL {@code LOG(number, number2)} function applied to double values. */ + public static double log(BigDecimal d0, BigDecimal d1) { + return Math.log(d0.doubleValue()) / Math.log(d1.doubleValue()); } // 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 af3aa62f27a..ae45e899e74 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 @@ -1177,6 +1177,18 @@ static RelDataType deriveTypeSplit(SqlOperatorBinding operatorBinding, OperandTypes.STRING.or(OperandTypes.BINARY), SqlFunctionCategory.STRING); + /** The "LOG(value [, value2])" function. + * + * @see SqlStdOperatorTable#LN + * @see SqlStdOperatorTable#LOG10 + */ + @LibraryOperator(libraries = {BIG_QUERY}) + public static final SqlFunction LOG = + SqlBasicFunction.create("LOG", + ReturnTypes.DOUBLE_NULLABLE, + OperandTypes.NUMERIC_OPTIONAL_NUMERIC, + SqlFunctionCategory.NUMERIC); + @LibraryOperator(libraries = {BIG_QUERY}) public static final SqlFunction POW = SqlStdOperatorTable.POWER.withName("POW"); diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java index c336f87b84e..b891ba7699a 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java @@ -354,6 +354,11 @@ public static SqlOperandTypeChecker variadic( public static final SqlSingleOperandTypeChecker INTEGER = family(SqlTypeFamily.INTEGER); + public static final SqlSingleOperandTypeChecker NUMERIC_OPTIONAL_NUMERIC = + family(ImmutableList.of(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC), + // Second operand optional (operand index 0, 1) + number -> number == 1); + public static final SqlSingleOperandTypeChecker NUMERIC_OPTIONAL_INTEGER = family(ImmutableList.of(SqlTypeFamily.NUMERIC, SqlTypeFamily.INTEGER), // Second operand optional (operand index 0, 1) 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 9edc77b636d..dae7fdc0c02 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -432,6 +432,7 @@ public enum BuiltInMethod { RAND_INTEGER(RandomFunction.class, "randInteger", int.class), RAND_INTEGER_SEED(RandomFunction.class, "randIntegerSeed", int.class, int.class), + LOG(SqlFunctions.class, "log", long.class, long.class), TANH(SqlFunctions.class, "tanh", long.class), SINH(SqlFunctions.class, "sinh", long.class), TRUNCATE(SqlFunctions.class, "truncate", String.class, int.class), diff --git a/site/_docs/reference.md b/site/_docs/reference.md index c1a441db80a..f9ea1d9d60c 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2706,6 +2706,7 @@ BigQuery's type system uses confusingly different names for types and functions: | b o | LEAST(expr [, expr ]* ) | Returns the least of the expressions | b m p | LEFT(string, length) | Returns the leftmost *length* characters from the *string* | b | LENGTH(string) | Equivalent to `CHAR_LENGTH(string)` +| b | LOG(numeric1 [, numeric2 ]) | Returns the logarithm of *numeric1* to base *numeric2*, or base e if *numeric2* is not present | b o | LPAD(string, length[, pattern ]) | Returns a string or bytes value that consists of *string* prepended to *length* with *pattern* | m | TO_BASE64(string) | Converts the *string* to base-64 encoded form and returns a encoded string | b m | FROM_BASE64(string) | Returns the decoded result of a base-64 *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 8cd4bff3900..a586c264f7d 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -5061,7 +5061,7 @@ private static void checkIf(SqlOperatorFixture f) { f.checkNull("ln(cast(null as tinyint))"); } - @Test void testLogFunc() { + @Test void testLog10Func() { final SqlOperatorFixture f = fixture(); f.setFor(SqlStdOperatorTable.LOG10, VmName.EXPAND); f.checkScalarApprox("log10(10)", "DOUBLE NOT NULL", @@ -5077,6 +5077,32 @@ private static void checkIf(SqlOperatorFixture f) { f.checkNull("log10(cast(null as real))"); } + @Test void testLogFunc() { + final SqlOperatorFixture f0 = fixture() + .setFor(SqlLibraryOperators.LOG, VmName.EXPAND); + f0.checkFails("^log(100, 10)^", + "No match found for function signature LOG\\(, \\)", false); + final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY); + f.checkScalarApprox("log(10, 10)", "DOUBLE NOT NULL", + isWithin(1.0, 0.000001)); + f.checkScalarApprox("log(64, 8)", "DOUBLE NOT NULL", + isWithin(2.0, 0.000001)); + f.checkScalarApprox("log(27,3)", "DOUBLE NOT NULL", + isWithin(3.0, 0.000001)); + f.checkScalarApprox("log(100, 10)", "DOUBLE NOT NULL", + isWithin(2.0, 0.000001)); + f.checkScalarApprox("log(10, 100)", "DOUBLE NOT NULL", + isWithin(0.5, 0.000001)); + f.checkScalarApprox("log(cast(10e6 as double), 10)", "DOUBLE NOT NULL", + isWithin(7.0, 0.000001)); + f.checkScalarApprox("log(cast(10e8 as float), 10)", "DOUBLE NOT NULL", + isWithin(9.0, 0.000001)); + f.checkScalarApprox("log(cast(10e-3 as real), 10)", "DOUBLE NOT NULL", + isWithin(-2.0, 0.000001)); + f.checkNull("log(cast(null as real), 10)"); + f.checkNull("log(10, cast(null as real))"); + } + @Test void testRandFunc() { final SqlOperatorFixture f = fixture(); f.setFor(SqlStdOperatorTable.RAND, VmName.EXPAND);