You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by zo...@apache.org on 2023/08/22 02:17:45 UTC

[calcite] branch main updated: [CALCITE-5848] Add BIT_GET and GETBIT functions (enabled in Spark library)

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

zoudan 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 95a59d66c0 [CALCITE-5848] Add BIT_GET and GETBIT functions (enabled in Spark library)
95a59d66c0 is described below

commit 95a59d66c0448f2742f229c0d7e7ed140254c880
Author: Runkang He <hr...@gmail.com>
AuthorDate: Sat Jul 15 10:53:07 2023 +0800

    [CALCITE-5848] Add BIT_GET and GETBIT functions (enabled in Spark library)
---
 .../calcite/adapter/enumerable/RexImpTable.java    |  6 ++
 .../apache/calcite/runtime/CalciteResource.java    |  6 ++
 .../org/apache/calcite/runtime/SqlFunctions.java   | 33 ++++++++++
 .../calcite/sql/fun/SqlLibraryOperators.java       | 13 ++++
 .../org/apache/calcite/sql/type/ReturnTypes.java   | 13 ++++
 .../org/apache/calcite/util/BuiltInMethod.java     |  1 +
 .../calcite/runtime/CalciteResource.properties     |  2 +
 site/_docs/reference.md                            |  2 +
 .../org/apache/calcite/test/SqlOperatorTest.java   | 77 ++++++++++++++++++++++
 9 files changed, 153 insertions(+)

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 588b49a15a..1f8994072a 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
@@ -143,6 +143,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_TO_STRING;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_UNION;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.ASINH;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.ATANH;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.BIT_GET;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.BIT_LENGTH;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.BOOL_AND;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.BOOL_OR;
@@ -180,6 +181,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_TIMESTAMP;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_BASE32;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_BASE64;
 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.ILIKE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.IS_INF;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.IS_NAN;
@@ -528,6 +530,10 @@ public class RexImpTable {
           NullPolicy.STRICT);
       defineMethod(BIT_LENGTH, BuiltInMethod.BIT_LENGTH.method,
           NullPolicy.STRICT);
+      defineMethod(BIT_GET, BuiltInMethod.BIT_GET.method,
+          NullPolicy.STRICT);
+      defineMethod(GETBIT, BuiltInMethod.BIT_GET.method,
+          NullPolicy.STRICT);
       map.put(CONCAT, new ConcatImplementor());
       defineMethod(CONCAT_FUNCTION, BuiltInMethod.MULTI_STRING_CONCAT.method,
           NullPolicy.STRICT);
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index bb33ae9cf0..83a7ec5af7 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -1055,4 +1055,10 @@ public interface CalciteResource {
 
   @BaseMessage("A table function at most has one input table with row semantics. Table function ''{0}'' has multiple input tables with row semantics")
   ExInst<SqlValidatorException> multipleRowSemanticsTables(String funcName);
+
+  @BaseMessage("BIT_GET/GETBIT error: negative position {0,number} not allowed")
+  ExInst<CalciteException> illegalNegativeBitGetPosition(int position);
+
+  @BaseMessage("BIT_GET/GETBIT error: position {0,number} exceeds the bit upper limit {1,number}")
+  ExInst<CalciteException> illegalBitGetPositionExceedsLimit(int position, int size);
 }
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 3faab94e95..8caa2803dd 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -949,6 +949,39 @@ public class SqlFunctions {
     return s.length() * 8;
   }
 
+  /** SQL BIT_GET(value, position) function. */
+  public static byte bitGet(long value, int position) {
+    checkPosition(position, java.lang.Long.SIZE - 1);
+    return (byte) ((value >> position) & 1);
+  }
+
+  /** SQL BIT_GET(value, position) function. */
+  public static byte bitGet(int value, int position) {
+    checkPosition(position, java.lang.Integer.SIZE - 1);
+    return (byte) ((value >> position) & 1);
+  }
+
+  /** SQL BIT_GET(value, position) function. */
+  public static byte bitGet(short value, int position) {
+    checkPosition(position, java.lang.Short.SIZE - 1);
+    return (byte) ((value >> position) & 1);
+  }
+
+  /** SQL BIT_GET(value, position) function. */
+  public static byte bitGet(byte value, int position) {
+    checkPosition(position, java.lang.Byte.SIZE - 1);
+    return (byte) ((value >> position) & 1);
+  }
+
+  private static void checkPosition(int position, int limit) {
+    if (position < 0) {
+      throw RESOURCE.illegalNegativeBitGetPosition(position).ex();
+    }
+    if (limit < position) {
+      throw RESOURCE.illegalBitGetPositionExceedsLimit(position, limit).ex();
+    }
+  }
+
   /** SQL {@code string || string} operator. */
   public static String concat(String s0, String s1) {
     return s0 + s1;
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 7aaa505cfb..6bceb25d3e 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
@@ -1941,4 +1941,17 @@ public abstract class SqlLibraryOperators {
           ReturnTypes.INTEGER_NULLABLE,
           OperandTypes.or(OperandTypes.CHARACTER, OperandTypes.BINARY),
           SqlFunctionCategory.NUMERIC);
+
+  /** The "BIT_GET(value, position)" function. */
+  @LibraryOperator(libraries = {SPARK})
+  public static final SqlBasicFunction BIT_GET =
+      SqlBasicFunction.create("BIT_GET",
+          ReturnTypes.TINYINT_NULLABLE,
+          OperandTypes.NUMERIC_INTEGER,
+          SqlFunctionCategory.NUMERIC);
+
+  /** Alias for {@link #BIT_GET}. */
+  @LibraryOperator(libraries = {SPARK})
+  public static final SqlFunction GETBIT =
+      BIT_GET.withName("GETBIT");
 }
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 acdd678a9d..3b13ba24cc 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
@@ -409,6 +409,19 @@ public abstract class ReturnTypes {
   public static final SqlReturnTypeInference CHAR_FORCE_NULLABLE =
       CHAR.andThen(SqlTypeTransforms.FORCE_NULLABLE);
 
+  /**
+   * Type-inference strategy whereby the result type of a call is a TINYINT.
+   */
+  public static final SqlReturnTypeInference TINYINT =
+          explicit(SqlTypeName.TINYINT);
+
+  /**
+   * Type-inference strategy whereby the result type of a call is a TINYINT
+   * with nulls allowed if any of the operands allow nulls.
+   */
+  public static final SqlReturnTypeInference TINYINT_NULLABLE =
+          TINYINT.andThen(SqlTypeTransforms.TO_NULLABLE);
+
   /**
    * Type-inference strategy whereby the result type of a call is an Integer.
    */
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 19aadc93d7..d4390f1bed 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -428,6 +428,7 @@ public enum BuiltInMethod {
   OCTET_LENGTH(SqlFunctions.class, "octetLength", ByteString.class),
   CHAR_LENGTH(SqlFunctions.class, "charLength", String.class),
   BIT_LENGTH(SqlFunctions.class, "bitLength", String.class),
+  BIT_GET(SqlFunctions.class, "bitGet", long.class, int.class),
   STRING_CONCAT(SqlFunctions.class, "concat", String.class, String.class),
   STRING_CONCAT_WITH_NULL(SqlFunctions.class, "concatWithNull", String.class,
       String.class),
diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 8c67a7538b..809d168a8e 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -345,4 +345,6 @@ InvalidPartitionKeys=Only tables with set semantics may be partitioned. Invalid
 InvalidOrderBy=Only tables with set semantics may be ordered. Invalid ORDER BY clause in the {0,number,#}-th operand of table function ''{1}''
 MultipleRowSemanticsTables=A table function at most has one input table with row semantics. Table function ''{0}'' has multiple input tables with row semantics
 NoOperator=No operator for ''{0}'' with kind: ''{1}'', syntax: ''{2}'' during JSON deserialization
+IllegalNegativeBitGetPosition=BIT_GET/GETBIT error: negative position {0,number} not allowed
+IllegalBitGetPositionExceedsLimit=BIT_GET/GETBIT error: position {0,number} exceeds the bit upper limit {1,number}
 # End CalciteResource.properties
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 018a96cf41..c7555adcbf 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2681,6 +2681,7 @@ BigQuery's type system uses confusingly different names for types and functions:
 | * | ATANH(numeric)                                 | Returns the inverse hyperbolic tangent of *numeric*
 | s | BIT_LENGTH(binary)                             | Returns the bit length of *binary*
 | s | BIT_LENGTH(string)                             | Returns the bit length of *string*
+| s | BIT_GET(value, position)                       | Returns the bit (0 or 1) value at the specified *position* of numeric *value*. The positions are numbered from right to left, starting at zero. The *position* argument cannot be negative
 | b | CEIL(value)                                    | Similar to standard `CEIL(value)` except if *value* is an integer type, the return type is a double
 | m s | CHAR(integer)                                | Returns the character whose ASCII code is *integer* % 256, or null if *integer* &lt; 0
 | b o p | CHR(integer)                               | Returns the character whose UTF-8 code is *integer*
@@ -2732,6 +2733,7 @@ BigQuery's type system uses confusingly different names for types and functions:
 | b | FORMAT_DATETIME(string, timestamp)             | Formats *timestamp* according to the specified format *string*
 | 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)`
 | b o | 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 | 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 d8a0264c0a..ec30702df8 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -3958,6 +3958,83 @@ public class SqlOperatorTest {
     f1.checkNull("BIT_LENGTH(cast(null as binary))");
   }
 
+  /** Generates parameters to test both BIT_GET and GETBIT functions. */
+  static Stream<Arguments> bitGetParameters() {
+    SqlOperatorFixture f0 = SqlOperatorFixtureImpl.DEFAULT.withTester(t -> TESTER);
+    f0.setFor(SqlLibraryOperators.BIT_GET, VmName.EXPAND);
+    SqlOperatorFixture f1 = SqlOperatorFixtureImpl.DEFAULT.withTester(t -> TESTER);
+    f1.setFor(SqlLibraryOperators.GETBIT, VmName.EXPAND);
+    return Stream.of(
+        () -> new Object[] {f0, "BIT_GET"},
+        () -> new Object[] {f1, "GETBIT"});
+  }
+
+  @ParameterizedTest
+  @MethodSource("bitGetParameters")
+  void testBitGetFunc(SqlOperatorFixture f, String functionName) {
+    f.checkFails("^" + functionName + "(cast(11 as bigint), 1)^",
+        "No match found for function signature " + functionName + "\\(<NUMERIC>, <NUMERIC>\\)",
+        false);
+
+    final SqlOperatorFixture f1 = f.withLibrary(SqlLibrary.SPARK);
+    // test for bigint value
+    f1.checkScalarExact(functionName + "(cast(11 as bigint), 3)",
+        "TINYINT NOT NULL", "1");
+    f1.checkScalarExact(functionName + "(cast(-1 as bigint), 0)",
+        "TINYINT NOT NULL", "1");
+    f1.checkScalarExact(functionName + "(cast(11 as bigint), 63)",
+        "TINYINT NOT NULL", "0");
+    f1.checkScalarExact(functionName + "(cast(-1 as bigint), 63)",
+        "TINYINT NOT NULL", "1");
+    f1.checkFails(functionName + "(cast(11 as bigint), 64)",
+        "BIT_GET/GETBIT error: position 64 exceeds the bit upper limit 63",
+        true);
+    f1.checkFails(functionName + "(cast(11 as bigint), -1)",
+        "BIT_GET/GETBIT error: negative position -1 not allowed",
+        true);
+    f1.checkNull(functionName + "(cast(null as bigint), 1)");
+    f1.checkNull(functionName + "(cast(11 as bigint), cast(null as integer))");
+
+    // test for integer value
+    f1.checkScalarExact(functionName + "(cast(11 as integer), 3)",
+        "TINYINT NOT NULL", "1");
+    f1.checkScalarExact(functionName + "(cast(-1 as integer), 0)",
+        "TINYINT NOT NULL", "1");
+    f1.checkScalarExact(functionName + "(cast(11 as integer), 31)",
+        "TINYINT NOT NULL", "0");
+    f1.checkScalarExact(functionName + "(cast(-1 as integer), 31)",
+        "TINYINT NOT NULL", "1");
+    f1.checkFails(functionName + "(cast(11 as integer), 32)",
+        "BIT_GET/GETBIT error: position 32 exceeds the bit upper limit 31",
+        true);
+
+    // test for smallint value
+    f1.checkScalarExact(functionName + "(cast(11 as smallint), 3)",
+        "TINYINT NOT NULL", "1");
+    f1.checkScalarExact(functionName + "(cast(-1 as smallint), 0)",
+        "TINYINT NOT NULL", "1");
+    f1.checkScalarExact(functionName + "(cast(11 as smallint), 15)",
+        "TINYINT NOT NULL", "0");
+    f1.checkScalarExact(functionName + "(cast(-1 as smallint), 15)",
+        "TINYINT NOT NULL", "1");
+    f1.checkFails(functionName + "(cast(11 as smallint), 16)",
+        "BIT_GET/GETBIT error: position 16 exceeds the bit upper limit 15",
+        true);
+
+    // test for tinyint value
+    f1.checkScalarExact(functionName + "(cast(11 as tinyint), 3)",
+        "TINYINT NOT NULL", "1");
+    f1.checkScalarExact(functionName + "(cast(-1 as tinyint), 0)",
+        "TINYINT NOT NULL", "1");
+    f1.checkScalarExact(functionName + "(cast(11 as tinyint), 7)",
+        "TINYINT NOT NULL", "0");
+    f1.checkScalarExact(functionName + "(cast(-1 as tinyint), 7)",
+        "TINYINT NOT NULL", "1");
+    f1.checkFails(functionName + "(cast(11 as tinyint), 8)",
+        "BIT_GET/GETBIT error: position 8 exceeds the bit upper limit 7",
+        true);
+  }
+
   @Test void testAsciiFunc() {
     final SqlOperatorFixture f = fixture();
     f.setFor(SqlStdOperatorTable.ASCII, VmName.EXPAND);