Skip to content

fix: resolve WITH items by scope in TablesNamesFinder - #2881

Merged
manticore-projects merged 1 commit into
JSQLParser:masterfrom
tanjiroK-coder:tablesnamesfinder-cte-scope
Oct 31, 2026
Merged

manticore-projects merged 1 commit into
JSQLParser:masterfrom
tanjiroK-coder:tablesnamesfinder-cte-scope

Conversation

@tanjiroK-coder

Copy link
Copy Markdown
Contributor

TablesNamesFinder drops a table when its name matches any alias or WITH item anywhere in the statement: visit(Table) skips names in otherItemNames, and getTables then subtracts that whole set from the result, whatever the scope (the @todo there already doubts it). So SELECT * FROM (SELECT * FROM secrets) AS secrets returns no tables, and neither do SELECT (SELECT max(pw) FROM secrets) FROM (SELECT 1) AS secrets, WITH secrets AS (SELECT * FROM secrets) SELECT * FROM secrets, SELECT * FROM secrets WHERE EXISTS (WITH secrets AS (SELECT 1 AS a) SELECT a FROM secrets), DELETE FROM secrets WHERE id IN (SELECT id FROM (SELECT 1 AS id) AS secrets) or WITH secrets AS (SELECT 1 AS id) DELETE FROM secrets, although each one reads or writes the real table. The reporter of #1987 uses the finder to "see if user get access to certain tables", and with a check like that anyone who can submit SQL gets past a table allowlist by reusing the table's name as an alias. WITH on VALUES and TABLE selects was also never walked, so SELECT * FROM (WITH a AS (SELECT * FROM secrets) TABLE a) AS s reported a and missed secrets.

A reference is now dropped only if it resolves to a WITH item in scope at that position. An item becomes visible after its own body, or all at once under RECURSIVE, which the parser sets on the first item but which covers the list, and it leaves scope with its query. DML targets resolve with nothing in scope. Aliases no longer hide table references, but still hide column qualifiers in findTablesInExpression. I compared findTables with the relations in PostgreSQL 16's EXPLAIN (FORMAT JSON) for 54 statements: it missed a touched table in 25 before and in none after (3 statements still report a table from a WITH item the planner prunes because nothing references it).

Two results change on purpose:

  1. testSubqueryAliasesIssue1987 and testSubqueryAliasesIssue2035 pinned the old behaviour, so I updated them. [BUG] JSQLParser Version: 5.0 RDBMS: ANY, TablesNamesFinder.getTables missing table when using same alias #2035 asked for A, B, C, which is now returned. The [BUG] The TablesNamesFinder mistakenly treat nested SELECT in FROM as table #1987 statements, such as SELECT * FROM b, (SELECT * FROM a) AS a1 WHERE b.id IN (SELECT id FROM a1), have the same shape as the bypass, so the finder cannot keep leaving a1 out. PostgreSQL, H2 and SQLite all reject them unless a1 is a real table, and read that table when it is.
  2. WITH t AS (SELECT 1 AS n UNION ALL SELECT n FROM t) ... without RECURSIVE now reports t, and so do the Oracle recursive statements in the test corpus. PostgreSQL reads a base table there while SQLite recurses, and the finder has no dialect to tell them apart. A WITH item as a DML target is reported the same way: PostgreSQL resolves it to the base table, while SQL Server can update through a CTE. I could not run Oracle or SQL Server here. Over-reporting is the safe side: as was said on [BUG] The TablesNamesFinder mistakenly treat nested SELECT in FROM as table #1987, "you are supposed to filter the returned list against the actual Meta Data", and that removes a spurious name but cannot bring back a missing one.

getTablesOrOtherSources returns the same for all 6,446 statements harvested from the test suite. getTables changes for 19: 8 alias statements like those in point 1, 8 recursive ones as in point 2, and 3 with WITH on VALUES that no longer report their item as a table. 31 cases in TablesNamesFinderTest fail before the change and pass after; ./gradlew check and mvn verify pass.

getTables removed every alias and WITH name from its result wherever
it was declared, so a real table with such a name went missing:
SELECT * FROM (SELECT * FROM secrets) AS secrets found no tables. A
name is now left out only where it resolves to a WITH item in scope,
and the target of a DML statement never does. WITH lists on VALUES and
TABLE selects are now walked too.
@manticore-projects
manticore-projects merged commit 7123d25 into JSQLParser:master Oct 31, 2026
10 checks passed
@manticore-projects

Copy link
Copy Markdown
Contributor

Thank you for fixing this! Appreciated.

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

⌂ Proxy