Skip to content

fix: bind bitwise operators looser than + and - - #2833

Merged
manticore-projects merged 1 commit into
JSQLParser:masterfrom
ybai08:fix/bitwise-precedence
Oct 18, 2026
Merged

manticore-projects merged 1 commit into
JSQLParser:masterfrom
ybai08:fix/bitwise-precedence

Conversation

@ybai08

@ybai08 ybai08 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Since the Pratt rewrite (5.4), &, |, #, << and >> have the same precedence as + and -. That gives 1 & 7 - 2 as (1 & 7) - 2 and 1 << n - 1 as (1 << n) - 1. PostgreSQL, MySQL and SQLite all bind + and - tighter:

Expression PostgreSQL 17 MySQL 8.4 SQLite JSqlParser master This PR
1 & 7 - 2 1 = 1 & (7 - 2) 1 1 (1 & 7) - 2 1 & (7 - 2)
1 << 2 + 1 8 = 1 << (2 + 1) 8 8 (1 << 2) + 1 1 << (2 + 1)
a + b >> 2 (a + b) >> 2 same same same unchanged

5.3 got this right: BitwiseAndOr took AdditiveExpression operands.

The change gives & | # << >> their own level (prec 4), below + - || (prec 5). With Dialect.POSTGRESQL, PostgreSQL's || and its JSON operators already use prec 4. So in that dialect, all of PostgreSQL's "other operators" now share a single level, which matches PostgreSQL. A space-separated | | concat keeps the level of +. The bitwise operators share one left-associative level, as in PostgreSQL and SQLite. MySQL separates |, & and the shifts into different levels, and this PR doesn't change how that case is handled.

One existing assertion changes. testGetSign expected 1&2||3 to parse as (1 & 2) || 3. Outside the PostgreSQL dialect, || now binds like + (since the Pratt rewrite), and & binds looser than +. So || binds tighter than & and the expression parses as 1 & (2 || 3). That is also how SQLite reads it. With Dialect.POSTGRESQL the result is still (1 & 2) || 3, as in PostgreSQL, where SELECT 1 & 2 || 'x' gives '0x'. The test now asserts both. Please tell me if you'd rather keep the old default for that mix.

Also unsure: in T-SQL, & and | sit at the + - level, so with Dialect.SQLSERVER the master behaviour was right for a & b + c. 5.3 behaved like this PR. If you'd like, I can add a Dialect.SQLSERVER branch.

Tests: three cases are added to ExpressionPrecedenceTest, covering &, |, <<, >>, # (PostgreSQL dialect) against + -, the shared bitwise level, | | concat, and a round trip. testGetSign is updated as described above.

What was run:

  • ./gradlew test --tests ExpressionPrecedenceTest without the grammar change: testBitwiseOperatorsBindLooserThanAddition and testSpaceSeparatedConcatStillBindsLikeAddition fail (expected: <BitwiseAnd> but was: <Subtraction> / <Concat>). With the change, both pass.
  • ./gradlew test with the change but before updating testGetSign: 10,747 tests, only testGetSign failed.
  • ./gradlew check (JDK 21, with a JDK 17 toolchain): BUILD SUCCESSFUL. That covers 10,748 tests with 0 failures, plus checkstyle, PMD, spotless, spotbugs and jacoco coverage verification.
  • A differential check against PostgreSQL 17. Random integer and boolean expressions using + - * / % ^ & | # << >>, comparisons, BETWEEN, IN, IS NULL, NOT, CASE and ::int were parsed with JSqlParser. Each tree was re-printed fully parenthesised, and PostgreSQL evaluated both forms. On master, a few percent of the expressions came out different. With this change, the only mismatches left involved ^, which fix: bind ^ tighter than *, / and % #2831 fixed. With both changes applied, 0 of 8,623 expressions differ.

Not run: gradle jmh. The only extra work is a getToken(2) check when the next token is |.

This is rebased on #2831, which touched the same lines. After the rebase, ExpressionPrecedenceTest was run again (without the grammar change, testGetSign, testBitwiseOperatorsBindLooserThanAddition and testSpaceSeparatedConcatStillBindsLikeAddition fail; with it, all 23 pass) along with the full ./gradlew test (10,750 tests, 0 failures).

Since the Pratt rewrite, &, |, #, << and >> share the precedence of +
and -, so 1 & 7 - 2 parses as (1 & 7) - 2 and 1 << n - 1 as
(1 << n) - 1. PostgreSQL, MySQL and SQLite all bind + and - tighter,
as did JSqlParser 5.3, where BitwiseAndOr was built from
AdditiveExpression operands.

Give the bitwise operators their own level below + - ||, the level
that PostgreSQL's || and JSON operators already use with
Dialect.POSTGRESQL. A space separated | | concat keeps the level of +.

1&2||3 is now 1 & (2 || 3) by default, as || binds like +, and
(1 & 2) || 3 with Dialect.POSTGRESQL, as in PostgreSQL.
@manticore-projects
manticore-projects merged commit f6ac257 into JSQLParser:master Oct 18, 2026
8 checks passed
@manticore-projects

Copy link
Copy Markdown
Contributor

Thank you much!

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.

2 participants