From 1ac762694736f16d556d2978ff90457ea3a76cfc Mon Sep 17 00:00:00 2001 From: tanjiro Kamado Date: Sat, 31 Oct 2026 12:11:01 +0530 Subject: [PATCH] fix: resolve WITH items by scope in TablesNamesFinder 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. --- .../sf/jsqlparser/util/TablesNamesFinder.java | 139 +++++++++++------- src/site/sphinx/usage.rst | 2 + .../util/TablesNamesFinderTest.java | 121 ++++++++++++++- 3 files changed, 200 insertions(+), 62 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 608f8bf93..7befd9beb 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.util; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -287,6 +288,9 @@ public class TablesNamesFinder private Set rowPatternVariables = java.util.Collections.emptySet(); private Set otherItemNames; + // the WITH items a table reference resolves to at the current position, counted because a + // nested WITH may repeat a name of an enclosing one + private Map withItemsInScope; public static Set findTables(String sqlStr) throws JSQLParserException { TablesNamesFinder tablesNamesFinder = new TablesNamesFinder<>(); @@ -317,10 +321,6 @@ public Set getTables(Statement statement) { init(false); statement.accept(this, null); - // @todo: assess this carefully, maybe we want to remove more specifically - // only Aliases on WithItems, Parenthesed Selects and Lateral Selects - otherItemNames.forEach(tables::remove); - return tables; } @@ -336,13 +336,9 @@ public Set getTablesOrOtherSources(Statement statement) { @Override public Void visit(Select select, S context) { - List> withItemsList = select.getWithItemsList(); - if (withItemsList != null && !withItemsList.isEmpty()) { - for (WithItem withItem : withItemsList) { - withItem.accept((SelectVisitor) this, context); - } - } + visitWithItems(select.getWithItemsList(), context); select.accept((SelectVisitor) this, context); + leaveWithItems(select.getWithItemsList()); return null; } @@ -401,6 +397,56 @@ public Set getTables(Expression expr) { return tables; } + /** + * Visits the WITH items of a query and brings their names into scope, which the caller ends + * with {@link #leaveWithItems(List)} after the query. An item only sees the items before it, so + * a reference to its own or a later name is a table, unless RECURSIVE makes all of them visible + * to each other. The parser sets RECURSIVE on the first item, but it covers the list. + */ + @Override + public Void visitWithItems(List> withItemsList, S context) { + if (withItemsList != null) { + boolean recursive = withItemsList.stream().anyMatch(WithItem::isRecursive); + if (recursive) { + withItemsList.forEach(this::enterWithItem); + } + for (WithItem withItem : withItemsList) { + withItem.accept((SelectVisitor) this, context); + if (!recursive) { + enterWithItem(withItem); + } + } + } + return null; + } + + private void enterWithItem(WithItem withItem) { + if (withItem.getAlias() != null) { + withItemsInScope.merge(withItem.getAlias().getName(), 1, Integer::sum); + } + } + + private void leaveWithItems(List> withItemsList) { + if (withItemsList != null) { + for (WithItem withItem : withItemsList) { + if (withItem.getAlias() != null) { + withItemsInScope.computeIfPresent(withItem.getAlias().getName(), + (name, count) -> count > 1 ? count - 1 : null); + } + } + } + } + + /** + * The target of a DML statement is a table even if a WITH item in scope has the same name. + */ + private void visitTargetTable(Table table, S context) { + Map scope = withItemsInScope; + withItemsInScope = new HashMap<>(); + visit(table, context); + withItemsInScope = scope; + } + @Override public Void visit(WithItem withItem, S context) { if (withItem.getAlias() != null) { @@ -428,12 +474,7 @@ public Void visit(ParenthesedSelect select, S context) { if (select.getAlias() != null) { otherItemNames.add(select.getAlias().getName()); } - List> withItemsList = select.getWithItemsList(); - if (withItemsList != null && !withItemsList.isEmpty()) { - for (WithItem withItem : withItemsList) { - withItem.accept((SelectVisitor) this, context); - } - } + visitWithItems(select.getWithItemsList(), context); select.getSelect().accept((SelectVisitor) this, context); visitOrderBy(select.getOrderByElements(), context); if (select.getPivot() != null) { @@ -449,6 +490,7 @@ public Void visit(ParenthesedSelect select, S context) { if (select.getFetch() != null) { select.getFetch().getExpression().accept(this, context); } + leaveWithItems(select.getWithItemsList()); return null; } @@ -517,6 +559,7 @@ private Void visitPlainSelectBody(PlainSelect plainSelect, S context) { visitSelectPagination(plainSelect, context); visitUpdateSets(plainSelect.getSettings(), context); visitFromItem(plainSelect.getIntoTempTable(), context); + leaveWithItems(plainSelect.getWithItemsList()); return null; } @@ -531,6 +574,7 @@ public Void visit(PivotQuery pivotQuery, S context) { visitLimit(pivotQuery.getLimit(), context); visitSelectPagination(pivotQuery, context); + leaveWithItems(pivotQuery.getWithItemsList()); return null; } @@ -540,6 +584,7 @@ public Void visit(UnPivotQuery unPivotQuery, S context) { visitFromItem(unPivotQuery.getFromItem(), context); visitExpressions(unPivotQuery.getOnExpressions(), context); visitExpressions(unPivotQuery.getValueColumns(), context); + leaveWithItems(unPivotQuery.getWithItemsList()); return null; } @@ -617,7 +662,7 @@ public Void visit(Table table, S context) { String tableWholeName = extractTableName(table); if (table.isTableVariable()) { otherItemNames.add(tableWholeName); - } else if (!otherItemNames.contains(tableWholeName)) { + } else if (!withItemsInScope.containsKey(table.getFullyQualifiedName())) { tables.add(tableWholeName); } if (table.getPivot() != null) { @@ -665,6 +710,7 @@ public Void visit(OverlapsCondition overlapsCondition, S context) { public Void visit(Column tableColumn, S context) { if (allowColumnProcessing && tableColumn.getTable() != null && tableColumn.getTable().getName() != null + && !otherItemNames.contains(extractTableName(tableColumn.getTable())) && !rowPatternVariables.contains( MatchRecognize.normalizeVariableName(tableColumn.getTable().getName()))) { visit(tableColumn.getTable(), context); @@ -1108,12 +1154,7 @@ private void visitWindowElement(WindowElement windowElement, S context) { @Override public Void visit(SetOperationList list, S context) { - List> withItemsList = list.getWithItemsList(); - if (withItemsList != null && !withItemsList.isEmpty()) { - for (WithItem withItem : withItemsList) { - withItem.accept((SelectVisitor) this, context); - } - } + visitWithItems(list.getWithItemsList(), context); for (Select selectBody : list.getSelects()) { selectBody.accept((SelectVisitor) this, context); } @@ -1125,6 +1166,7 @@ public Void visit(SetOperationList list, S context) { if (list.getFetch() != null) { list.getFetch().getExpression().accept(this, context); } + leaveWithItems(list.getWithItemsList()); return null; } @@ -1157,7 +1199,9 @@ public void visit(LateralSubSelect lateralSubSelect) { @Override public Void visit(TableStatement tableStatement, S context) { + visitWithItems(tableStatement.getWithItemsList(), context); tableStatement.getTable().accept(this, null); + leaveWithItems(tableStatement.getWithItemsList()); return null; } @@ -1168,12 +1212,7 @@ public void visit(TableStatement tableStatement) { @Override public Void visit(FromQuery fromQuery, S context) { - List> withItemsList = fromQuery.getWithItemsList(); - if (withItemsList != null && !withItemsList.isEmpty()) { - for (WithItem withItem : withItemsList) { - withItem.accept((SelectVisitor) this, context); - } - } + visitWithItems(fromQuery.getWithItemsList(), context); if (fromQuery.getFromItem() != null) { fromQuery.getFromItem().accept(this, context); } @@ -1186,6 +1225,7 @@ public Void visit(FromQuery fromQuery, S context) { for (PipeOperator pipeOperator : fromQuery.getPipeOperators()) { pipeOperator.accept(this, null); } + leaveWithItems(fromQuery.getWithItemsList()); return null; } @@ -1356,6 +1396,7 @@ public Void visit(DateUnitExpression dateUnitExpression, S context) { */ protected void init(boolean allowColumnProcessing) { otherItemNames = new LinkedHashSet(); + withItemsInScope = new HashMap<>(); tables = new HashSet<>(); this.allowColumnProcessing = allowColumnProcessing; this.rowPatternVariables = java.util.Collections.emptySet(); @@ -1485,12 +1526,8 @@ public Void visit(MySQLGroupConcat groupConcat, S context) { @Override public Void visit(Delete delete, S context) { - if (delete.getWithItemsList() != null) { - for (WithItem withItem : delete.getWithItemsList()) { - withItem.accept((SelectVisitor) this, context); - } - } - visit(delete.getTable(), context); + visitWithItems(delete.getWithItemsList(), context); + visitTargetTable(delete.getTable(), context); if (delete.getUsingFromItemList() != null) { for (FromItem usingFromItem : delete.getUsingFromItemList()) { @@ -1510,6 +1547,7 @@ public Void visit(Delete delete, S context) { visitLimit(delete.getLimit(), context); visitOutputClause(delete.getOutputClause(), context); visitReturningClause(delete.getReturningClause(), context); + leaveWithItems(delete.getWithItemsList()); return null; } @@ -1530,14 +1568,10 @@ public Void visit(SessionStatement sessionStatement, S context) { @Override public Void visit(Update update, S context) { - if (update.getWithItemsList() != null) { - for (WithItem withItem : update.getWithItemsList()) { - withItem.accept((SelectVisitor) this, context); - } - } + visitWithItems(update.getWithItemsList(), context); if (!update.isTargetTableAlias()) { - visit(update.getTable(), context); + visitTargetTable(update.getTable(), context); } visitJoins(update.getStartJoins(), context); @@ -1565,6 +1599,7 @@ public Void visit(Update update, S context) { visitLimit(update.getLimit(), context); visitOutputClause(update.getOutputClause(), context); visitReturningClause(update.getReturningClause(), context); + leaveWithItems(update.getWithItemsList()); return null; } @@ -1589,20 +1624,16 @@ public Void visit(Insert insert, S context) { continue; } for (OracleMultiInsertClause clause : branch.getClauses()) { - visit(clause.getTable(), context); + visitTargetTable(clause.getTable(), context); if (clause.getSelect() != null) { visit(clause.getSelect(), context); } } } } else if (insert.getTable() != null) { - visit(insert.getTable(), context); - } - if (insert.getWithItemsList() != null) { - for (WithItem withItem : insert.getWithItemsList()) { - withItem.accept((SelectVisitor) this, context); - } + visitTargetTable(insert.getTable(), context); } + visitWithItems(insert.getWithItemsList(), context); if (insert.getSetUpdateSets() != null) { visitUpdateSets(insert.getSetUpdateSets(), context); } @@ -1620,6 +1651,7 @@ public Void visit(Insert insert, S context) { if (insert.getSelect() != null) { visit(insert.getSelect(), context); } + leaveWithItems(insert.getWithItemsList()); return null; } @@ -2032,12 +2064,8 @@ public Void visit(HexValue hexValue, S context) { @Override public Void visit(Merge merge, S context) { - visit(merge.getTable(), context); - if (merge.getWithItemsList() != null) { - for (WithItem withItem : merge.getWithItemsList()) { - withItem.accept((SelectVisitor) this, context); - } - } + visitTargetTable(merge.getTable(), context); + visitWithItems(merge.getWithItemsList(), context); if (merge.getFromItem() != null) { merge.getFromItem().accept(this, context); @@ -2054,6 +2082,7 @@ public Void visit(Merge merge, S context) { } visitOutputClause(merge.getOutputClause(), context); visitReturningClause(merge.getReturningClause(), context); + leaveWithItems(merge.getWithItemsList()); return null; } @@ -2266,6 +2295,7 @@ public void visit(Comment comment) { @Override public Void visit(Values values, S context) { + visitWithItems(values.getWithItemsList(), context); values.getExpressions().accept(this, context); visitOrderBy(values.getOrderByElements(), context); visitLimit(values.getLimit(), context); @@ -2275,6 +2305,7 @@ public Void visit(Values values, S context) { if (values.getFetch() != null) { values.getFetch().getExpression().accept(this, context); } + leaveWithItems(values.getWithItemsList()); return null; } diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index c784f81d3..9af701f0f 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -1335,6 +1335,8 @@ Find Table Names tableNames = TablesNamesFinder.findTablesInExpression(exprStr); assertThat( tableNames ).containsExactlyInAnyOrder("A", "B", "C"); +A name is left out only where it resolves to a ``WITH`` item in scope. An alias hides no table, and without ``RECURSIVE`` a ``WITH`` item does not see itself or the items after it. The target of a DML statement is always reported. So the result may hold names that are no tables on some databases, such as a ``WITH`` item that recurses without ``RECURSIVE``: check it against the metadata of the database if that matters. + Use the Visitor Patterns ============================== diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java index aeac09906..551075b48 100644 --- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java +++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java @@ -40,6 +40,8 @@ import net.sf.jsqlparser.statement.select.WithItem; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class TablesNamesFinderTest { @@ -559,13 +561,13 @@ void testSubqueryAliasesIssue1987() throws JSQLParserException { assertThat(tables).containsExactlyInAnyOrder("a", "b"); assertThat(tables).doesNotContain("a1"); + // a sub-select cannot select from an alias: this statement only runs if a1 is a table sqlStr = "SELECT * FROM b, (SELECT * FROM a) as a1 WHERE b.id IN ( SELECT id FROM a1 )"; tables = TablesNamesFinder.findTablesOrOtherSources(sqlStr); assertThat(tables).containsExactlyInAnyOrder("a1", "a", "b"); tables = TablesNamesFinder.findTables(sqlStr); - assertThat(tables).containsExactlyInAnyOrder("a", "b"); - assertThat(tables).doesNotContain("a1"); + assertThat(tables).containsExactlyInAnyOrder("a", "a1", "b"); sqlStr = "select (a_alias.col1), b_alias.col2\n" + "from b b_alias, a as a_alias, c join b on c.id = b.id\n" + @@ -589,14 +591,14 @@ void testSubqueryAliasesIssue1987() throws JSQLParserException { + " and a.max > (select max(val) from bc_nested, f) and a.desc like 'abc'"; tables = TablesNamesFinder.findTables(sqlStr); - assertThat(tables).containsExactlyInAnyOrder("a", "b", "c", "d", "e", "f"); + assertThat(tables).containsExactlyInAnyOrder("a", "b", "bc_nested", "c", "d", "e", "f"); sqlStr = " select (select max(val) from e) as maxval, id\n" + " from (select * from b, (select * from c) c_alias) as bc_nested, a\n" + " where a.max > (select max(val) from bc_nested, f) and \n" + " a.id in ( select id from (select * from d) d_alias join bc_nested on bc_nested.id = d_alias.id )"; tables = TablesNamesFinder.findTables(sqlStr); - assertThat(tables).containsExactlyInAnyOrder("a", "b", "c", "d", "e", "f"); + assertThat(tables).containsExactlyInAnyOrder("a", "b", "bc_nested", "c", "d", "e", "f"); sqlStr = "select a.id, bc_nested.id\n" + " from (select * from b, (select * from c) c_alias) as bc_nested, a\n" + @@ -608,7 +610,7 @@ void testSubqueryAliasesIssue1987() throws JSQLParserException { " on bc_nested_alias.id = d.id\n" + " )))"; tables = TablesNamesFinder.findTables(sqlStr); - assertThat(tables).containsExactlyInAnyOrder("a", "b", "c", "d", "e"); + assertThat(tables).containsExactlyInAnyOrder("a", "b", "bc_nested", "c", "d", "e"); sqlStr = "select id\n" + "from (select * from c, (select * from b) b_alias) as bc_nested, a\n" + @@ -618,7 +620,7 @@ void testSubqueryAliasesIssue1987() throws JSQLParserException { "join bc_nested on bc_nested_alias.id = bc_nested.id\n" + ")"; tables = TablesNamesFinder.findTables(sqlStr); - assertThat(tables).containsExactlyInAnyOrder("a", "b", "c", "d", "e"); + assertThat(tables).containsExactlyInAnyOrder("a", "b", "bc_nested", "c", "d", "e"); sqlStr = "with\n" + " temp1 as (\n" + @@ -634,7 +636,7 @@ void testSubqueryAliasesIssue1987() throws JSQLParserException { + "where exist ( select * from e, e_alias where e.test = dtest.test ) and temp1.max = (select max(column_1) from g)"; tables = TablesNamesFinder.findTables(sqlStr); - assertThat(tables).containsExactlyInAnyOrder("a", "b", "c", "d", "e", "f", "g"); + assertThat(tables).containsExactlyInAnyOrder("a", "b", "c", "d", "e", "e_alias", "f", "g"); } @Test @@ -646,7 +648,110 @@ void testSubqueryAliasesIssue2035() throws JSQLParserException { assertThat(tables).containsExactlyInAnyOrder("A", "B", "C"); tables = TablesNamesFinder.findTables(sqlStr); - assertThat(tables).containsExactlyInAnyOrder("B", "C"); + assertThat(tables).containsExactlyInAnyOrder("A", "B", "C"); + } + + @ParameterizedTest + @ValueSource(strings = { + "SELECT * FROM (SELECT * FROM secrets) AS secrets", + "SELECT (SELECT max(pw) FROM secrets) FROM (SELECT 1) AS secrets", + "SELECT * FROM (SELECT 1) AS secrets, LATERAL (SELECT * FROM secrets) AS x", + "SELECT * FROM t, LATERAL (SELECT * FROM secrets) AS secrets", + "FROM secrets |> AS secrets", + "DELETE FROM secrets WHERE id IN (SELECT id FROM (SELECT 1 AS id) AS secrets)", + "UPDATE secrets SET pw = 'x' WHERE id IN (SELECT id FROM (SELECT 1 AS id) AS secrets)", + "INSERT INTO secrets (id) SELECT id FROM (SELECT 1 AS id) AS secrets", + "MERGE INTO secrets AS t USING (SELECT 1 AS id) AS secrets ON (t.id = secrets.id)" + + " WHEN MATCHED THEN DELETE"}) + void testAliasDoesNotHideTable(String sqlStr) throws JSQLParserException { + assertThat(TablesNamesFinder.findTables(sqlStr)).contains("secrets"); + } + + @ParameterizedTest + @ValueSource(strings = { + // without RECURSIVE a WITH item does not see itself or the items after it + "WITH secrets AS (SELECT * FROM secrets) SELECT * FROM secrets", + "WITH secrets AS (SELECT 1 AS id UNION ALL SELECT id FROM secrets) SELECT * FROM secrets", + "WITH x AS (SELECT id FROM secrets), secrets AS (SELECT 1 AS id) SELECT * FROM x, secrets", + // a WITH item only exists in the query that declares it + "SELECT * FROM secrets WHERE EXISTS (WITH secrets AS (SELECT 1 AS a) SELECT a FROM secrets)", + "SELECT id FROM secrets UNION ALL (WITH secrets AS (SELECT 1 AS id) SELECT id FROM secrets)", + "SELECT * FROM (WITH secrets AS (SELECT 1 AS id) SELECT * FROM secrets) AS s, secrets", + "WITH a AS (WITH secrets AS (SELECT 1 AS id) SELECT * FROM secrets) SELECT * FROM a, secrets", + // the target of a DML statement is never a WITH item + "WITH secrets AS (SELECT 1 AS id) DELETE FROM secrets", + "WITH secrets AS (SELECT 1 AS id) UPDATE secrets SET pw = 'x'", + "WITH secrets AS (SELECT 1 AS id) INSERT INTO secrets (id) SELECT id FROM secrets", + "WITH secrets AS (SELECT 1 AS id) MERGE INTO secrets USING secrets AS s" + + " ON (s.id = secrets.id) WHEN MATCHED THEN DELETE", + "WITH secrets AS (SELECT 1 AS id), d AS (DELETE FROM secrets RETURNING *) SELECT * FROM d", + "WITH secrets AS (SELECT 1 AS id), d AS (UPDATE secrets SET pw = 'x' RETURNING *)" + + " SELECT * FROM d", + "WITH secrets AS (SELECT 1 AS id), d AS (INSERT INTO secrets (id) VALUES (2) RETURNING *)" + + " SELECT * FROM d", + // WITH on the kinds of select that used to skip it + "SELECT * FROM (WITH a AS (SELECT * FROM secrets) TABLE a) AS s", + "SELECT * FROM (WITH a AS (SELECT id FROM secrets) VALUES ((SELECT max(id) FROM a))) AS s"}) + void testWithItemDoesNotHideTableOutsideItsScope(String sqlStr) throws JSQLParserException { + assertThat(TablesNamesFinder.findTables(sqlStr)).contains("secrets"); + } + + @ParameterizedTest + @ValueSource(strings = { + "WITH RECURSIVE t(n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 5) SELECT * FROM t", + // RECURSIVE is parsed onto the first item, but covers the whole list + "WITH RECURSIVE a AS (SELECT 1 AS n), t(n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM t" + + " WHERE n < 5) SELECT * FROM t, a", + "WITH RECURSIVE x AS (SELECT id FROM y), y AS (SELECT 1 AS id) SELECT * FROM x", + "WITH a AS (SELECT 1 AS id), b AS (SELECT * FROM a) SELECT * FROM b", + "WITH a AS (SELECT 1 AS id) SELECT id FROM a UNION SELECT id FROM a", + "WITH a AS (SELECT 1 AS id) SELECT * FROM a WHERE id IN (SELECT id FROM a)", + "WITH a AS (SELECT 1 AS id) (SELECT * FROM a)", + "WITH a AS (SELECT 1 AS id) SELECT * FROM (SELECT * FROM a) AS s", + // a nested WITH repeating a name does not end the enclosing one + "WITH a AS (SELECT 1 AS id) SELECT * FROM (WITH a AS (SELECT 2 AS id) SELECT * FROM a) AS s, a", + "SELECT * FROM (WITH a AS (SELECT 1 AS id) TABLE a) AS s", + "SELECT * FROM (WITH a AS (SELECT 1 AS id) VALUES ((SELECT max(id) FROM a))) AS s"}) + void testWithItemInScopeIsNoTable(String sqlStr) throws JSQLParserException { + assertThat(TablesNamesFinder.findTables(sqlStr)).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = { + "WITH a AS (SELECT 1 AS id) INSERT INTO t (id) SELECT id FROM a", + "INSERT INTO t (id) WITH a AS (SELECT 1 AS id) SELECT id FROM a", + "WITH a AS (SELECT 1 AS id) UPDATE t SET id = a.id FROM a", + "WITH a AS (SELECT 1 AS id) DELETE FROM t USING a WHERE t.id = a.id", + "WITH a AS (SELECT 1 AS id) MERGE INTO t USING a ON (t.id = a.id) WHEN MATCHED THEN DELETE"}) + void testWithItemInScopeOfDmlIsNoTable(String sqlStr) throws JSQLParserException { + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactly("t"); + } + + @Test + void testWithItemDoesNotHideQualifiedTable() throws JSQLParserException { + String sqlStr = "WITH secrets AS (SELECT 1 AS id) SELECT * FROM public.secrets"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactly("public.secrets"); + + TablesNamesFinder finderWithoutSchema = new TablesNamesFinder() { + @Override + protected String extractTableName(Table table) { + return table.getName(); + } + }; + assertThat(finderWithoutSchema.getTables(CCJSqlParserUtil.parse(sqlStr))) + .containsExactly("secrets"); + } + + @Test + void testAliasInExpression() throws JSQLParserException { + assertThat(TablesNamesFinder + .findTablesInExpression("(SELECT 1 FROM (SELECT * FROM secrets) AS secrets)")) + .containsExactly("secrets"); + + // an alias still is no table when it qualifies a column + assertThat(TablesNamesFinder + .findTablesInExpression("(SELECT max(id) FROM (SELECT id FROM t) AS x) = x.id")) + .containsExactly("t"); } @Test