Skip to content

fix(parser): pass modifier to ExceptOp/MinusOp constructors (#2419)#2420

Merged
manticore-projects merged 2 commits intoJSQLParser:masterfrom
queelius:fix/except-minus-all-modifier-lost
Mar 25, 2026
Merged

fix(parser): pass modifier to ExceptOp/MinusOp constructors (#2419)#2420
manticore-projects merged 2 commits intoJSQLParser:masterfrom
queelius:fix/except-minus-all-modifier-lost

Conversation

@queelius
Copy link
Contributor

Summary

  • Fix EXCEPT ALL/DISTINCT and MINUS ALL/DISTINCT modifiers being silently dropped during parsing
  • Reset the modifier variable between set-operation loop iterations to prevent modifier leaking from one operator to the next

Problem

The grammar in JSqlParserCC.jjt captures the set operation modifier via SetOperationModifier() but constructs ExceptOp and MinusOp with their no-arg constructors (which default to empty string), discarding the captured modifier. UnionOp and IntersectOp correctly receive modifier as a constructor argument.

// CORRECT: modifier passed to constructor
<K_UNION> [ modifier=SetOperationModifier() ] { UnionOp union = new UnionOp(modifier); ... }
<K_INTERSECT> [ modifier=SetOperationModifier() ] { IntersectOp intersect = new IntersectOp(modifier); ... }

// BUG: modifier NOT passed to constructor
<K_MINUS> [ modifier=SetOperationModifier() ] { MinusOp minus = new MinusOp(); ... }
<K_EXCEPT> [ modifier=SetOperationModifier() ] { ExceptOp except = new ExceptOp(); ... }

Additionally, the modifier variable was not reset between iterations of the (LOOKAHEAD(2) ...)+ loop, so a UNION ALL ... EXCEPT would incorrectly leak the ALL modifier to the EXCEPT.

Fix

  1. Pass modifier to MinusOp(modifier) and ExceptOp(modifier) constructors
  2. Add { modifier = null; } at the start of each loop iteration

Test plan

  • testExceptAllRoundTrip - verifies EXCEPT ALL round-trips correctly
  • testExceptDistinctRoundTrip - verifies EXCEPT DISTINCT round-trips correctly
  • testMinusAllRoundTrip - verifies MINUS ALL round-trips correctly
  • testMinusDistinctRoundTrip - verifies MINUS DISTINCT round-trips correctly
  • testModifierDoesNotLeakFromUnionAllToExcept - verifies modifier doesn't leak across operators
  • testModifierDoesNotLeakFromIntersectAllToUnion - verifies modifier doesn't leak across operators
  • testMixedModifiersPreserved - verifies mixed modifiers in chained set ops
  • testExceptAllSetOperationObject / testMinusAllSetOperationObject - verifies SetOperation state
  • Full test suite passes (4671+ tests, 0 failures)

Fixes #2419

🤖 Generated with Claude Code

… between iterations

EXCEPT ALL/DISTINCT and MINUS ALL/DISTINCT modifiers were silently
dropped during parsing because the grammar captured the modifier via
SetOperationModifier() but constructed ExceptOp and MinusOp with
their no-arg constructors (defaulting to empty string), unlike
UnionOp and IntersectOp which correctly received the modifier.

Additionally, the modifier variable was not reset between iterations
of the set-operation loop, causing modifiers to leak from one
operator to the next (e.g., UNION ALL ... EXCEPT would incorrectly
make the EXCEPT inherit ALL).

Fixes JSQLParser#2419

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 10:48
@manticore-projects
Copy link
Contributor

manticore-projects commented Mar 25, 2026

Thank you very much for finding and fixing this.
I would merge as soon as the CI/QA passes.

Since you are using a LLM assistant: please use Parametrized Unit Tests where possible. And run './gradlew spotlessApply' to fix all violations. Thanks!

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes parsing/deparsing fidelity for set-operation modifiers by ensuring ALL/DISTINCT captured by the grammar are preserved for EXCEPT and MINUS, and by preventing modifier state from leaking across chained set operations.

Changes:

  • Pass the parsed modifier into MinusOp(modifier) and ExceptOp(modifier) constructors.
  • Reset modifier at the start of each set-operation loop iteration to prevent modifier leakage.
  • Add regression tests covering round-trip behavior and object state for EXCEPT/MINUS modifiers, plus leak-prevention cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt Ensures EXCEPT/MINUS retain ALL/DISTINCT modifiers and avoids modifier leakage between operations.
src/test/java/net/sf/jsqlparser/statement/select/SetOperationModifierTest.java Adds regression coverage for modifier preservation and leakage prevention across chained set operations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* parse-toString round-trips for all set operation types: UNION, INTERSECT,
* EXCEPT, and MINUS.
*
* @see <a href="https://github.com/JSQLParser/JSqlParser/issues/2080">#2080</a>
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The Javadoc references issue #2080, but this PR (and the described regression) is for #2419. Please update the link so future readers can trace the correct bug report.

Suggested change
* @see <a href="https://github.com/JSQLParser/JSqlParser/issues/2080">#2080</a>
* @see <a href="https://github.com/JSQLParser/JSqlParser/issues/2419">#2419</a>

Copilot uses AI. Check for mistakes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@queelius
Copy link
Contributor Author

Converted to parameterized tests and ran spotlessApply. Thanks for the quick feedback.

@manticore-projects manticore-projects merged commit 7fc300f into JSQLParser:master Mar 25, 2026
7 checks passed
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] EXCEPT ALL/DISTINCT and MINUS ALL/DISTINCT modifiers lost during parse-toString round-trip

3 participants