Skip to content

fix(parser): parse Oracle outer join operator (+) after LIKE / SIMILAR TO operands (#2598) - #2600

Merged
manticore-projects merged 1 commit into
JSQLParser:masterfrom
fudianchn:like-oracle-join
Sep 11, 2026
Merged

fix(parser): parse Oracle outer join operator (+) after LIKE / SIMILAR TO operands (#2598)#2600
manticore-projects merged 1 commit into
JSQLParser:masterfrom
fudianchn:like-oracle-join

Conversation

@fudianchn

@fudianchn fudianchn commented Sep 11, 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 right-hand operand of LIKE conditions and SIMILAR TO, mirroring the BETWEEN fix from #2564. 9 new cases in LikeExpressionTest.

Why

WHERE t1.col1 LIKE t2.col2(+) fails to parse, although (+) already works on comparison operands (t1.c = t2.c(+), t1.c(+) = t2.c), on BETWEEN operands (#2564), and on the left operand of LIKE itself. The same failure occurs for NOT LIKE and SIMILAR TO, and when an ESCAPE clause follows. Fixes #2598.

How

The grammar consumes (+) at Condition() (left operand), RegularConditionRHS() (comparison right-hand side) and Between() (#2564); the right operand of LikeExpression() / SimilarToExpression() reaches none of them. The fix adds the same [ LOOKAHEAD("(" "+" ")") "(" "+" ")" ] block after rightExpression = SimpleExpression() in both productions, placed before the optional ESCAPE clause, setting ORACLE_JOIN_RIGHT on the operand Column through the existing SupportsOldOracleJoinSyntax interface. No AST change, deparsing comes from Column directly. The block lives in the shared production, so the other keywords it serves (ILIKE, RLIKE, REGEXP, MATCH_*) gain the same postfix. Oracle's documented (+) restrictions name OR, IN, subqueries and non-column expressions, not LIKE (rules and restrictions in the Joins chapter of the Oracle SQL Reference).

Root cause

LikeExpression() and SimilarToExpression() parse their right operand via SimpleExpression() and never consume a (+) postfix on that path, so LIKE t2.col2(+) fails on the ( token.

Testing

  • 9 new cases in net.sf.jsqlparser.expression.operators.relational.LikeExpressionTest (right-operand forms: LIKE, NOT LIKE, SIMILAR TO single token, SIMILAR TO on separate tokens, LIKE + ESCAPE, SIMILAR TO + ESCAPE, ILIKE, LIKE inside a join ON clause, left-operand guard): the 8 encoder cases fail on master 7cc86386 with ParseException at ( and nothing else fails, all pass with this change (mvn clean test -Dtest=LikeExpressionTest; ./gradlew check and mvn verify are green).
  • Every new form asserts parse + deparse round-trip plus AST attribution: the right operand column carries ORACLE_JOIN_RIGHT, the other operand NO_ORACLE_JOIN.
  • Mutation check, each variant kills exactly its own path: never marking in the LikeExpression() block turns its path tests red; marking the left operand instead in the SimilarToExpression() block turns only the separate-token test red; disabling the marker of the pre-existing Condition() left-operand block turns only the left-operand guard red (round-trip loses the (+)), proving the guard pins the pre-existing route.
  • Boundary, disclosed: for a non-Column right operand followed by (+) the token is consumed and not recorded, matching the existing Condition() left-operand and Between() semantics.
  • Performance, JSQLParserBenchmark.parseSQLStatements, 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 7cc86386 12.333 ± 0.227, 11.912 ± 0.088
branch 11.953 ± 0.103, 11.917 ± 0.095

The first master window has a wide interval (±0.227, background load on the shared host) and sits high; the remaining three windows agree within overlapping intervals (11.912-11.953), the mean delta is 0.1%, no regression. The new lookahead only executes inside the LIKE family productions.

Note on the corpus: the stock performance.sql contains @Prompt macros JSQLParser cannot parse (long-standing, both builds fail that statement identically; tracked in #2599, which also documents that parseStatements used to hide this by returning null until #2568 / #2594 made it fail fast). Its only LIKE-bearing statement is that one. Both builds therefore ran on an identical corpus consisting of the 48 statements that master parses plus one synthetic statement carrying 381 LIKE-family predicates (LIKE/NOT LIKE/SIMILAR TO/ESCAPE, modeled on the excluded statement's CASE WHEN ... LIKE cascades), so the changed productions are actually exercised on both sides.

Verification of the original issue

SELECT * FROM table1 t1, table2 t2 WHERE t1.col1 LIKE t2.col2(+)

  • master 7cc86386: ParseException: Encountered: <OPENING_BRACKET> / "(", at line 1, column 62
  • branch: parses, the right operand column is marked ORACLE_JOIN_RIGHT, deparse is identical to the input.

Fixes #2598

@fudianchn
fudianchn marked this pull request as draft September 11, 2026 13:07
…R TO operands (JSQLParser#2598)

Signed-off-by: 付典 <fudianchn@gmail.com>
@fudianchn
fudianchn marked this pull request as ready for review September 11, 2026 13:08
@manticore-projects
manticore-projects merged commit 35f84d5 into JSQLParser:master Sep 11, 2026
9 checks passed
@manticore-projects

Copy link
Copy Markdown
Contributor

Nice, thank you very 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.

[BUG] JSQLParser 5.4-SNAPSHOT : Oracle : outer join operator (+) after LIKE / SIMILAR TO operand

2 participants