Skip to content

fix(parser): parse Oracle outer join operator (+) on BETWEEN operands (#672) - #2564

Open
fudianchn wants to merge 1 commit into
JSQLParser:masterfrom
fudianchn:fix/oracle-join-between-672
Open

fix(parser): parse Oracle outer join operator (+) on BETWEEN operands (#672)#2564
fudianchn wants to merge 1 commit into
JSQLParser:masterfrom
fudianchn:fix/oracle-join-between-672

Conversation

@fudianchn

@fudianchn fudianchn commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

AI disclosure: this change was prepared with AI coding agents, reviewed and revised line by line by me.

What

JSqlParserCC.jjt: accept the Oracle outer join operator (+) after the BETWEEN start and end operands, mirroring the existing consumption point in Condition(). 6 new cases in BetweenTest.

Why

WHERE t1.col1 BETWEEN t2.col2(+) AND t2.col3(+) fails to parse, although (+) already works on comparison operands (t1.c = t2.c(+), t1.c(+) = t2.c) and even on the left operand of BETWEEN itself (t2.col2(+) BETWEEN t1.a AND t1.b). Fixes #672.

How

Today the grammar consumes (+) at three sites: after the condition's left operand in Condition(), after the comparison right-hand side in RegularConditionRHS(), and in PrimaryExpression() only when followed by , or ) (IN lists). The BETWEEN start and end operands reach none of them. The fix adds the same [ LOOKAHEAD("(" "+" ")") "(" "+" ")" ] block after both operands in Between(), placed after the optional comparison-suffix block so inputs like BETWEEN t2.c(+) = 5 AND 1 keep their pre-existing route, setting ORACLE_JOIN_RIGHT on the operand Column through the existing SupportsOldOracleJoinSyntax interface, so there is no AST change and deparsing comes from Column directly.

Root cause

Between() parses its operands via SimpleExpression() and only continues into RegularConditionRHS() when a comparison operator follows; no site consumes the (+) postfix on this path, so BETWEEN t2.col2(+) fails on the ( token.

Testing

  • New cases in net.sf.jsqlparser.expression.operators.relational.BetweenTest (issue form, start-only and end-only forms, NOT BETWEEN, SYMMETRIC combination, join ON clause, left-operand guard, comparison-suffix guard): 6 of 8 new cases fail on master 6c726d8, all 11 pass with this change (./gradlew test --tests "...BetweenTest").
  • Every new form asserts parse + deparse round-trip, plus AST attribution: start and end operand columns carry ORACLE_JOIN_RIGHT.
  • Mutation check, each variant kills exactly its own family: removing the start block turns the start-operand forms red while the end-only form stays green, and vice versa; removing the pre-existing Condition() left-operand block turns only the left-operand guard red; reverting the block order (consuming (+) before the comparison suffix) turns only the comparison-suffix guard red, which asserts the marker stays on the comparison exactly as on master.
  • Boundary, disclosed: when (+) is directly followed by a comparison operator, the pre-existing RegularConditionRHS route applies (marker on the comparison itself, via isComparisonOperatorAhead), the new block only consumes (+) when that route does not apply; for a non-Column operand followed by (+) the token is consumed and not recorded, matching the existing Condition() left-operand semantics.
  • Performance, JSQLParserBenchmark.parseSQLStatements on performance.sql, version=latest, 10 forks x 10 iterations (100 samples) per run, interleaved master -> branch -> master -> branch on a 32-core host (JDK 17):
build ms/op
master 6c726d8 3.830 ± 0.024, 3.848 ± 0.029
branch 3.867 ± 0.026, 3.856 ± 0.025

All four runs are clean windows, the mean delta of 0.6% is within the confidence interval overlap, no regression. The new lookahead only executes inside the BETWEEN production.

Verification of the original issue

SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 BETWEEN t2.col2(+) AND t2.col3(+)

  • master 6c726d8: ParseException: Encountered: <OPENING_BRACKET> / "(", at line 1, column 65
  • branch: parses, start and end operand columns are marked ORACLE_JOIN_RIGHT, deparse is identical to the input.

Fixes #672

@fudianchn
fudianchn marked this pull request as draft September 8, 2026 06:58
@fudianchn
fudianchn force-pushed the fix/oracle-join-between-672 branch from 2b729bb to 489346e Compare September 8, 2026 07:09
@fudianchn
fudianchn marked this pull request as ready for review September 8, 2026 07:21
@fudianchn
fudianchn force-pushed the fix/oracle-join-between-672 branch from 489346e to b9d4405 Compare September 8, 2026 07:59
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.

parse error between with oracle outer join(+)

1 participant