Skip to content

[CALCITE-7639] Support bitwise right shift (>>) operator and RIGHTSHIFT function#5073

Open
nielspardon wants to merge 3 commits into
apache:mainfrom
nielspardon:CALCITE-7639-bitwise-right-shift
Open

[CALCITE-7639] Support bitwise right shift (>>) operator and RIGHTSHIFT function#5073
nielspardon wants to merge 3 commits into
apache:mainfrom
nielspardon:CALCITE-7639-bitwise-right-shift

Conversation

@nielspardon

@nielspardon nielspardon commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Jira Link

CALCITE-7639

Changes Proposed

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, and the same INTEGER / BINARY / UNSIGNED_NUMERIC operand families).
  • RIGHTSHIFT(x, n) scalar function, mirroring LEFTSHIFT.
  • SqlFunctions.rightShift(...) runtime overloads (int, long, byte[], ByteString, and the joou unsigned types), plus BuiltInMethod.RIGHT_SHIFT and the RexImpTable registrations for both operator and function.

Unlike <<, 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.

The code in this PR was generated with the help of AI.

@nielspardon nielspardon force-pushed the CALCITE-7639-bitwise-right-shift branch from bb53cb6 to a6d5390 Compare July 3, 2026 15:28
@nielspardon nielspardon changed the title [CALCITE-7639] support bitwise right shift (>>) operator and RIGHTSHIFT function [CALCITE-7639] Support bitwise right shift (>>) operator and RIGHTSHIFT function Jul 3, 2026
…FT 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, and the same
    INTEGER / BINARY / UNSIGNED_NUMERIC operand families).
  * `RIGHTSHIFT(x, n)` scalar function, mirroring `LEFTSHIFT`.
  * SqlFunctions.rightShift(...) runtime overloads (int, long, byte[],
    ByteString, and the joou unsigned types), plus BuiltInMethod.RIGHT_SHIFT
    and the RexImpTable registrations for both operator and function.

Unlike `<<`, 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.
@nielspardon nielspardon force-pushed the CALCITE-7639-bitwise-right-shift branch from a6d5390 to d765854 Compare July 3, 2026 15:44

@Dwrite Dwrite left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM. This is consistent with the LEFTSHIFT operator introduced in #4478 (parser-level resolution with precedence 32). Nice symmetric completion of the feature.

Comment thread core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java Outdated
Comment thread core/src/main/codegen/templates/Parser.jj Outdated
… out shift operand checker

Address review feedback on the right shift PR:

  * Parser.jj: replace the LOOKAHEAD(2) on the two '>' tokens with a
    semantic lookahead that checks the tokens are immediately adjacent
    (same line, adjacent columns). Previously '>>' matched even when the
    two '>' were separated by whitespace, so 'a > > b' was parsed as a
    right shift; it is now a syntax error, falling through to the single
    '>' (greater-than) alternative.

  * SqlStdOperatorTable: factor the duplicated operand type checker shared
    by '<<', '>>', LEFTSHIFT and RIGHTSHIFT into a single
    SHIFT_OPERAND_TYPE_CHECKER constant.

  * SqlParserTest: add testShiftOperators covering '<<'/'>>' parsing,
    precedence and left-associativity, and rejection of 'a > > b'.
@nielspardon nielspardon requested a review from mihaibudiu July 7, 2026 14:41
Comment thread core/src/main/codegen/templates/Parser.jj Outdated
// 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.

…oken check

Address review feedback: factor the '>>' token-adjacency check used by the
right shift operator into a reusable SqlParserPos.adjacent(SqlParserPos)
method, rather than open-coding the line/column comparison in the parser.

  * SqlParserPos: add adjacent(SqlParserPos), which reports whether two
    positions are immediately adjacent (one ends exactly where the other
    begins, with no characters in between).

  * Parser.jj: add a plain pos(Token) helper and use
    pos(getToken(1)).adjacent(pos(getToken(2))) in the BIT_RIGHT_SHIFT
    lookahead. pos is a plain method (not JAVACODE) so it can be called
    from generated lookahead code, which does not declare
    throws ParseException.

  * SqlParserPosTest: add a unit test for SqlParserPos.adjacent.
@sonarqubecloud

sonarqubecloud Bot commented Jul 8, 2026

Copy link
Copy Markdown

@nielspardon nielspardon requested a review from mihaibudiu July 8, 2026 09:42
* that is, one position ends exactly where the other begins, with no
* characters (such as whitespace) in between.
*
* <p>For example, the two {@code >} characters in {@code >>} are adjacent,

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.

I would probably remove this part of the comment.

| LOOKAHEAD({ getToken(1).kind == GT && getToken(2).kind == GT
&& getToken(1).endLine == getToken(2).beginLine
&& getToken(1).endColumn + 1 == getToken(2).beginColumn })
&& pos(getToken(1)).adjacent(pos(getToken(2))) })

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.

here you can use endsImmediatelyBefore, it's a bit more precise.
This would make "adjacent" dead code. I am not sure that function is as useful.

* @return the shifted integer
*/
public static int rightShift(int x, int y) {
int shift = ((y % 32) + 32) % 32; // normalize to 0~31

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.

I would pull this into a helper positive_modulo function.

* @param y the long shift amount
* @return the shifted value as long
*/
public static long rightShift(int x, long y) {

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.

you could only implement the long shift amount by casting any other type to long in the generated enumerable code.

Is there a test with a >> CAST(x AS UNSIGNED)? If not, there should be.

* Performs PostgresSQL-style bitwise shift on ULong.
* Overflow bits are masked to 64 bits (long shifts naturally truncate).
*/
public static ULong rightShift(ULong x, int y) {

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.

why don't you need versions with long y for these?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants