Skip to content

Translate the first branch of a UNION query - #1843

Open
agusaldasoro wants to merge 5 commits into
masterfrom
fix/smtlib-set-operation-select
Open

agusaldasoro wants to merge 5 commits into
masterfrom
fix/smtlib-set-operation-select

Conversation

@agusaldasoro

@agusaldasoro agusaldasoro commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

JSqlParser 4.9 parses a query that combines SELECTs with UNION into a SetOperationList, not a PlainSelect. SmtLibGenerator cast sqlQuery.selectBody as PlainSelect in four places. For a UNION, the cast threw ClassCastException, the query was recorded as untranslatable, and no data was ever generated for it. A parenthesised top-level SELECT (ParenthesedSelect) failed the same way.

Fix

The four casts are replaced by plainSelectOf, which always returns the plain SELECT to translate:

  • a PlainSelect is used as is, and a ParenthesedSelect is unwrapped;
  • for a UNION (or UNION ALL), the first branch is translated as if it were the whole query. A UNION returns rows as soon as any one branch does, so data for the first branch is enough;
  • INTERSECT and EXCEPT constrain every branch, which the translation cannot express. They are rejected with an explicit message instead of a ClassCastException, and still end up recorded as untranslatable.

The default table for unqualified columns is now taken from the translated branch rather than from all the tables of the statement. Otherwise a WHERE of the first branch could resolve to a table of another branch.

Tests

New SetOperationSolvingTest, end to end against Z3 and H2:

  • unionGeneratesDataForItsFirstBranch: solves SELECT … FROM users WHERE age > 30 UNION SELECT … FROM products WHERE stock < 0, inserts the generated rows and checks that the UNION returns them. On master the solver returns no actions.
  • intersectIsRejectedWithoutCrashing: an INTERSECT returns no actions and does not throw.

All tests under org.evomaster.core.database.sql.solver pass.

This PR touches the same lines of SmtLibGenerator.appendQueryConstraints and appendJoinConstraints as #1842 (the default table), so whichever is merged second will need a small rebase.

  A UNION is parsed into a SetOperationList, which the generator cast to
  PlainSelect, so the query failed with a ClassCastException and never
  got data. The first branch of a UNION is now translated, since one
  branch returning rows is enough; INTERSECT and EXCEPT are rejected
  explicitly.
@agusaldasoro agusaldasoro changed the title Translate the first branch of a UNION query Translate the first branch of a UNION query Oct 7, 2026
@agusaldasoro
agusaldasoro marked this pull request as ready for review October 8, 2026 13:59
@agusaldasoro
agusaldasoro requested a review from jgaleotti October 8, 2026 13:59
) {
if (sqlQuery is Select) { // TODO: Handle other queries
val plainSelect = sqlQuery.selectBody as PlainSelect
val plainSelect = translatedSelect(sqlQuery)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

but this is no longer a plainSelect, right?

when (sqlQuery) {
is Select -> {
val plainSelect = sqlQuery.selectBody as PlainSelect
val plainSelect = translatedSelect(sqlQuery)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is the output of translatedSelect a PlainSelect?

// Add tables from JOINs and WHERE clause if they exist
if (sqlQuery is Select) {
val plainSelect = sqlQuery.selectBody as PlainSelect
val plainSelect = translatedSelect(sqlQuery)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

same comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, you are right, I renamed the method as it still is a plain select

@agusaldasoro
agusaldasoro requested a review from jgaleotti October 9, 2026 09:05
@jgaleotti
jgaleotti requested a review from arcuri82 October 9, 2026 17:53
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.

2 participants