Skip to content

fix: reject illegal MERGE WHEN NOT MATCHED clause pairings (#2480) - #2481

Open
fudianchn wants to merge 1 commit into
JSQLParser:masterfrom
fudianchn:fix/merge-illegal-pairing
Open

fix: reject illegal MERGE WHEN NOT MATCHED clause pairings (#2480)#2481
fudianchn wants to merge 1 commit into
JSQLParser:masterfrom
fudianchn:fix/merge-illegal-pairing

Conversation

@fudianchn

Copy link
Copy Markdown
Contributor

What

Rejects the five illegal WHEN NOT MATCHED side / clause pairings at parse time instead of silently rewriting them: WHEN NOT MATCHED [BY TARGET] THEN UPDATE/DELETE and WHEN NOT MATCHED BY SOURCE THEN INSERT now throw, matching what SQL Server and BigQuery do. Fixes #2480.

Why

On master these pairings are accepted and the deparse collapses them into the opposite semantics: WHEN NOT MATCHED [BY TARGET] THEN UPDATE/DELETE prints as WHEN MATCHED (condition inverted) and WHEN NOT MATCHED BY SOURCE THEN INSERT prints as WHEN NOT MATCHED (BY SOURCE dropped, a different row set). A silent semantic rewrite is the worst failure mode for tools that parse SQL to inspect it. This closes the gap left by #2453, which calibrated the six legal combinations but did not guard the illegal ones.

How

MergeWhenNotMatched ends with MergeSide.validatePairing(side, operation), which enforces the pairing rule already documented on MergeSide: BY TARGET (the default) only allows INSERT, BY SOURCE only allows UPDATE/DELETE. The validation lives in MergeSide next to the existing fromImage validation and keeps the grammar action a single statement. WHEN MATCHED THEN INSERT is already rejected by the grammar and is pinned by a test as well.

Alternative considered (not implemented): accept the illegal pairings and render them faithfully. This would require extending MergeUpdate / MergeDelete / MergeInsert with combinations no dialect accepts (MergeUpdate currently has no NOT MATCHED dimension), adding AST surface for SQL that no database executes. Happy to switch to that direction or to move the check into the statement validator instead, if preferred.

Root cause

MergeWhenNotMatched accepted any clause for any side, while the AST can only represent the legal pairings (MergeUpdate:82 / MergeDelete:55 render WHEN MATCHED for any side other than SOURCE, MergeInsert:77 always renders WHEN NOT MATCHED), so the deparse silently changed the meaning.

Testing

  • New MergeTest#testMergeRejectsInvalidWhenNotMatchedClausePairings: on master the five illegal pairings parse (test fails), with this change they throw JSQLParserException with the pairing message; ./gradlew test --tests net.sf.jsqlparser.statement.merge.MergeTest -> 26 tests, 0 failures.
  • The six legal combinations keep round-tripping (existing #2421 tests, untouched).

Verification of the original issue

Case from #2480 master f41c0b8 with this change
WHEN NOT MATCHED [BY TARGET] THEN UPDATE/DELETE accepted, deparses as WHEN MATCHED rejected with WHEN NOT MATCHED [BY TARGET] cannot take an UPDATE or DELETE clause
WHEN NOT MATCHED BY SOURCE THEN INSERT accepted, deparses as WHEN NOT MATCHED rejected with WHEN NOT MATCHED BY SOURCE cannot take an INSERT clause
all six legal pairings accepted, round-trip unchanged

Grammar tokens, productions and LOOKAHEADs are untouched (javacc warnings stay at 13) and the benchmark input performance.sql contains no MERGE statement, so the changed path never executes in gradle jmh; no performance impact is possible.

Programmatic AST construction (building an illegal MergeUpdate by hand and printing it) is not guarded; the check protects the parse entry point.

Fixes #2480

…r#2480)

WHEN NOT MATCHED accepted any clause regardless of the BY qualifier,
and the AST collapsed the illegal pairings into the opposite semantics
on deparse: WHEN NOT MATCHED [BY TARGET] THEN UPDATE/DELETE printed as
WHEN MATCHED, WHEN NOT MATCHED BY SOURCE THEN INSERT printed as WHEN
NOT MATCHED. SQL Server and BigQuery reject these pairings at parse
time. Validate the pairing via MergeSide.validatePairing, mirroring
the existing MergeSide.fromImage validation.

Signed-off-by: 付典 <fudianchn@gmail.com>
@fudianchn

Copy link
Copy Markdown
Contributor Author

The only failing check is Gradle Check (macos-latest), which failed in its Set up job step while downloading the actions/setup-java action (429 Too Many Requests from codeload.github.com): a transient GitHub infrastructure issue unrelated to this change. Gradle Check on ubuntu/windows and Codacy pass for the same commit; Maven Verify was skipped because of the failed sibling job. I cannot re-run the failed job on a fork PR, please re-run it when convenient.

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] MERGE silently rewrites illegal WHEN NOT MATCHED clause pairings into the opposite semantics

1 participant