You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2023/04/11 01:53:22 UTC

[calcite] branch main updated: [CALCITE-5565] Add LOG function (enabled in BigQuery library)

This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 74bc025fbb [CALCITE-5565] Add LOG function (enabled in BigQuery library)
74bc025fbb is described below

commit 74bc025fbb5af4f43762926e0bec75acf6ac3025
Author: Tanner Clary <ta...@google.com>
AuthorDate: Wed Mar 8 01:31:11 2023 +0000

    [CALCITE-5565] Add LOG function (enabled in BigQuery library)
    
    Close apache/calcite#3099
---
 babel/src/test/resources/sql/big-query.iq          | 62 ++++++++++++++++++++++
 .../calcite/adapter/enumerable/RexImpTable.java    | 42 ++++++++++++++-
 .../org/apache/calcite/runtime/SqlFunctions.java   | 31 +++++------
 .../calcite/sql/fun/SqlLibraryOperators.java       | 12 +++++
 .../org/apache/calcite/sql/type/OperandTypes.java  |  5 ++
 .../org/apache/calcite/util/BuiltInMethod.java     |  1 +
 site/_docs/reference.md                            |  1 +
 .../org/apache/calcite/test/SqlOperatorTest.java   | 28 +++++++++-
 8 files changed, 164 insertions(+), 18 deletions(-)

diff --git a/babel/src/test/resources/sql/big-query.iq b/babel/src/test/resources/sql/big-query.iq
index 82586abaf1..0df376e76a 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 c2cb550cca..b683216972 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_SET;
 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 @@ public class RexImpTable {
       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 @@ public class RexImpTable {
     }
   }
 
+  /** Implementor for the {@code LN}, {@code LOG}, and {@code LOG10} operators.
+   *
+   * <p>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<Expression> argValueList) {
+      return Expressions.call(BuiltInMethod.LOG.method, args(call, argValueList));
+    }
+
+    private static List<Expression> args(RexCall call,
+        List<Expression> argValueList) {
+      Expression operand0 = argValueList.get(0);
+      final Expressions.FluentList<Expression> 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 fcd88bac1e..d2939dc314 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 class SqlFunctions {
     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 <code>LOG10(numeric)</code> 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 af3aa62f27..ae45e899e7 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 @@ public abstract class SqlLibraryOperators {
           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 c336f87b84..b891ba7699 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 abstract class OperandTypes {
   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 9edc77b636..dae7fdc0c0 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 c1a441db80..f9ea1d9d60 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 8cd4bff390..a586c264f7 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 @@ public class SqlOperatorTest {
     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 @@ public class SqlOperatorTest {
     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\\(<NUMERIC>, <NUMERIC>\\)", 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);