This is an automated email from the ASF dual-hosted git repository.
mihaibudiu 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 cb24d30828 [CALCITE-7639] Support bitwise right shift (>>) operator
and RIGHTSHIFT function
cb24d30828 is described below
commit cb24d308280783c06750b6e8f3e971d4fca2fd80
Author: Niels Pardon <[email protected]>
AuthorDate: Fri Jul 24 07:59:07 2026 +0200
[CALCITE-7639] Support bitwise right shift (>>) operator and RIGHTSHIFT
function
Mirrors the left-shift work in CALCITE-7109 to close a gap in the umbrella
issue CALCITE-5087. Adds:
* `>>` (SqlStdOperatorTable.BIT_RIGHT_SHIFT), a signed/arithmetic right
shift (Java `>>`), symmetric to `<<` (precedence 32, left-assoc,
ReturnTypes.ARG0_NULLABLE, InferTypes.FIRST_KNOWN).
* `RIGHTSHIFT(x, n)` scalar function, mirroring `LEFTSHIFT`.
* SqlFunctions.rightShift(...) runtime overloads (int, long, and the joou
unsigned types), plus BuiltInMethod.RIGHT_SHIFT and the RexImpTable
registrations for both operator and function.
Operands are limited to integer and unsigned numeric types. Unlike `<<`,
binary (BINARY/VARBINARY) right shift is intentionally rejected at
validation until the endianness of bitwise shifts on binary is settled;
that follow-up is tracked in CALCITE-7651.
A greedy `>>` token cannot be added: the lexer would also match the two `>`
that close nested angle-bracket types (e.g. MAP<INT, MAP<INT, INT>>),
breaking type parsing. `>>` is therefore recognized in expression context as
two adjacent `>` tokens via LOOKAHEAD(2) in BinaryRowOperator, leaving type
parsing unchanged. Because there is no dedicated token, the SQL advisor
advertises `>` rather than `>>`, so SqlAdvisorTest is not modified.
Scope is limited to `>>` (arithmetic). The logical/fill-zero `>>>`
(RIGHT_SHIFT_FILL_ZERO) remains a possible follow-up.
Tests: SqlOperatorTest (operator + function forms), SqlFunctionsTest,
operator.iq, and the operator-precedence dump in SqlValidatorTest; docs in
site/_docs/reference.md.
---
core/src/main/codegen/templates/Parser.jj | 29 +++
.../calcite/adapter/enumerable/RexImpTable.java | 14 ++
.../org/apache/calcite/runtime/SqlFunctions.java | 135 +++++++++++---
.../calcite/sql/fun/SqlStdOperatorTable.java | 57 +++++-
.../apache/calcite/sql/parser/SqlParserPos.java | 14 ++
.../org/apache/calcite/util/BuiltInMethod.java | 3 +-
.../calcite/sql/parser/SqlParserPosTest.java | 57 ++++++
.../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 | 3 +-
.../apache/calcite/sql/parser/SqlParserTest.java | 19 ++
.../org/apache/calcite/test/SqlOperatorTest.java | 197 +++++++++++++++++++++
13 files changed, 544 insertions(+), 41 deletions(-)
diff --git a/core/src/main/codegen/templates/Parser.jj
b/core/src/main/codegen/templates/Parser.jj
index d3c1159741..ce69124c4b 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -279,6 +279,24 @@ public class ${parser.class} extends SqlAbstractParserImpl
Span.of(table, extendList).pos(), table, extendList);
}
+ /**
+ * Returns the parser position of the given token.
+ *
+ * <p>Declared as a plain method (rather than JAVACODE) so it can be called
+ * from generated lookahead code, which does not declare
+ * {@code throws ParseException}.
+ *
+ * @param token token whose position to return
+ * @return parser position spanning the given token
+ */
+ private static SqlParserPos pos(Token token) {
+ return new SqlParserPos(
+ token.beginLine,
+ token.beginColumn,
+ token.endLine,
+ token.endColumn);
+ }
+
/** Adds a warning that a token such as "HOURS" was used,
* whereas the SQL standard only allows "HOUR".
*
@@ -8487,6 +8505,17 @@ SqlBinaryOperator BinaryRowOperator() :
// <IN> is handled as a special case
<EQ> { return SqlStdOperatorTable.EQUALS; }
| <LEFTSHIFT> { return SqlStdOperatorTable.BIT_LEFT_SHIFT; }
+ // The right shift operator ">>" is matched as two ">" tokens rather than a
+ // single ">>" token. A greedy ">>" token would be produced by the lexer
even
+ // when the two ">" characters close nested angle-bracket types (e.g.
+ // MAP<INT, MAP<INT, INT>>), breaking type parsing. Matching two ">"
tokens here
+ // keeps right shift confined to expression context. The semantic lookahead
+ // requires the two ">" to be immediately adjacent (no intervening
whitespace),
+ // so "a > > b" is not treated as a right shift; otherwise we fall through
to
+ // the single ">" (greater-than) alternative below.
+| LOOKAHEAD({ getToken(1).kind == GT && getToken(2).kind == GT
+ && pos(getToken(1)).endsImmediatelyBefore(pos(getToken(2))) })
+ <GT> <GT> { return SqlStdOperatorTable.BIT_RIGHT_SHIFT; }
| <GT> { return SqlStdOperatorTable.GREATER_THAN; }
| <LT> { return SqlStdOperatorTable.LESS_THAN; }
| <LE> { return SqlStdOperatorTable.LESS_THAN_OR_EQUAL; }
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 bf5de12e74..2ad2be2893 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
@@ -377,6 +377,7 @@
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_RIGHT_SHIFT;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BIT_XOR;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CARDINALITY;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CAST;
@@ -514,6 +515,7 @@
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.REGR_COUNT;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.REINTERPRET;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.REPLACE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.RIGHTSHIFT;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ROUND;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ROW;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ROW_NUMBER;
@@ -922,6 +924,18 @@ void populate1() {
// (e.g., x << y)
defineMethod(BIT_LEFT_SHIFT, BuiltInMethod.LEFT_SHIFT.method,
NullPolicy.STRICT);
+ // Right shift operations: shift bits to the right by specified amount.
+ // Supports integer and unsigned integer data types. Binary right shift
is
+ // intentionally not supported; see [CALCITE-7651].
+ // Shift amount is normalized using modulo arithmetic based on data type
bit width.
+
+ // RIGHTSHIFT: Function call syntax for bitwise right shift operation
(e.g., RIGHTSHIFT(x, y))
+ defineMethod(RIGHTSHIFT, BuiltInMethod.RIGHT_SHIFT.method,
NullPolicy.STRICT);
+
+ // BIT_RIGHT_SHIFT: Operator syntax for bitwise right shift in SQL
expressions
+ // (e.g., x >> y)
+ defineMethod(BIT_RIGHT_SHIFT, BuiltInMethod.RIGHT_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 c41ab84d71..fd4101b5a4 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -3789,6 +3789,21 @@ private static ByteString binaryOperator(
return new ByteString(result);
}
+ /**
+ * Returns {@code x} modulo {@code m}, normalized to the range {@code [0, m)}
+ * (unlike {@code %}, the result is never negative). Used to normalize a
shift
+ * amount to the bit width of the value being shifted.
+ *
+ * @param x the value (typically a shift amount, which may be negative)
+ * @param m the modulus, which must be positive (typically a bit width)
+ * @return {@code x} modulo {@code m}, in the range {@code [0, m)}
+ */
+ private static int positiveModulo(long x, int m) {
+ // Math.floorMod(long, int) is only available since JDK 9, so widen to
+ // Math.floorMod(long, long) and narrow the result (always in [0, m)) to
int.
+ return (int) Math.floorMod(x, (long) m);
+ }
+
/**
* Performs PostgresSQL-style bitwise shift on a 32-bit integer.
*
@@ -3796,8 +3811,8 @@ private static ByteString binaryOperator(
* @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
+ public static int leftShift(int x, long y) {
+ int shift = positiveModulo(y, 32); // normalize to 0~31
return y >= 0 ? x << shift : x >> shift; // arithmetic right shift
}
@@ -3810,23 +3825,11 @@ public static int leftShift(int x, int y) {
* @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
+ public static long leftShift(long x, long y) {
+ int shift = positiveModulo(y, 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.
@@ -3836,7 +3839,7 @@ public static long leftShift(int x, long y) {
* @param y the shift amount in bits
* @return the shifted byte array
*/
- public static byte[] leftShift(byte[] bytes, int y) {
+ public static byte[] leftShift(byte[] bytes, long y) {
if (bytes.length == 0) {
return new byte[0];
}
@@ -3845,7 +3848,7 @@ public static byte[] leftShift(byte[] bytes, int y) {
// PostgreSQL behavior: always treat as left shift with modulo arithmetic
// Negative y becomes equivalent positive shift
- int shift = ((y % bitLen) + bitLen) % bitLen;
+ int shift = positiveModulo(y, bitLen);
if (shift == 0) {
return bytes.clone();
@@ -3883,7 +3886,7 @@ public static byte[] leftShift(byte[] bytes, int y) {
* @param y the shift amount in bits
* @return shifted ByteString
*/
- public static ByteString leftShift(ByteString bytes, int y) {
+ public static ByteString leftShift(ByteString bytes, long y) {
return new ByteString(leftShift(bytes.getBytes(), y));
}
@@ -3891,8 +3894,8 @@ public static ByteString leftShift(ByteString bytes, int
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;
+ public static UByte leftShift(UByte x, long y) {
+ int shift = positiveModulo(y, 8);
int val = x.byteValue() & 0xFF;
val = (y >= 0) ? (val << shift) & 0xFF : (val >> shift) & 0xFF;
return UByte.valueOf((byte) val);
@@ -3902,8 +3905,8 @@ public static UByte leftShift(UByte x, int y) {
* 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;
+ public static UShort leftShift(UShort x, long y) {
+ int shift = positiveModulo(y, 16);
int val = x.shortValue() & 0xFFFF;
val = (y >= 0) ? (val << shift) & 0xFFFF : (val >> shift) & 0xFFFF;
return UShort.valueOf((short) val);
@@ -3913,8 +3916,8 @@ public static UShort leftShift(UShort x, int y) {
* 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;
+ public static UInteger leftShift(UInteger x, long y) {
+ int shift = positiveModulo(y, 32);
long val = x.longValue() & 0xFFFFFFFFL;
val = (y >= 0) ? (val << shift) & 0xFFFFFFFFL : (val >> shift) &
0xFFFFFFFFL;
return UInteger.valueOf(val);
@@ -3924,10 +3927,86 @@ public static UInteger leftShift(UInteger x, int y) {
* 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;
+ public static ULong leftShift(ULong x, long y) {
+ int shift = positiveModulo(y, 64);
+ long val = x.longValue();
+ // A negative shift amount shifts right; use a logical (unsigned) shift so
+ // the full-width ULong value is not sign-extended.
+ val = (y >= 0) ? val << shift : val >>> shift;
+ return ULong.valueOf(val);
+ }
+
+ /**
+ * Performs PostgresSQL-style bitwise shift on a 32-bit integer.
+ *
+ * @param x the integer value to shift
+ * @param y the shift amount (positive: right shift, negative: left shift)
+ * @return the shifted integer
+ */
+ public static int rightShift(int x, long y) {
+ int shift = positiveModulo(y, 32); // normalize to 0~31
+ return y >= 0 ? x >> shift : x << shift; // arithmetic right shift
+ }
+
+ /**
+ * 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 rightShift(long x, long y) {
+ int shift = positiveModulo(y, 64); // normalize to 0~63
+ return y >= 0 ? x >> shift : x << shift;
+ }
+
+ // Right shift on binary (byte[]/ByteString) is intentionally not
implemented:
+ // BINARY/VARBINARY operands are rejected for >> and RIGHTSHIFT until the
+ // endianness of bitwise shifts on binary is settled. See [CALCITE-7651].
+
+ /**
+ * Performs PostgresSQL-style bitwise shift on UByte.
+ * Overflow bits are masked to 8 bits.
+ */
+ public static UByte rightShift(UByte x, long y) {
+ int shift = positiveModulo(y, 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 rightShift(UShort x, long y) {
+ int shift = positiveModulo(y, 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 rightShift(UInteger x, long y) {
+ int shift = positiveModulo(y, 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 rightShift(ULong x, long y) {
+ int shift = positiveModulo(y, 64);
long val = x.longValue();
- val = (y >= 0) ? val << shift : val >> shift;
+ // Use a logical (unsigned) right shift: ULong holds the full 64 bits, so
+ // the raw long may be negative and an arithmetic '>>' would sign-extend.
+ val = (y >= 0) ? val >>> shift : val << shift;
return ULong.valueOf(val);
}
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 b1f0c0d504..20a5e690ff 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
@@ -64,6 +64,7 @@
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlOperandCountRanges;
+import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlReturnTypeInference;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
@@ -1367,6 +1368,29 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
public static final SqlAggFunction BIT_XOR =
new SqlBitOpAggFunction(SqlKind.BIT_XOR);
+ /**
+ * Operand type checker shared by the left shift operator ({@code <<}) and
its
+ * function form ({@code LEFTSHIFT}). The first operand is the value being
+ * shifted (integer, binary or unsigned numeric) and the second is the
integer
+ * shift amount.
+ */
+ private static final SqlOperandTypeChecker SHIFT_OPERAND_TYPE_CHECKER =
+ OperandTypes.INTEGER_INTEGER
+ .or(OperandTypes.family(SqlTypeFamily.BINARY, SqlTypeFamily.INTEGER))
+ .or(OperandTypes.family(SqlTypeFamily.UNSIGNED_NUMERIC,
SqlTypeFamily.INTEGER));
+
+ /**
+ * Operand type checker for the right shift operator ({@code >>}) and its
+ * function form ({@code RIGHTSHIFT}). Like {@link
#SHIFT_OPERAND_TYPE_CHECKER}
+ * but the value being shifted may only be integer or unsigned numeric, not
+ * binary. Binary right shift is intentionally excluded until the endianness
of
+ * bitwise shifts on {@code BINARY}/{@code VARBINARY} is settled; see
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7651">[CALCITE-7651]</a>.
+ */
+ private static final SqlOperandTypeChecker
NON_BINARY_SHIFT_OPERAND_TYPE_CHECKER =
+ OperandTypes.INTEGER_INTEGER
+ .or(OperandTypes.family(SqlTypeFamily.UNSIGNED_NUMERIC,
SqlTypeFamily.INTEGER));
+
/**
* <code>{@code <<}</code> (left shift) operator.
*/
@@ -1378,10 +1402,7 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
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)));
+ SHIFT_OPERAND_TYPE_CHECKER);
/**
* left shift function.
@@ -1391,10 +1412,30 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
"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)));
+ SHIFT_OPERAND_TYPE_CHECKER);
+
+ /**
+ * <code>{@code >>}</code> (right shift) operator.
+ */
+ public static final SqlBinaryOperator BIT_RIGHT_SHIFT =
+ new SqlBinaryOperator(
+ ">>",
+ SqlKind.OTHER,
+ 32, // Standard shift operator
precedence
+ true,
+ ReturnTypes.ARG0_NULLABLE,
+ InferTypes.FIRST_KNOWN,
+ NON_BINARY_SHIFT_OPERAND_TYPE_CHECKER);
+
+ /**
+ * right shift function.
+ */
+ public static final SqlFunction RIGHTSHIFT =
+ SqlBasicFunction.create(
+ "RIGHTSHIFT",
+ SqlKind.OTHER_FUNCTION,
+ ReturnTypes.ARG0_NULLABLE,
+ NON_BINARY_SHIFT_OPERAND_TYPE_CHECKER);
//-------------------------------------------------------------
// WINDOW Aggregate Functions
diff --git a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserPos.java
b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserPos.java
index 97fb72cac0..ed74719ab3 100644
--- a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserPos.java
+++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserPos.java
@@ -264,6 +264,20 @@ public boolean startsAt(SqlParserPos pos) {
&& columnNumber == pos.columnNumber;
}
+ /**
+ * Returns whether this position ends exactly one column before another
+ * position begins, on the same line, with no characters (such as whitespace)
+ * in between.
+ *
+ * @param pos position that may immediately follow this one
+ * @return whether this position ends exactly one column before {@code pos}
+ * begins, on the same line
+ */
+ public boolean endsImmediatelyBefore(SqlParserPos pos) {
+ return endLineNumber == pos.lineNumber
+ && endColumnNumber + 1 == pos.columnNumber;
+ }
+
/** Parser position for an identifier segment that is quoted. */
private static class QuotedParserPos extends SqlParserPos {
QuotedParserPos(int startLineNumber, int startColumnNumber,
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 3c3a3e363e..629357b4e0 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -714,7 +714,8 @@ 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),
+ LEFT_SHIFT(SqlFunctions.class, "leftShift", int.class, long.class),
+ RIGHT_SHIFT(SqlFunctions.class, "rightShift", int.class, long.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/parser/SqlParserPosTest.java
b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserPosTest.java
new file mode 100644
index 0000000000..ed6d07e77c
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserPosTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.parser;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Tests for {@link SqlParserPos}.
+ */
+public class SqlParserPosTest {
+ /** Tests {@link SqlParserPos#endsImmediatelyBefore(SqlParserPos)}. */
+ @Test void testEndsImmediatelyBefore() {
+ // A single-character position ends immediately before the one in the next
+ // column, like the two '>' of a '>>' token.
+ final SqlParserPos col1 = new SqlParserPos(1, 1);
+ final SqlParserPos col2 = new SqlParserPos(1, 2);
+ assertThat(col1.endsImmediatelyBefore(col2), is(true));
+
+ // The relation is directional, not symmetric.
+ assertThat(col2.endsImmediatelyBefore(col1), is(false));
+
+ // A gap between the positions (e.g. whitespace, like '> >') does not
+ // qualify.
+ final SqlParserPos col3 = new SqlParserPos(1, 3);
+ assertThat(col1.endsImmediatelyBefore(col3), is(false));
+
+ // A position does not end immediately before itself.
+ assertThat(col1.endsImmediatelyBefore(col1), is(false));
+
+ // A multi-column position ends immediately before the position that starts
+ // one column after it ends.
+ final SqlParserPos cols1To2 = new SqlParserPos(1, 1, 1, 2);
+ assertThat(cols1To2.endsImmediatelyBefore(col3), is(true));
+ assertThat(cols1To2.endsImmediatelyBefore(col2), is(false));
+
+ // Positions on different lines never qualify.
+ final SqlParserPos line2 = new SqlParserPos(2, 1);
+ assertThat(new SqlParserPos(1, 5).endsImmediatelyBefore(line2), is(false));
+ }
+}
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 959e2fabc2..9231437cf8 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
@@ -2045,19 +2045,30 @@ private long sqlTimestamp(String str) {
return toLong(java.sql.Timestamp.valueOf(str));
}
@Test void testLeftShift() {
- // Test 1-byte array
+ // For every shift amount, cross-check the byte-array shift against the
+ // equivalent shift computed on the value's integer interpretation. The
byte
+ // array is treated as a little-endian bit string of width 8 * length
(index
+ // 0 is the least-significant byte), and leftShift always shifts left by
the
+ // amount normalized modulo that width (so a negative amount wraps into
range
+ // rather than reversing direction).
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);
+ int norm = Math.floorMod(shift, 8);
+ int expected = ((data1[0] & 0xFF) << norm) & 0xFF;
+ assertEquals((byte) expected, result[0]);
}
- // 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);
+ int value = (data2[0] & 0xFF) | ((data2[1] & 0xFF) << 8); //
little-endian
+ int norm = Math.floorMod(shift, 16);
+ int expected = (value << norm) & 0xFFFF;
+ assertEquals((byte) expected, result[0]);
+ assertEquals((byte) (expected >>> 8), result[1]);
}
// Verify specific known cases
@@ -2068,6 +2079,14 @@ private long sqlTimestamp(String str) {
SqlFunctions.leftShift(new byte[]{(byte) 0x40, (byte) 0x00}, 1));
}
+ @Test void testRightShift() {
+ // Scalar arithmetic (sign-preserving) right shift. Binary right shift is
+ // intentionally not supported (see [CALCITE-7651]), so there is no
+ // byte-array overload to exercise here.
+ assertEquals(2, SqlFunctions.rightShift(8, 2));
+ assertEquals(-5, SqlFunctions.rightShift(-20, 2));
+ }
+
@Test void testCombineQueryResults() {
// Test combining two equal-length lists
List<Integer> list1 = Arrays.asList(1, 2, 3);
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 08667c036b..7bfc58eafe 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -11467,6 +11467,7 @@ private static int prec(SqlOperator op) {
+ "> SOME left\n"
+ ">= ALL left\n"
+ ">= SOME left\n"
+ + ">> left\n"
+ "BETWEEN ASYMMETRIC -\n"
+ "BETWEEN SYMMETRIC -\n"
+ "IN left\n"
diff --git a/core/src/test/resources/sql/operator.iq
b/core/src/test/resources/sql/operator.iq
index 33731a08c4..41a470e5f9 100644
--- a/core/src/test/resources/sql/operator.iq
+++ b/core/src/test/resources/sql/operator.iq
@@ -121,6 +121,21 @@ WHERE comm IS NOT NULL LIMIT 4;
!ok
+# [CALCITE-7639] Add support for >> operator in Calcite
+SELECT CAST(comm AS INTEGER) >> 2 AS foo FROM "scott".emp
+WHERE comm IS NOT NULL LIMIT 4;
++-----+
+| FOO |
++-----+
+| 75 |
+| 0 |
+| 125 |
+| 350 |
++-----+
+(4 rows)
+
+!ok
+
# [CALCITE-5531] COALESCE throws ClassCastException
SELECT COALESCE(DATE '2021-07-08', DATE '2020-01-01') as d;
+------------+
@@ -811,4 +826,20 @@ SELECT
!ok
+-- Bitwise RIGHT SHIFT operator `>>`
+SELECT
+ 1 >> 0 AS shift1,
+ 2 >> 1 AS shift2,
+ 8 >> 2 AS shift3,
+ 32 >> 3 AS shift4,
+ CAST(16 AS SMALLINT) >> CAST(3 AS INTEGER) AS cast_shift;
++--------+--------+--------+--------+------------+
+| SHIFT1 | SHIFT2 | SHIFT3 | SHIFT4 | CAST_SHIFT |
++--------+--------+--------+--------+------------+
+| 1 | 1 | 2 | 4 | 2 |
++--------+--------+--------+--------+------------+
+(1 row)
+
+!ok
+
# End operator.iq
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index b1f67eeef1..56fbf8a86c 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -3001,7 +3001,8 @@ ### 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. [...]
+| * | 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: for signed integer types the modulus is
32 for `TINYINT`, `SMALLINT` and `INTEGER` (all backed by a 32-bit
representation) and 64 for `BIGINT`; for unsigned integer types it matches the
type's bit width (modulo 8, 16, 32 [...]
+| * | RIGHTSHIFT(value1, value2) | Returns the result of right-shifting
*value1* by *value2* bits. For signed integers the shift is arithmetic (the
sign bit is preserved). *value1* can be integer or unsigned integer (binary
right shift is not yet supported). The shift amount *value2* is normalized
using modulo arithmetic: for signed integer types the modulus is 32 for
`TINYINT`, `SMALLINT` and `INTEGER` (all backed by a 32-bit representation) and
64 for `BIGINT`; for unsigned integer typ [...]
| * | 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/sql/parser/SqlParserTest.java
b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 9916e0f25e..394aecf26f 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -1254,6 +1254,25 @@ private void checkLarge(int n) {
.ok("((NOT (NOT (`A` = `B`))) OR (NOT (NOT (`C` = `D`))))");
}
+ @Test void testShiftOperators() {
+ expr("1 << 2")
+ .ok("(1 << 2)");
+ // '>>' is recognized as two adjacent '>' tokens.
+ expr("1 >> 2")
+ .ok("(1 >> 2)");
+
+ // '<<' and '>>' have the same precedence and are left-associative.
+ expr("a << b >> c")
+ .ok("((`A` << `B`) >> `C`)");
+ expr("a >> b >> c")
+ .ok("((`A` >> `B`) >> `C`)");
+
+ // The two '>' of a right shift must be adjacent, so "a > > b" (with a
space
+ // between the '>' characters) is not parsed as a right shift.
+ expr("a ^>^ > b")
+ .fails("(?s).*Encountered \"> >\" at line 1, column 3\\..*");
+ }
+
@Test void testIsBooleans() {
String[] inOuts = {"NULL", "TRUE", "FALSE", "UNKNOWN"};
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 d710b516aa..8ddefaa32b 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -16955,6 +16955,7 @@ private static void
checkLogicalOrFunc(SqlOperatorFixture f) {
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");
+ f.checkScalar("CAST(2 AS BIGINT) << CAST(3 AS BIGINT)", "16", "BIGINT NOT
NULL");
// === BigInt shifts with explicit BIGINT inputs ===
f.checkScalar("CAST(1 AS BIGINT) << 62",
BigInteger.ONE.shiftLeft(62).toString(),
@@ -17002,8 +17003,16 @@ private static void
checkLogicalOrFunc(SqlOperatorFixture f) {
"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");
+ // BIGINT UNSIGNED with the high bit set (2^63, built via 1 << 63), shifted
+ // left by a negative amount (i.e. right by 60): the implied right shift
must
+ // be logical, not arithmetic (the raw long is negative).
+ f.checkScalar("CAST(1 AS BIGINT UNSIGNED) << 63 << -4",
+ "8", "BIGINT UNSIGNED NOT NULL");
// === Negative shift counts ===
+ // A negative left shift shifts right by the normalized magnitude (here
+ // 32 - 2 = 30); it is not the same as a right shift by the given amount.
+ f.checkScalar("1 << -2", "0", "INTEGER NOT NULL"); // 1 >> 30
f.checkScalar("8 << -1", "0", "INTEGER NOT NULL");
f.checkScalar("16 << -2", "0", "INTEGER NOT NULL");
@@ -17075,6 +17084,7 @@ private static void
checkLogicalOrFunc(SqlOperatorFixture f) {
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");
+ f.checkScalar("LEFTSHIFT(CAST(2 AS BIGINT), CAST(3 AS BIGINT))", "16",
"BIGINT NOT NULL");
// === BigInt shifts with explicit BIGINT inputs ===
f.checkScalar("LEFTSHIFT(CAST(1 AS BIGINT), 62)",
@@ -17155,6 +17165,193 @@ private static void
checkLogicalOrFunc(SqlOperatorFixture f) {
f.checkNull("LEFTSHIFT(CAST(NULL AS INTEGER UNSIGNED), 2)");
}
+ /**
+ * Test cases for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7639">[CALCITE-7639]
+ * Support bitwise right shift (>>) operator and RIGHTSHIFT
function</a>.
+ */
+ @Test void testRightShiftScalarFunc() {
+ final SqlOperatorFixture f = fixture();
+ f.setFor(SqlStdOperatorTable.BIT_RIGHT_SHIFT, VmName.EXPAND);
+
+ // === Basic functionality ===
+ f.checkScalar("8 >> 2", "2", "INTEGER NOT NULL");
+ f.checkScalar("1024 >> 10", "1", "INTEGER NOT NULL");
+ f.checkScalar("0 >> 5", "0", "INTEGER NOT NULL");
+
+ // === Type coercion and signed (arithmetic) behavior ===
+ f.checkScalar("CAST(16 AS INTEGER) >> CAST(3 AS BIGINT)", "2", "INTEGER
NOT NULL");
+ f.checkScalar("-20 >> 2", "-5", "INTEGER NOT NULL");
+ f.checkScalar("-40 >> 3", "-5", "INTEGER NOT NULL");
+ f.checkScalar("CAST(-20 AS TINYINT) >> CAST(2 AS TINYINT)", "-5", "TINYINT
NOT NULL");
+
+ // === Verify return type matches first argument type ===
+ f.checkType("CAST(8 AS TINYINT) >> CAST(2 AS TINYINT)", "TINYINT NOT
NULL");
+ f.checkType("CAST(8 AS SMALLINT) >> CAST(2 AS SMALLINT)", "SMALLINT NOT
NULL");
+ f.checkType("CAST(8 AS INTEGER) >> CAST(2 AS INTEGER)", "INTEGER NOT
NULL");
+ f.checkType("CAST(8 AS BIGINT) >> CAST(2 AS BIGINT)", "BIGINT NOT NULL");
+ f.checkScalar("CAST(8 AS BIGINT) >> CAST(2 AS BIGINT)", "2", "BIGINT NOT
NULL");
+
+ // === BigInt shifts with explicit BIGINT inputs
(arithmetic/sign-preserving) ===
+ f.checkScalar("CAST(4611686018427387904 AS BIGINT) >> 62", "1", "BIGINT
NOT NULL"); // 2^62
+ f.checkScalar("CAST(9223372036854775807 AS BIGINT) >> 1",
+ BigInteger.valueOf(Long.MAX_VALUE).shiftRight(1).toString(), "BIGINT
NOT NULL");
+ f.checkScalar("CAST(-1 AS BIGINT) >> 63", "-1", "BIGINT NOT NULL"); //
sign bit preserved
+ f.checkScalar("CAST(-1 AS BIGINT) >> 1", "-1", "BIGINT NOT NULL");
+ f.checkScalar("CAST(1000000000 AS BIGINT) >> 5", "31250000", "BIGINT NOT
NULL");
+
+ // === Shift amount normalized using modulo of the bit width ===
+ f.checkScalar("CAST(1024 AS BIGINT) >> 64", "1024", "BIGINT NOT NULL"); //
64 % 64 = 0
+ f.checkScalar("CAST(1024 AS BIGINT) >> 74", "1", "BIGINT NOT NULL"); // 74
% 64 = 10
+ f.checkScalar("1 >> 32", "1", "INTEGER NOT NULL"); // 32 % 32 = 0
+ f.checkScalar("123 >> 60", "0", "INTEGER NOT NULL"); // 60 % 32 = 28
+
+ // === Unsigned types ===
+ f.checkScalar("CAST(252 AS TINYINT UNSIGNED) >> 2", "63", "TINYINT
UNSIGNED NOT NULL");
+ f.checkScalar("CAST(65280 AS SMALLINT UNSIGNED) >> 8", "255", "SMALLINT
UNSIGNED NOT NULL");
+ f.checkScalar("CAST(4294901760 AS INTEGER UNSIGNED) >> 16", "65535",
+ "INTEGER UNSIGNED NOT NULL");
+ f.checkScalar("CAST(2147483648 AS INTEGER UNSIGNED) >> 31", "1", "INTEGER
UNSIGNED NOT NULL");
+ f.checkScalar("CAST(1 AS INTEGER UNSIGNED) >> -1", "2147483648", "INTEGER
UNSIGNED NOT NULL");
+ // BIGINT UNSIGNED with the high bit set (2^63, built via 1 << 63): the
+ // right shift must be logical, not arithmetic (the raw long is negative).
+ f.checkScalar("CAST(1 AS BIGINT UNSIGNED) << 63 >> 4",
+ "576460752303423488", "BIGINT UNSIGNED NOT NULL");
+
+ // A BIGINT shift amount is accepted (INTEGER family), but an unsigned
shift
+ // amount is not: the second operand must be a signed integer type.
+ f.checkScalar("CAST(8 AS INTEGER) >> CAST(2 AS BIGINT)", "2", "INTEGER NOT
NULL");
+ f.checkFails("^8 >> CAST(2 AS INTEGER UNSIGNED)^",
+ "Cannot apply '>>' to arguments of type '<INTEGER> >> <INTEGER
UNSIGNED>'\\. "
+ + "Supported form\\(s\\): '<INTEGER> >> <INTEGER>'\\n"
+ + "'<UNSIGNED_NUMERIC> >> <INTEGER>'",
+ false);
+
+ // === Negative shift counts (normalized via modulo, then shifted the
other way) ===
+ // A negative right shift shifts left by the normalized magnitude (here
+ // 32 - 2 = 30); it is not the same as a left shift by the given amount.
+ f.checkScalar("1 >> -2", "1073741824", "INTEGER NOT NULL"); // 1 << 30
+ 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");
+
+ // === Binary operands are not supported ===
+ // Unlike '<<', binary right shift is intentionally rejected until the
+ // endianness of bitwise shifts on binary is settled (see [CALCITE-7651]).
+ // A binary literal such as X'FF' already has type BINARY(1).
+ f.checkFails("^X'FF' >> 1^",
+ "Cannot apply '>>' to arguments of type '<BINARY\\(1\\)> >>
<INTEGER>'\\. "
+ + "Supported form\\(s\\): '<INTEGER> >> <INTEGER>'\\n"
+ + "'<UNSIGNED_NUMERIC> >> <INTEGER>'",
+ false);
+ f.checkFails("^CAST(X'FF' AS VARBINARY) >> 1^",
+ "Cannot apply '>>' to arguments of type '<VARBINARY> >> <INTEGER>'\\. "
+ + "Supported form\\(s\\): '<INTEGER> >> <INTEGER>'\\n"
+ + "'<UNSIGNED_NUMERIC> >> <INTEGER>'",
+ false);
+
+ // === Invalid argument types ===
+ f.checkFails("^1.2 >> 2^",
+ "Cannot apply '>>' to arguments of type '<DECIMAL\\(2, 1\\)> >>
<INTEGER>'\\. Supported "
+ + "form\\(s\\): '<INTEGER> >> <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 testRightShiftFunctionCall() {
+ final SqlOperatorFixture f = fixture();
+ f.setFor(SqlStdOperatorTable.BIT_RIGHT_SHIFT, VmName.EXPAND);
+
+ // === Basic functionality ===
+ f.checkScalar("RIGHTSHIFT(8, 2)", "2", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(1024, 10)", "1", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(0, 5)", "0", "INTEGER NOT NULL");
+
+ // === Type coercion and signed (arithmetic) behavior ===
+ f.checkScalar("RIGHTSHIFT(CAST(16 AS INTEGER), CAST(3 AS BIGINT))", "2",
"INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(-20, 2)", "-5", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(-40, 3)", "-5", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(-20 AS TINYINT), CAST(2 AS TINYINT))",
"-5", "TINYINT NOT NULL");
+
+ // === Verify return type matches first argument type ===
+ f.checkType("RIGHTSHIFT(CAST(8 AS TINYINT), CAST(2 AS TINYINT))", "TINYINT
NOT NULL");
+ f.checkType("RIGHTSHIFT(CAST(8 AS SMALLINT), CAST(2 AS SMALLINT))",
"SMALLINT NOT NULL");
+ f.checkType("RIGHTSHIFT(CAST(8 AS INTEGER), CAST(2 AS INTEGER))", "INTEGER
NOT NULL");
+ f.checkType("RIGHTSHIFT(CAST(8 AS BIGINT), CAST(2 AS BIGINT))", "BIGINT
NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(8 AS BIGINT), CAST(2 AS BIGINT))", "2",
"BIGINT NOT NULL");
+
+ // === BigInt shifts with explicit BIGINT inputs ===
+ f.checkScalar("RIGHTSHIFT(CAST(4611686018427387904 AS BIGINT), 62)", "1",
"BIGINT NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(9223372036854775807 AS BIGINT), 1)",
+ BigInteger.valueOf(Long.MAX_VALUE).shiftRight(1).toString(), "BIGINT
NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(-1 AS BIGINT), 63)", "-1", "BIGINT NOT
NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(-1 AS BIGINT), 1)", "-1", "BIGINT NOT
NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(1000000000 AS BIGINT), 5)", "31250000",
"BIGINT NOT NULL");
+
+ // === Shift amount normalized using modulo of the bit width ===
+ f.checkScalar("RIGHTSHIFT(CAST(1024 AS BIGINT), 64)", "1024", "BIGINT NOT
NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(1024 AS BIGINT), 74)", "1", "BIGINT NOT
NULL");
+ f.checkScalar("RIGHTSHIFT(1, 32)", "1", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(123, 60)", "0", "INTEGER NOT NULL");
+
+ // === Unsigned types ===
+ f.checkScalar("RIGHTSHIFT(CAST(252 AS TINYINT UNSIGNED), 2)", "63",
+ "TINYINT UNSIGNED NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(65280 AS SMALLINT UNSIGNED), 8)", "255",
+ "SMALLINT UNSIGNED NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(4294901760 AS INTEGER UNSIGNED), 16)",
"65535",
+ "INTEGER UNSIGNED NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(2147483648 AS INTEGER UNSIGNED), 31)", "1",
+ "INTEGER UNSIGNED NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(1 AS INTEGER UNSIGNED), -1)", "2147483648",
+ "INTEGER UNSIGNED NOT NULL");
+
+ // === Negative shifts ===
+ f.checkScalar("RIGHTSHIFT(8, -1)", "0", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(16, -2)", "0", "INTEGER NOT NULL");
+
+ // === Large shifts ===
+ f.checkScalar("RIGHTSHIFT(0, 32)", "0", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(0, 100)", "0", "INTEGER NOT NULL");
+
+ // === Binary operands are not supported ===
+ // Unlike LEFTSHIFT, binary right shift is intentionally rejected until the
+ // endianness of bitwise shifts on binary is settled (see [CALCITE-7651]).
+ // A binary literal such as X'FF' already has type BINARY(1).
+ f.checkFails("^RIGHTSHIFT(X'FF', 1)^",
+ "Cannot apply 'RIGHTSHIFT' to arguments of type
'RIGHTSHIFT\\(<BINARY\\(1\\)>, <INTEGER>\\)'\\. Supported form\\(s\\):
'RIGHTSHIFT\\(<INTEGER>, <INTEGER>\\)'\\n'RIGHTSHIFT\\(<UNSIGNED_NUMERIC>,
<INTEGER>\\)'",
+ false);
+ f.checkFails("^RIGHTSHIFT(CAST(X'FF' AS VARBINARY), 1)^",
+ "Cannot apply 'RIGHTSHIFT' to arguments of type
'RIGHTSHIFT\\(<VARBINARY>, <INTEGER>\\)'\\. Supported form\\(s\\):
'RIGHTSHIFT\\(<INTEGER>, <INTEGER>\\)'\\n'RIGHTSHIFT\\(<UNSIGNED_NUMERIC>,
<INTEGER>\\)'",
+ false);
+
+ // === Invalid types ===
+ f.checkFails("^RIGHTSHIFT(1.2, 2)^",
+ "Cannot apply 'RIGHTSHIFT' to arguments of type
'RIGHTSHIFT\\(<DECIMAL\\(2, 1\\)>, <INTEGER>\\)'\\. Supported form\\(s\\):
'RIGHTSHIFT\\(<INTEGER>, <INTEGER>\\)'\\n'RIGHTSHIFT\\(<UNSIGNED_NUMERIC>,
<INTEGER>\\)'",
+ false);
+ // A BIGINT shift amount is accepted, but an unsigned shift amount is not:
+ // the second argument must be a signed integer type.
+ f.checkScalar("RIGHTSHIFT(8, CAST(2 AS BIGINT))", "2", "INTEGER NOT NULL");
+ f.checkFails("^RIGHTSHIFT(8, CAST(2 AS INTEGER UNSIGNED))^",
+ "Cannot apply 'RIGHTSHIFT' to arguments of type
'RIGHTSHIFT\\(<INTEGER>, <INTEGER UNSIGNED>\\)'\\. Supported form\\(s\\):
'RIGHTSHIFT\\(<INTEGER>, <INTEGER>\\)'\\n'RIGHTSHIFT\\(<UNSIGNED_NUMERIC>,
<INTEGER>\\)'",
+ false);
+
+ // === Nulls ===
+ f.checkNull("RIGHTSHIFT(CAST(NULL AS INTEGER), 5)");
+ f.checkNull("RIGHTSHIFT(10, CAST(NULL AS INTEGER))");
+ f.checkNull("RIGHTSHIFT(CAST(NULL AS INTEGER), CAST(NULL AS INTEGER))");
+ f.checkNull("RIGHTSHIFT(CAST(NULL AS INTEGER UNSIGNED), 2)");
+ }
+
/**
* Test cases for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-7184">[CALCITE-7184]