Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -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".
*
Expand Down Expand Up @@ -8470,6 +8488,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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -512,6 +513,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;
Expand Down Expand Up @@ -920,6 +922,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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since LEFTSHIFT supports negative amounts, isn't RIGHTSHIFT(a, b) the same as LEFTSHIFT(a, -b)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, but no — they aren't equivalent under the current (released) semantics. Counterexample, both values from the existing tests: RIGHTSHIFT(8, 1) = 4, whereas LEFTSHIFT(8, -1) = 0.

The reason is how the runtime handles the shift amount: it normalizes the magnitude with ((amount % w) + w) % w (where w is the type's bit width, e.g. 32 for INTEGER) and uses only the amount's sign to pick the direction. So for LEFTSHIFT(a, -b) the amount is -b, and its normalized magnitude is w - (b mod w) — not b. In other words LEFTSHIFT(a, -b) shifts by w - b, not by b. Concretely for INTEGER, -1 becomes a shift of 31: LEFTSHIFT(8, -1) = 8 >> 31 = 0, while RIGHTSHIFT(8, 1) = 8 >> 1 = 4. (BINARY differs again: the byte[] path ignores the sign entirely and always shifts in its native direction.)

That negative-amount behavior of LEFTSHIFT shipped in 1.41.0/1.42.0 (CALCITE-7109), so making the equivalence hold would mean changing released semantics — a separate change, out of scope here.

That said, your instinct points at real duplication: leftShift/rightShift differ only in which way the ternary points. Happy to factor the int/long/unsigned bodies through a shared private helper in a follow-up (or here) to cut that down, without touching behavior.


// 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,
Expand Down
135 changes: 107 additions & 28 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -3772,15 +3772,30 @@ 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.
*
* @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
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
}

Expand All @@ -3793,23 +3808,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.
Expand All @@ -3819,7 +3822,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];
}
Expand All @@ -3828,7 +3831,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();
Expand Down Expand Up @@ -3866,16 +3869,16 @@ 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));
}

/**
* 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);
Expand All @@ -3885,8 +3888,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);
Expand All @@ -3896,8 +3899,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);
Expand All @@ -3907,10 +3910,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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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.
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/parser/SqlParserPos.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading