Skip to content

fix(parser): preserve Oracle PRIOR position on all condition expressions (#2601) - #2602

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

fix(parser): preserve Oracle PRIOR position on all condition expressions (#2601)#2602
manticore-projects merged 1 commit into
JSQLParser:masterfrom
fudianchn:oracle-prior-position

Conversation

@fudianchn

Copy link
Copy Markdown
Contributor

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

What

Between, LikeExpression, SimilarToExpression, IsNullExpression, IsBooleanExpression, IsUnknownExpression, IsDistinctExpression and MemberOfExpression now implement SupportsOldOracleJoinSyntax with a real prior position, InExpression stores the position instead of rejecting it, and ExpressionDeParser renders PRIOR for all of them plus both positions of OldOracleJoinBinaryExpression. 20 new cases in OraclePriorPositionTest. No grammar change.

Why

Condition() consumes a leading PRIOR and only propagates it onto conditions that implement SupportsOldOracleJoinSyntax. For the eight classes above the keyword is silently dropped (WHERE PRIOR a BETWEEN 1 AND 2 deparses without PRIOR, inverting hierarchical-query semantics), PRIOR a IN (1, 2) throws IllegalArgumentException, and comparisons lose PRIOR through the deparser although toString() keeps it. Fixes #2601.

How

Each of the eight classes carries an oraclePriorPosition field, renders PRIOR before its left expression in toString() and in its ExpressionDeParser visit, and rejects setOldOracleJoinSyntax with an explicit IllegalArgumentException (mirroring how InExpression used to reject prior positions: unsupported means explicit rejection, not silent acceptance; the (+) operator keeps its existing route through the operand Column, as in #2564 / #2600). InExpression replaces its always-throw setOraclePriorPosition with a stored field and renders PRIOR in toString() and the deparser. deparse(OldOracleJoinBinaryExpression, ...) gains the two PRIOR positions alongside the existing (+) marks. The grammar needs no change: the instanceof gate in Condition() picks the new implementations up automatically. Known boundary, disclosed: ExpressionValidator reports Feature.oraclePriorPosition for comparisons carrying PRIOR but not yet for these nine kinds (lenient direction only); aligning that is left as a follow-up. PRIOR on the BETWEEN start operand (a BETWEEN PRIOR 1 AND 2, a plain ParseException today) is a separate grammar gap and is left out of scope.

Root cause

Condition() consumes the PRIOR token into a local oraclePrior and applies it only if (result instanceof SupportsOldOracleJoinSyntax); everything else discards it at parse time.

Testing

  • 26 new cases in net.sf.jsqlparser.expression.operators.relational.OraclePriorPositionTest: encoder cases for every condition kind (including SIMILAR TO on both tokenizations, NOT forms, the ISNULL / NOTNULL shorthands, IS FALSE, IS NOT UNKNOWN, the IN form that used to throw), deparser guards for PRIOR a = b / a = PRIOR b and a PRIOR comparison inside a join ON clause (all used to lose PRIOR in the round-trip), the unchanged plain forms, and combinations (NOT PRIOR, several conditions in one WHERE, PRIOR combined with (+) on a BETWEEN operand). On master, the encoder cases fail (silent loss / IllegalArgumentException, verified per form) and the comparison round-trip guards fail through the deparser; all 26 pass with this change.
  • Mutation check, each variant kills exactly its own family: making Between.setOraclePriorPosition a no-op turns the 3 BETWEEN cases red only; making InExpression.setOraclePriorPosition a no-op turns the 2 IN cases red only; removing only the deparser's PRIOR guard for Between turns the 4 BETWEEN round-trip cases red while the AST assertions still see the position, proving toString() and the deparser are pinned independently; reverting only the two PRIOR marks in deparse(OldOracleJoinBinaryExpression, ...) turns exactly the 4 comparison round-trip cases red (the two guards, the ON-clause case and the multi-condition case) while all AST assertions stay green.
  • ./gradlew check and mvn verify are green (full suite).
  • Performance: no benchmark run, rationale: the grammar is untouched and the parse-time path for inputs without PRIOR is bit-identical (the only new work is a setter call when PRIOR is present); the corpus contains no PRIOR, so a benchmark could not exercise the changed path.

Verification of the original issue

SELECT * FROM emp WHERE PRIOR empno BETWEEN 100 AND 200

  • master 7cc86386: parses, toString() is ... WHERE empno BETWEEN 100 AND 200 (PRIOR lost)
  • branch: parses, Between.getOraclePriorPosition() is ORACLE_PRIOR_START, deparse identical to the input.

Fixes #2601

…ons (JSQLParser#2601)

Condition() drops the consumed PRIOR keyword unless the parsed condition
implements SupportsOldOracleJoinSyntax: BETWEEN / LIKE / SIMILAR TO /
IS NULL / IS TRUE / IS UNKNOWN / IS DISTINCT FROM / MEMBER OF silently
lose it, InExpression throws IllegalArgumentException, and comparisons
keep it in toString() but lose it in the deparser. The eight classes now
implement the interface (real prior field, explicit reject for the join
operator), InExpression stores the position instead of rejecting it, and
ExpressionDeParser renders PRIOR for all of them including both
positions of OldOracleJoinBinaryExpression.

Signed-off-by: 付典 <fudianchn@gmail.com>
@manticore-projects
manticore-projects merged commit 3774451 into JSQLParser:master Sep 11, 2026
8 of 9 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.

[BUG] JSQLParser 5.4-SNAPSHOT : Oracle : PRIOR silently dropped on non-comparison conditions (BETWEEN / LIKE / IS NULL / IN ...)

2 participants