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();