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

mbudiu 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 f8d2dfe91d [CALCITE-7109] Support bitwise leftshifit(<<) operator And 
Implement LEFT_SHIFT function in SQL
f8d2dfe91d is described below

commit f8d2dfe91da218b94a0d22bbf04dc26a081cedd4
Author: xiaojun <[email protected]>
AuthorDate: Sat Aug 2 16:23:31 2025 +0800

    [CALCITE-7109] Support bitwise leftshifit(<<) operator And Implement 
LEFT_SHIFT function in SQL
---
 core/src/main/codegen/templates/Parser.jj          |   4 +-
 .../calcite/adapter/enumerable/RexImpTable.java    |  13 ++
 .../org/apache/calcite/runtime/SqlFunctions.java   | 142 +++++++++++++
 .../calcite/sql/fun/SqlStdOperatorTable.java       |  29 +++
 .../org/apache/calcite/util/BuiltInMethod.java     |   1 +
 .../apache/calcite/sql/test/SqlAdvisorTest.java    |   1 +
 .../org/apache/calcite/test/SqlFunctionsTest.java  |  25 +++
 .../org/apache/calcite/test/SqlValidatorTest.java  |   1 +
 core/src/test/resources/sql/operator.iq            |  31 +++
 site/_docs/reference.md                            |   1 +
 .../org/apache/calcite/test/SqlOperatorTest.java   | 221 +++++++++++++++++++++
 11 files changed, 468 insertions(+), 1 deletion(-)

diff --git a/core/src/main/codegen/templates/Parser.jj 
b/core/src/main/codegen/templates/Parser.jj
index 1aba2ed176..319a67c90e 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -7968,6 +7968,7 @@ SqlBinaryOperator BinaryRowOperator() :
 {
     // <IN> is handled as a special case
     <EQ> { return SqlStdOperatorTable.EQUALS; }
+|   <LEFTSHIFT> { return SqlStdOperatorTable.BIT_LEFT_SHIFT; }
 |   <GT> { return SqlStdOperatorTable.GREATER_THAN; }
 |   <LT> { return SqlStdOperatorTable.LESS_THAN; }
 |   <LE> { return SqlStdOperatorTable.LESS_THAN_OR_EQUAL; }
@@ -7999,8 +8000,8 @@ SqlBinaryOperator BinaryRowOperator() :
 |   <NOT> <SUBMULTISET> <OF> { return SqlStdOperatorTable.NOT_SUBMULTISET_OF; }
 |   <CONTAINS> { return SqlStdOperatorTable.CONTAINS; }
 |   <OVERLAPS> { return SqlStdOperatorTable.OVERLAPS; }
-|   <EQUALS> { return SqlStdOperatorTable.PERIOD_EQUALS; }
 |   <CARET> { return SqlStdOperatorTable.BITXOR_OPERATOR; }
+|   <EQUALS> { return SqlStdOperatorTable.PERIOD_EQUALS; }
 |   <PRECEDES> { return SqlStdOperatorTable.PRECEDES; }
 |   <SUCCEEDS> { return SqlStdOperatorTable.SUCCEEDS; }
 |   LOOKAHEAD(2) <IMMEDIATELY> <PRECEDES> { return 
SqlStdOperatorTable.IMMEDIATELY_PRECEDES; }
@@ -8991,6 +8992,7 @@ void NonReservedKeyWord2of3() :
 |   < DOUBLE_QUOTE: "\"" >
 |   < VERTICAL_BAR: "|" >
 |   < CARET: "^" >
+|   < LEFTSHIFT: "<<" >
 |   < DOLLAR: "$" >
 <#list (parser.binaryOperatorsTokens!default.parser.binaryOperatorsTokens) as 
operator>
 |   ${operator}
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 fad04050a1..2fdc90e5fb 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
@@ -373,6 +373,7 @@
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BITXOR;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BITXOR_OPERATOR;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BIT_AND;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BIT_LEFT_SHIFT;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BIT_OR;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BIT_XOR;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CARDINALITY;
@@ -458,6 +459,7 @@
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LAST_DAY;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LAST_VALUE;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LEAD;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LEFTSHIFT;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LESS_THAN;
 import static 
org.apache.calcite.sql.fun.SqlStdOperatorTable.LESS_THAN_OR_EQUAL;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LIKE;
@@ -895,6 +897,17 @@ void populate1() {
       defineMethod(TYPEOF, BuiltInMethod.TYPEOF.method, NullPolicy.STRICT);
       defineMethod(VARIANTNULL, BuiltInMethod.VARIANTNULL.method, 
NullPolicy.STRICT);
 
+      // Left shift operations: shift bits to the left by specified amount.
+      // Supports integer, unsigned integer, and binary data types.
+      // Shift amount is normalized using modulo arithmetic based on data type 
bit width.
+
+      // LEFTSHIFT: Function call syntax for bitwise left shift operation 
(e.g., LEFTSHIFT(x, y))
+      defineMethod(LEFTSHIFT, BuiltInMethod.LEFT_SHIFT.method, 
NullPolicy.STRICT);
+
+      // BIT_LEFT_SHIFT: Operator syntax for bitwise left shift in SQL 
expressions
+      // (e.g., x << y)
+      defineMethod(BIT_LEFT_SHIFT, BuiltInMethod.LEFT_SHIFT.method, 
NullPolicy.STRICT);
+
       define(SAFE_ADD,
           new SafeArithmeticImplementor(BuiltInMethod.SAFE_ADD.method));
       define(SAFE_DIVIDE,
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 4912dda4b2..6d94477de8 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -3506,6 +3506,148 @@ private static ByteString binaryOperator(
     return new ByteString(result);
   }
 
+  /**
+   * Performs PostgresSQL-style bitwise shift on a 32-bit integer.
+   *
+   * @param x the integer value to shift
+   * @param y the shift amount (positive: left shift, negative: right shift)
+   * @return the shifted integer
+   */
+  public static int leftShift(int x, int y) {
+    int shift = ((y % 32) + 32) % 32; // normalize to 0~31
+    return y >= 0 ? x << shift : x >> shift; // arithmetic right shift
+  }
+
+
+  // ----------------- long -----------------
+  /**
+   * Performs PostgresSQL-style bitwise shift on a 64-bit long value.
+   *
+   * @param x the long value to shift
+   * @param y the shift amount
+   * @return the shifted long value
+   */
+  public static long leftShift(long x, int y) {
+    int shift = ((y % 64) + 64) % 64; // normalize to 0~63
+    return y >= 0 ? x << shift : x >> shift;
+  }
+
+  /**
+   * Performs PostgresSQL-style bitwise shift on an int value with a long 
shift amount.
+   *
+   * @param x the int value to shift
+   * @param y the long shift amount
+   * @return the shifted value as long
+   */
+  public static long leftShift(int x, long y) {
+    int shift = (int) (((y % 32) + 32) % 32); // normalize to 0~31
+    return y >= 0 ? (long) x << shift : (long) x >> shift;
+  }
+
+  /**
+   * Performs PostgresSQL-style bitwise shift on a byte array.
+   * Positive shift: left shift.
+   * Negative shift: treated as positive shift with modulo arithmetic.
+   *
+   * @param bytes the input byte array
+   * @param y the shift amount in bits
+   * @return the shifted byte array
+   */
+  public static byte[] leftShift(byte[] bytes, int y) {
+    if (bytes.length == 0) {
+      return new byte[0];
+    }
+
+    int bitLen = bytes.length * 8;
+
+    // PostgreSQL behavior: always treat as left shift with modulo arithmetic
+    // Negative y becomes equivalent positive shift
+    int shift = ((y % bitLen) + bitLen) % bitLen;
+
+    if (shift == 0) {
+      return bytes.clone();
+    }
+
+    byte[] result = new byte[bytes.length];
+
+    // Always perform left shift (even for originally negative y)
+    int byteShift = shift / 8;
+    int bitShift = shift % 8;
+
+    for (int i = 0; i < bytes.length; i++) {
+      int srcIndex = i - byteShift;
+      int val = 0;
+
+      // Get the main byte
+      if (srcIndex >= 0) {
+        val = (bytes[srcIndex] & 0xFF) << bitShift;
+      }
+
+      // Get carry bits from previous byte
+      if (srcIndex - 1 >= 0 && bitShift != 0) {
+        val |= (bytes[srcIndex - 1] & 0xFF) >>> (8 - bitShift);
+      }
+
+      result[i] = (byte) val;
+    }
+    return result;
+  }
+
+  /**
+   * Performs PostgresSQL-style bitwise shift on ByteString.
+   *
+   * @param bytes the ByteString to shift
+   * @param y the shift amount in bits
+   * @return shifted ByteString
+   */
+  public static ByteString leftShift(ByteString bytes, int y) {
+    return new ByteString(leftShift(bytes.getBytes(), y));
+  }
+
+  /**
+   * Performs PostgresSQL-style bitwise shift on UByte.
+   * Overflow bits are masked to 8 bits.
+   */
+  public static UByte leftShift(UByte x, int y) {
+    int shift = ((y % 8) + 8) % 8;
+    int val = x.byteValue() & 0xFF;
+    val = (y >= 0) ? (val << shift) & 0xFF : (val >> shift) & 0xFF;
+    return UByte.valueOf((byte) val);
+  }
+
+  /**
+   * Performs PostgresSQL-style bitwise shift on UShort.
+   * Overflow bits are masked to 16 bits.
+   */
+  public static UShort leftShift(UShort x, int y) {
+    int shift = ((y % 16) + 16) % 16;
+    int val = x.shortValue() & 0xFFFF;
+    val = (y >= 0) ? (val << shift) & 0xFFFF : (val >> shift) & 0xFFFF;
+    return UShort.valueOf((short) val);
+  }
+
+  /**
+   * Performs PostgresSQL-style bitwise shift on UInteger.
+   * Overflow bits are masked to 32 bits.
+   */
+  public static UInteger leftShift(UInteger x, int y) {
+    int shift = ((y % 32) + 32) % 32;
+    long val = x.longValue() & 0xFFFFFFFFL;
+    val = (y >= 0) ? (val << shift) & 0xFFFFFFFFL : (val >> shift) & 
0xFFFFFFFFL;
+    return UInteger.valueOf(val);
+  }
+
+  /**
+   * Performs PostgresSQL-style bitwise shift on ULong.
+   * Overflow bits are masked to 64 bits (long shifts naturally truncate).
+   */
+  public static ULong leftShift(ULong x, int y) {
+    int shift = ((y % 64) + 64) % 64;
+    long val = x.longValue();
+    val = (y >= 0) ? val << shift : val >> shift;
+    return ULong.valueOf(val);
+  }
+
   // EXP
 
   /** SQL <code>EXP</code> operator applied to double values. */
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index c4d19e18ad..029e62bf10 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -1339,6 +1339,35 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
   public static final SqlAggFunction BIT_XOR =
       new SqlBitOpAggFunction(SqlKind.BIT_XOR);
 
+  /**
+   * <code>{@code <<}</code> (left shift) operator.
+   */
+  public static final SqlBinaryOperator BIT_LEFT_SHIFT =
+      new SqlBinaryOperator(
+          "<<",
+          SqlKind.OTHER,
+          32,                                     // Standard shift operator 
precedence
+          true,
+          ReturnTypes.ARG0_NULLABLE,
+          InferTypes.FIRST_KNOWN,
+          OperandTypes.or(
+              OperandTypes.family(SqlTypeFamily.INTEGER, 
SqlTypeFamily.INTEGER),
+              OperandTypes.family(SqlTypeFamily.BINARY, SqlTypeFamily.INTEGER),
+              OperandTypes.family(SqlTypeFamily.UNSIGNED_NUMERIC, 
SqlTypeFamily.INTEGER)));
+
+  /**
+   * left shift function.
+   */
+  public static final SqlFunction LEFTSHIFT =
+      SqlBasicFunction.create(
+          "LEFTSHIFT",
+          SqlKind.OTHER_FUNCTION,
+          ReturnTypes.ARG0_NULLABLE,
+          OperandTypes.or(
+              OperandTypes.family(SqlTypeFamily.INTEGER, 
SqlTypeFamily.INTEGER),
+              OperandTypes.family(SqlTypeFamily.BINARY, SqlTypeFamily.INTEGER),
+              OperandTypes.family(SqlTypeFamily.UNSIGNED_NUMERIC, 
SqlTypeFamily.INTEGER)));
+
   //-------------------------------------------------------------
   // WINDOW Aggregate Functions
   //-------------------------------------------------------------
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 bd8ce7f2b7..3477322715 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -677,6 +677,7 @@ public enum BuiltInMethod {
   BIT_OR(SqlFunctions.class, "bitOr", long.class, long.class),
   BIT_XOR(SqlFunctions.class, "bitXor", long.class, long.class),
   BIT_NOT(SqlFunctions.class, "bitNot", long.class),
+  LEFT_SHIFT(SqlFunctions.class, "leftShift", int.class, int.class),
   MODIFIABLE_TABLE_GET_MODIFIABLE_COLLECTION(ModifiableTable.class,
       "getModifiableCollection"),
   SCANNABLE_TABLE_SCAN(ScannableTable.class, "scan", DataContext.class),
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java 
b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 28e0420b1e..b1385d01cb 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -309,6 +309,7 @@ class SqlAdvisorTest extends SqlValidatorTestCase {
           "KEYWORD(%)",
           "KEYWORD(<)",
           "KEYWORD(<=)",
+          "KEYWORD(<<)",
           "KEYWORD(<>)",
           "KEYWORD(!=)",
           "KEYWORD(=)",
diff --git a/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
index d1f8cb9322..043c23b105 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
@@ -86,6 +86,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.closeTo;
 import static org.hamcrest.Matchers.hasToString;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -2032,4 +2034,27 @@ private int sqlTime(String str) {
   private long sqlTimestamp(String str) {
     return toLong(java.sql.Timestamp.valueOf(str));
   }
+  @Test void testLeftShift() {
+    // Test 1-byte array
+    byte[] data1 = {(byte) 0x0F}; // 00001111
+    for (int shift = -10; shift <= 10; shift++) {
+      byte[] result = SqlFunctions.leftShift(data1.clone(), shift);
+      // Just verify it doesn't crash and returns correct length
+      assertEquals(1, result.length);
+    }
+
+    // Test 2-byte array
+    byte[] data2 = {(byte) 0x12, (byte) 0x34};
+    for (int shift = -18; shift <= 18; shift++) {
+      byte[] result = SqlFunctions.leftShift(data2.clone(), shift);
+      assertEquals(2, result.length);
+    }
+
+    // Verify specific known cases
+    assertArrayEquals(new byte[]{(byte) 0xAB, (byte) 0xCD},
+        SqlFunctions.leftShift(new byte[]{(byte) 0xAB, (byte) 0xCD}, 0));
+
+    assertArrayEquals(new byte[]{(byte) 0x80, (byte) 0x00},
+        SqlFunctions.leftShift(new byte[]{(byte) 0x40, (byte) 0x00}, 1));
+  }
 }
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 61c39e6416..2d785caf07 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -10036,6 +10036,7 @@ private static int prec(SqlOperator op) {
         + "\n"
         + "< ALL left\n"
         + "< SOME left\n"
+        + "<< left\n"
         + "<= ALL left\n"
         + "<= SOME left\n"
         + "<> ALL left\n"
diff --git a/core/src/test/resources/sql/operator.iq 
b/core/src/test/resources/sql/operator.iq
index ffdcdd2bbd..c0c9d0b422 100644
--- a/core/src/test/resources/sql/operator.iq
+++ b/core/src/test/resources/sql/operator.iq
@@ -106,6 +106,21 @@ SELECT CAST(empno AS BIGINT) ^ CAST(mgr AS INTEGER) AS foo 
FROM "scott".emp WHER
 
 !ok
 
+# [CALCITE-7109] Add support for << operator in Calcite
+SELECT CAST(deptno AS INTEGER) << CAST(comm AS INTEGER) AS foo FROM "scott".emp
+WHERE comm IS NOT NULL LIMIT 4;
++-----------+
+| FOO       |
++-----------+
+|    122880 |
+|        30 |
+|  31457280 |
+| 503316480 |
++-----------+
+(4 rows)
+
+!ok
+
 # [CALCITE-5531] COALESCE throws ClassCastException
 SELECT COALESCE(DATE '2021-07-08', DATE '2020-01-01') as d;
 +------------+
@@ -782,4 +797,20 @@ SELECT 255 ^ 128 AS foo;
 
 !ok
 
+-- Bitwise LEFT SHIFT operator `<<`
+SELECT
+  1 << 0 AS shift1,
+  1 << 1 AS shift2,
+  2 << 2 AS shift3,
+  4 << 3 AS shift4,
+  CAST(2 AS SMALLINT) << CAST(3 AS INTEGER) AS cast_shift;
++--------+--------+--------+--------+------------+
+| SHIFT1 | SHIFT2 | SHIFT3 | SHIFT4 | CAST_SHIFT |
++--------+--------+--------+--------+------------+
+|      1 |      2 |      8 |     32 |         16 |
++--------+--------+--------+--------+------------+
+(1 row)
+
+!ok
+
 # End operator.iq
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 7e4f0fa807..b07ba0c8fa 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2828,6 +2828,7 @@ ### Dialect-specific Operators
 | * | BITAND(value1, value2)                         | Returns the bitwise AND 
of *value1* and *value2*. *value1* and *value2* must both be integer or binary 
values. Binary values must be of the same length.
 | * | BITOR(value1, value2)                          | Returns the bitwise OR 
of *value1* and *value2*. *value1* and *value2* must both be integer or binary 
values. Binary values must be of the same length.
 | * | BITXOR(value1, value2)                         | Returns the bitwise XOR 
of *value1* and *value2*. *value1* and *value2* must both be integer or binary 
values. Binary values must be of the same length.
+| * | LEFTSHIFT(value1, value2) | Returns the result of left-shifting *value1* 
by *value2* bits. *value1* can be integer, unsigned integer, or binary. For 
binary, the result has the same length as *value1*. The shift amount *value2* 
is normalized using modulo arithmetic based on the bit width of *value1*. For 
integers, this uses modulo 32; for binary types, it uses modulo (8 × 
byte_length). Negative shift amounts are converted to equivalent positive 
shifts through this modulo operation.  [...]
 | * | BITNOT(value)                                  | Returns the bitwise NOT 
of *value*. *value* must be either an integer type or a binary value.
 | f | BITAND_AGG(value)                              | Equivalent to 
`BIT_AND(value)`
 | f | BITOR_AGG(value)                               | Equivalent to 
`BIT_OR(value)`
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 8e6f754661..7f057c888a 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -100,6 +100,7 @@
 
 import java.lang.reflect.Field;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -16519,6 +16520,226 @@ private static void 
checkLogicalOrFunc(SqlOperatorFixture f) {
     f.checkNull("CAST(NULL AS INTEGER UNSIGNED) ^^ CAST(NULL AS INTEGER 
UNSIGNED)");
   }
 
+  /**
+   * Test cases for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7109";>[CALCITE-7109]
+   * Implement SHIFT_LEFT operator </a>.
+   */
+  @Test void testLeftShiftScalarFunc() {
+    final SqlOperatorFixture f = fixture();
+    f.setFor(SqlStdOperatorTable.BIT_LEFT_SHIFT, VmName.EXPAND);
+
+    // === Basic functionality ===
+    f.checkScalar("2 << 2", "8", "INTEGER NOT NULL");
+    f.checkScalar("1 << 10", "1024", "INTEGER NOT NULL");
+    f.checkScalar("0 << 5", "0", "INTEGER NOT NULL");
+
+    // === Type coercion and signed behavior ===
+    f.checkScalar("CAST(2 AS INTEGER) << CAST(3 AS BIGINT)", "16", "INTEGER 
NOT NULL");
+    f.checkScalar("-5 << 2", "-20", "INTEGER NOT NULL");
+    f.checkScalar("-5 << 3", "-40", "INTEGER NOT NULL");
+    f.checkScalar("CAST(-5 AS TINYINT) << CAST(2 AS TINYINT)", "-20", "TINYINT 
NOT NULL");
+
+    // === Verify return type matches first argument type ===
+    f.checkType("CAST(2 AS TINYINT) << CAST(3 AS TINYINT)", "TINYINT NOT 
NULL");
+    f.checkType("CAST(2 AS SMALLINT) << CAST(3 AS SMALLINT)", "SMALLINT NOT 
NULL");
+    f.checkType("CAST(2 AS INTEGER) << CAST(3 AS INTEGER)", "INTEGER NOT 
NULL");
+    f.checkType("CAST(2 AS BIGINT) << CAST(3 AS BIGINT)", "BIGINT NOT NULL");
+
+    // === BigInt shifts with explicit BIGINT inputs ===
+    f.checkScalar("CAST(1 AS BIGINT) << 62", 
BigInteger.ONE.shiftLeft(62).toString(),
+        "BIGINT NOT NULL"); // 2^62
+    f.checkScalar("CAST(1 AS BIGINT) << 63",
+        
BigInteger.ONE.shiftLeft(63).multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL"); // overflow
+    f.checkScalar("CAST(4611686018427387904 AS BIGINT) << 1",
+        BigInteger.valueOf(4611686018427387904L).shiftLeft(1).
+            multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL");
+    f.checkScalar("CAST(2305843009213693952 AS BIGINT) << 2",
+        BigInteger.valueOf(2305843009213693952L).shiftLeft(2).
+            multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL");
+    f.checkScalar("CAST(-4611686018427387904 AS BIGINT) << 1",
+        BigInteger.valueOf(-4611686018427387904L).shiftLeft(1).toString(), 
"BIGINT NOT NULL");
+    f.checkScalar("CAST(-1 AS BIGINT) << 63",
+        
BigInteger.ONE.shiftLeft(63).multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL");
+    f.checkScalar("CAST(9223372036854775807 AS BIGINT) << 0",
+        BigInteger.valueOf(Long.MAX_VALUE).shiftLeft(0).toString(), "BIGINT 
NOT NULL");
+    f.checkScalar("CAST(1000000000 AS BIGINT) << 35",
+        "-2533749779419103232", "BIGINT NOT NULL");
+    f.checkScalar("CAST(9223372036854775807 AS BIGINT) << 1",
+        "-2", "BIGINT NOT NULL");
+
+    // === Java shift semantics: bits masked to 5/6 bits ===
+    f.checkScalar("CAST(1 AS BIGINT) << 32",
+        BigInteger.ONE.shiftLeft(32).toString(), "BIGINT NOT NULL");
+    f.checkScalar("CAST(1 AS BIGINT) << 50",
+        BigInteger.ONE.shiftLeft(50).toString(), "BIGINT NOT NULL");
+    f.checkScalar("CAST(1 AS BIGINT) << 100",
+        BigInteger.ONE.shiftLeft(100 & 63).toString(), "BIGINT NOT NULL");
+    f.checkScalar("CAST(100 AS BIGINT) << 50",
+        BigInteger.valueOf(100L).shiftLeft(50).toString(), "BIGINT NOT NULL");
+
+    f.checkScalar("CAST(100 AS BIGINT) << 50",
+        BigInteger.valueOf(100L).shiftLeft(50).toString(), "BIGINT NOT NULL");
+
+    // === Unsigned types ===
+    f.checkScalar("CAST(63 AS TINYINT UNSIGNED) << 2", "252", "TINYINT 
UNSIGNED NOT NULL");
+    f.checkScalar("CAST(255 AS SMALLINT UNSIGNED) << 8", "65280", "SMALLINT 
UNSIGNED NOT NULL");
+    f.checkScalar("CAST(65535 AS INTEGER UNSIGNED) << 16", "4294901760",
+        "INTEGER UNSIGNED NOT NULL");
+    f.checkScalar("CAST(1 AS INTEGER UNSIGNED) << 31", "2147483648", "INTEGER 
UNSIGNED NOT NULL");
+    f.checkScalar("CAST(1 AS INTEGER UNSIGNED) << -1", "0", "INTEGER UNSIGNED 
NOT NULL");
+
+    // === Negative shift counts ===
+    f.checkScalar("8 << -1", "0", "INTEGER NOT NULL");
+    f.checkScalar("16 << -2", "0", "INTEGER NOT NULL");
+
+    // === Shift by zero and large shifts ===
+    f.checkScalar("0 << 32", "0", "INTEGER NOT NULL");
+    f.checkScalar("0 << 100", "0", "INTEGER NOT NULL");
+
+    // === Non-zero values with large shifts ===
+    f.checkScalar("1 << 32", "1", "INTEGER NOT NULL");
+    f.checkScalar("1 << 40", "256", "INTEGER NOT NULL");
+    f.checkScalar("2 << 50", "524288", "INTEGER NOT NULL");
+    f.checkScalar("123 << 60", "-1342177280", "INTEGER NOT NULL");
+
+    // === Binary type tests ===
+    f.checkScalar("CAST(X'FF' AS BINARY(1)) << 1", "fe", "BINARY(1) NOT NULL");
+    f.checkScalar("CAST(X'0F' AS BINARY(1)) << 4", "f0", "BINARY(1) NOT NULL");
+    f.checkScalar("CAST(X'01' AS BINARY(1)) << 3", "08", "BINARY(1) NOT NULL");
+    f.checkScalar("CAST(X'00' AS BINARY(1)) << 5", "00", "BINARY(1) NOT NULL");
+
+    f.checkScalar("CAST(X'FFFF' AS BINARY(2)) << 1", "feff", "BINARY(2) NOT 
NULL");
+    f.checkScalar("CAST(X'1234' AS BINARY(2)) << 4", "2041", "BINARY(2) NOT 
NULL");
+    f.checkScalar("CAST(X'1234' AS BINARY(2)) << 8", "0012", "BINARY(2) NOT 
NULL");
+
+    f.checkScalar("CAST(X'FF' AS BINARY(1)) << 8", "ff", "BINARY(1) NOT NULL");
+    f.checkScalar("CAST(X'FFFF' AS BINARY(2)) << 16", "ffff", "BINARY(2) NOT 
NULL");
+
+    f.checkScalar("CAST(X'ABCD' AS BINARY(2)) << 0", "abcd", "BINARY(2) NOT 
NULL");
+    f.checkScalar("CAST(X'123456' AS BINARY(3)) << 4", "204163", "BINARY(3) 
NOT NULL");
+    f.checkScalar("CAST(X'8000' AS BINARY(2)) << 1", "0001", "BINARY(2) NOT 
NULL");
+    f.checkScalar("CAST(X'4000' AS BINARY(2)) << 1", "8000", "BINARY(2) NOT 
NULL");
+    f.checkScalar("CAST(X'0F' AS BINARY(1)) << -4", "f0", "BINARY(1) NOT 
NULL");
+
+    // === Invalid argument types ===
+    f.checkFails("^1.2 << 2^",
+        "Cannot apply '<<' to arguments of type '<DECIMAL\\(2, 1\\)> << 
<INTEGER>'\\. Supported "
+            + "form\\(s\\): '<INTEGER> << <INTEGER>'\\n'<BINARY> << 
<INTEGER>'\\n'<UNSIGNED_NUMERIC> "
+            + "<< <INTEGER>'",
+        false);
+
+    // === Null propagation ===
+    f.checkNull("CAST(NULL AS INTEGER) << 5");
+    f.checkNull("10 << CAST(NULL AS INTEGER)");
+    f.checkNull("CAST(NULL AS INTEGER) << CAST(NULL AS INTEGER)");
+    f.checkNull("CAST(NULL AS INTEGER UNSIGNED) << 2");
+  }
+
+  @Test void testLeftShiftFunctionCall() {
+    final SqlOperatorFixture f = fixture();
+    f.setFor(SqlStdOperatorTable.BIT_LEFT_SHIFT, VmName.EXPAND);
+
+    // === Basic functionality ===
+    f.checkScalar("LEFTSHIFT(2, 2)", "8", "INTEGER NOT NULL");
+    f.checkScalar("LEFTSHIFT(1, 10)", "1024", "INTEGER NOT NULL");
+    f.checkScalar("LEFTSHIFT(0, 5)", "0", "INTEGER NOT NULL");
+
+    // === Type coercion and signed behavior ===
+    f.checkScalar("LEFTSHIFT(CAST(2 AS INTEGER), CAST(3 AS BIGINT))", "16", 
"INTEGER NOT NULL");
+    f.checkScalar("LEFTSHIFT(-5, 2)", "-20", "INTEGER NOT NULL");
+    f.checkScalar("LEFTSHIFT(-5, 3)", "-40", "INTEGER NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(-5 AS TINYINT), CAST(2 AS TINYINT))", "-20", 
"TINYINT NOT NULL");
+
+    // === Verify return type matches first argument type ===
+    f.checkType("LEFTSHIFT(CAST(2 AS TINYINT), CAST(3 AS TINYINT))", "TINYINT 
NOT NULL");
+    f.checkType("LEFTSHIFT(CAST(2 AS SMALLINT), CAST(3 AS SMALLINT))", 
"SMALLINT NOT NULL");
+    f.checkType("LEFTSHIFT(CAST(2 AS INTEGER), CAST(3 AS INTEGER))", "INTEGER 
NOT NULL");
+    f.checkType("LEFTSHIFT(CAST(2 AS BIGINT), CAST(3 AS BIGINT))", "BIGINT NOT 
NULL");
+
+    // === BigInt shifts with explicit BIGINT inputs ===
+    f.checkScalar("LEFTSHIFT(CAST(1 AS BIGINT), 62)",
+        "4611686018427387904", "BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(1 AS BIGINT), 63)",
+        
BigInteger.ONE.shiftLeft(63).multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(4611686018427387904 AS BIGINT), 1)",
+        
BigInteger.ONE.shiftLeft(63).multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(2305843009213693952 AS BIGINT), 2)",
+        
BigInteger.ONE.shiftLeft(63).multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(-4611686018427387904 AS BIGINT), 1)",
+        
BigInteger.ONE.shiftLeft(63).multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(-1 AS BIGINT), 63)",
+        
BigInteger.ONE.shiftLeft(63).multiply(BigInteger.valueOf(-1)).toString(),
+        "BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(9223372036854775807 AS BIGINT), 0)",
+        BigInteger.ONE.shiftLeft(63).add(BigInteger.valueOf(-1)).toString(), 
"BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(1000000000 AS BIGINT), 35)",
+        "-2533749779419103232", "BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(9223372036854775807 AS BIGINT), 1)",
+        "-2", "BIGINT NOT NULL");
+
+    // === Java shift semantics: bits masked to 5/6 bits ===
+    f.checkScalar("LEFTSHIFT(CAST(1 AS BIGINT), 32)", "4294967296", "BIGINT 
NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(1 AS BIGINT), 50)", "1125899906842624", 
"BIGINT NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(1 AS BIGINT), 100)", "68719476736", "BIGINT 
NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(100 AS BIGINT), 50)", "112589990684262400", 
"BIGINT NOT NULL");
+
+    // === Unsigned types ===
+    f.checkScalar("LEFTSHIFT(CAST(63 AS TINYINT UNSIGNED), 2)", "252", 
"TINYINT UNSIGNED NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(255 AS SMALLINT UNSIGNED), 8)", "65280",
+        "SMALLINT UNSIGNED NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(65535 AS INTEGER UNSIGNED), 16)", 
"4294901760",
+        "INTEGER UNSIGNED NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(1 AS INTEGER UNSIGNED), 31)", "2147483648",
+        "INTEGER UNSIGNED NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(1 AS INTEGER UNSIGNED), -1)", "0",
+        "INTEGER UNSIGNED NOT NULL");
+
+    // === Negative shifts ===
+    f.checkScalar("LEFTSHIFT(8, -1)", "0", "INTEGER NOT NULL");
+    f.checkScalar("LEFTSHIFT(16, -2)", "0", "INTEGER NOT NULL");
+
+    // === Large shifts ===
+    f.checkScalar("LEFTSHIFT(0, 32)", "0", "INTEGER NOT NULL");
+    f.checkScalar("LEFTSHIFT(0, 100)", "0", "INTEGER NOT NULL");
+
+    // === Binary types ===
+    f.checkScalar("LEFTSHIFT(CAST(X'FF' AS BINARY(1)), 1)", "fe", "BINARY(1) 
NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'0F' AS BINARY(1)), 4)", "f0", "BINARY(1) 
NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'01' AS BINARY(1)), 3)", "08", "BINARY(1) 
NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'00' AS BINARY(1)), 5)", "00", "BINARY(1) 
NOT NULL");
+
+    f.checkScalar("LEFTSHIFT(CAST(X'FFFF' AS BINARY(2)), 1)", "feff", 
"BINARY(2) NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'1234' AS BINARY(2)), 4)", "2041", 
"BINARY(2) NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'1234' AS BINARY(2)), 8)", "0012", 
"BINARY(2) NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'FF' AS BINARY(1)), 8)", "ff", "BINARY(1) 
NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'FFFF' AS BINARY(2)), 16)", "ffff", 
"BINARY(2) NOT NULL");
+
+    f.checkScalar("LEFTSHIFT(CAST(X'ABCD' AS BINARY(2)), 0)", "abcd", 
"BINARY(2) NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'123456' AS BINARY(3)), 4)", "204163", 
"BINARY(3) NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'8000' AS BINARY(2)), 1)", "0001", 
"BINARY(2) NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'4000' AS BINARY(2)), 1)", "8000", 
"BINARY(2) NOT NULL");
+    f.checkScalar("LEFTSHIFT(CAST(X'0F' AS BINARY(1)), -4)", "f0", "BINARY(1) 
NOT NULL");
+    // === Invalid types ===
+    f.checkFails("^LEFTSHIFT(1.2, 2)^",
+        "Cannot apply 'LEFTSHIFT' to arguments of type 
'LEFTSHIFT\\(<DECIMAL\\(2, 1\\)>, <INTEGER>\\)'\\. Supported form\\(s\\): 
'LEFTSHIFT\\(<INTEGER>, <INTEGER>\\)'\\n'LEFTSHIFT\\(<BINARY>, 
<INTEGER>\\)'\\n'LEFTSHIFT\\(<UNSIGNED_NUMERIC>, <INTEGER>\\)'",
+        false);
+
+
+    // === Nulls ===
+    f.checkNull("LEFTSHIFT(CAST(NULL AS INTEGER), 5)");
+    f.checkNull("LEFTSHIFT(10, CAST(NULL AS INTEGER))");
+    f.checkNull("LEFTSHIFT(CAST(NULL AS INTEGER), CAST(NULL AS INTEGER))");
+    f.checkNull("LEFTSHIFT(CAST(NULL AS INTEGER UNSIGNED), 2)");
+  }
 
   @Test void testBitAndScalarFunc() {
     final SqlOperatorFixture f = fixture();

Reply via email to