diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 720e16254..3e15be9a7 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -76,6 +76,7 @@ import net.sf.jsqlparser.statement.PurgeObjectType; import net.sf.jsqlparser.statement.PurgeStatement; import net.sf.jsqlparser.statement.ResetStatement; +import net.sf.jsqlparser.statement.ReturningClause; import net.sf.jsqlparser.statement.RollbackStatement; import net.sf.jsqlparser.statement.SavepointStatement; import net.sf.jsqlparser.statement.SessionStatement; @@ -92,6 +93,7 @@ import net.sf.jsqlparser.statement.alter.AlterSystemStatement; import net.sf.jsqlparser.statement.alter.RenameTableStatement; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; +import net.sf.jsqlparser.statement.OutputClause; import net.sf.jsqlparser.statement.analyze.Analyze; import net.sf.jsqlparser.statement.comment.Comment; import net.sf.jsqlparser.statement.create.database.CreateDatabase; @@ -111,12 +113,38 @@ import net.sf.jsqlparser.statement.grant.Grant; import net.sf.jsqlparser.statement.imprt.Import; import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.InsertConflictAction; +import net.sf.jsqlparser.statement.insert.InsertDuplicateAction; import net.sf.jsqlparser.statement.insert.OracleMultiInsertBranch; import net.sf.jsqlparser.statement.insert.OracleMultiInsertClause; import net.sf.jsqlparser.statement.insert.ParenthesedInsert; import net.sf.jsqlparser.statement.lock.LockStatement; import net.sf.jsqlparser.statement.merge.Merge; +import net.sf.jsqlparser.statement.merge.MergeDelete; +import net.sf.jsqlparser.statement.merge.MergeInsert; +import net.sf.jsqlparser.statement.merge.MergeOperation; +import net.sf.jsqlparser.statement.merge.MergeOperationVisitor; +import net.sf.jsqlparser.statement.merge.MergeUpdate; +import net.sf.jsqlparser.statement.piped.AggregatePipeOperator; +import net.sf.jsqlparser.statement.piped.AsPipeOperator; +import net.sf.jsqlparser.statement.piped.CallPipeOperator; +import net.sf.jsqlparser.statement.piped.DropPipeOperator; +import net.sf.jsqlparser.statement.piped.ExtendPipeOperator; import net.sf.jsqlparser.statement.piped.FromQuery; +import net.sf.jsqlparser.statement.piped.JoinPipeOperator; +import net.sf.jsqlparser.statement.piped.LimitPipeOperator; +import net.sf.jsqlparser.statement.piped.OrderByPipeOperator; +import net.sf.jsqlparser.statement.piped.PipeOperator; +import net.sf.jsqlparser.statement.piped.PipeOperatorVisitor; +import net.sf.jsqlparser.statement.piped.PivotPipeOperator; +import net.sf.jsqlparser.statement.piped.RenamePipeOperator; +import net.sf.jsqlparser.statement.piped.SelectPipeOperator; +import net.sf.jsqlparser.statement.piped.SetOperationPipeOperator; +import net.sf.jsqlparser.statement.piped.SetPipeOperator; +import net.sf.jsqlparser.statement.piped.TableSamplePipeOperator; +import net.sf.jsqlparser.statement.piped.UnPivotPipeOperator; +import net.sf.jsqlparser.statement.piped.WherePipeOperator; +import net.sf.jsqlparser.statement.piped.WindowPipeOperator; import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement; import net.sf.jsqlparser.statement.select.AllColumns; import net.sf.jsqlparser.statement.select.AllTableColumns; @@ -156,7 +184,8 @@ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.UncommentedEmptyMethodBody"}) public class TablesNamesFinder implements SelectVisitor, FromItemVisitor, ExpressionVisitor, - SelectItemVisitor, StatementVisitor { + SelectItemVisitor, StatementVisitor, MergeOperationVisitor, + PipeOperatorVisitor { private Set tables; private boolean allowColumnProcessing = false; @@ -277,9 +306,8 @@ public Void visit(WithItem withItem, S context) { if (withItem.getAlias() != null) { otherItemNames.add(withItem.getAlias().getName()); } - if (withItem.getSelect() != null) { - withItem.getSelect().accept((SelectVisitor) this, context); - } + // dispatch any ParenthesedStatement payload (Select, Delete, Update, Insert) + withItem.accept((StatementVisitor) this, context); return null; } @@ -768,7 +796,15 @@ public Void visit(AnalyticExpression analytic, S context) { if (analytic.getKeep() != null) { analytic.getKeep().accept(this, context); } + if (analytic.getFilterExpression() != null) { + analytic.getFilterExpression().accept(this, context); + } if (analytic.getFuncOrderBy() != null) { + for (OrderByElement element : analytic.getFuncOrderBy()) { + element.getExpression().accept(this, context); + } + } + if (analytic.getOrderByElements() != null) { for (OrderByElement element : analytic.getOrderByElements()) { element.getExpression().accept(this, context); } @@ -852,9 +888,143 @@ 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); + } + } + if (fromQuery.getFromItem() != null) { + fromQuery.getFromItem().accept(this, context); + } + for (PipeOperator pipeOperator : fromQuery.getPipeOperators()) { + pipeOperator.accept(this, null); + } + return null; + } + + @Override + public Void visit(AggregatePipeOperator aggregate, Void context) { + for (SelectItem selectItem : aggregate.getSelectItems()) { + selectItem.accept(this, context); + } + for (SelectItem groupItem : aggregate.getGroupItems()) { + groupItem.accept(this, context); + } return null; } + @Override + public Void visit(AsPipeOperator as, Void context) { + if (as.getAlias() != null) { + otherItemNames.add(as.getAlias().getName()); + } + return null; + } + + @Override + public Void visit(CallPipeOperator call, Void context) { + visit(call.getTableFunction(), context); + return null; + } + + @Override + public Void visit(DropPipeOperator drop, Void context) { + drop.getColumns().accept(this, context); + return null; + } + + @Override + public Void visit(ExtendPipeOperator extend, Void context) { + return visit((SelectPipeOperator) extend, context); + } + + @Override + public Void visit(JoinPipeOperator joinPipeOperator, Void context) { + visitJoins(List.of(joinPipeOperator.getJoin()), context); + return null; + } + + @Override + public Void visit(LimitPipeOperator limit, Void context) { + limit.getLimitExpression().accept(this, context); + if (limit.getOffsetExpression() != null) { + limit.getOffsetExpression().accept(this, context); + } + return null; + } + + @Override + public Void visit(OrderByPipeOperator orderBy, Void context) { + for (OrderByElement element : orderBy.getOrderByElements()) { + element.getExpression().accept(this, context); + } + return null; + } + + @Override + public Void visit(PivotPipeOperator pivot, Void context) { + pivot.getAggregateExpression().accept(this, context); + for (SelectItem pivotColumn : pivot.getPivotColumns()) { + pivotColumn.accept(this, context); + } + return null; + } + + @Override + public Void visit(RenamePipeOperator rename, Void context) { + return visit((SelectPipeOperator) rename, context); + } + + @Override + public Void visit(SelectPipeOperator select, Void context) { + for (SelectItem selectItem : select.getSelectItems()) { + selectItem.accept(this, context); + } + return null; + } + + @Override + public Void visit(SetPipeOperator set, Void context) { + for (UpdateSet updateSet : set.getUpdateSets()) { + updateSet.getColumns().accept(this, context); + updateSet.getValues().accept(this, context); + } + return null; + } + + @Override + public Void visit(TableSamplePipeOperator tableSample, Void context) { + return null; + } + + @Override + public Void visit(SetOperationPipeOperator setOperation, Void context) { + for (ParenthesedSelect select : setOperation.getSelects()) { + select.accept((SelectVisitor) this, context); + } + return null; + } + + @Override + public Void visit(UnPivotPipeOperator unPivot, Void context) { + for (SelectItem pivotColumn : unPivot.getPivotColumns()) { + pivotColumn.accept(this, context); + } + return null; + } + + @Override + public Void visit(WherePipeOperator where, Void context) { + where.getExpression().accept(this, context); + return null; + } + + @Override + public Void visit(WindowPipeOperator window, Void context) { + return visit((SelectPipeOperator) window, context); + } + @Override public Void visit(DateUnitExpression dateUnitExpression, S context) { return null; @@ -988,6 +1158,11 @@ 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); if (delete.getUsingFromItemList() != null) { @@ -1001,6 +1176,8 @@ public Void visit(Delete delete, S context) { if (delete.getWhere() != null) { delete.getWhere().accept(this, context); } + visitOutputClause(delete.getOutputClause(), context); + visitReturningClause(delete.getReturningClause(), context); return null; } @@ -1058,6 +1235,8 @@ public Void visit(Update update, S context) { if (update.getWhere() != null) { update.getWhere().accept(this, context); } + visitOutputClause(update.getOutputClause(), context); + visitReturningClause(update.getReturningClause(), context); return null; } @@ -1096,12 +1275,55 @@ public Void visit(Insert insert, S context) { withItem.accept((SelectVisitor) this, context); } } + if (insert.getSetUpdateSets() != null) { + visitUpdateSets(insert.getSetUpdateSets(), context); + } + if (insert.getDuplicateAction() != null) { + visitInsertAction(insert.getDuplicateAction(), context); + } + if (insert.getConflictAction() != null) { + visitInsertAction(insert.getConflictAction(), context); + } + visitOutputClause(insert.getOutputClause(), context); + visitReturningClause(insert.getReturningClause(), context); if (insert.getSelect() != null) { visit(insert.getSelect(), context); } return null; } + private void visitInsertAction(InsertDuplicateAction action, S context) { + visitUpdateSets(action.getUpdateSets(), context); + if (action.getWhereExpression() != null) { + action.getWhereExpression().accept(this, context); + } + } + + private void visitInsertAction(InsertConflictAction action, S context) { + visitUpdateSets(action.getUpdateSets(), context); + if (action.getWhereExpression() != null) { + action.getWhereExpression().accept(this, context); + } + } + + @Override + public Void visitOutputClause(OutputClause outputClause, S context) { + if (outputClause != null && outputClause.getSelectItemList() != null) { + for (SelectItem selectItem : outputClause.getSelectItemList()) { + selectItem.accept(this, context); + } + } + return null; + } + + private void visitReturningClause(ReturningClause returningClause, S context) { + if (returningClause != null) { + for (SelectItem selectItem : returningClause) { + selectItem.accept(this, context); + } + } + } + @Override public Void visit(ParenthesedInsert insert, S context) { return visit(insert.getInsert(), context); @@ -1315,6 +1537,53 @@ public Void visit(Merge merge, S context) { if (merge.getFromItem() != null) { merge.getFromItem().accept(this, context); } + + if (merge.getOnCondition() != null) { + merge.getOnCondition().accept(this, context); + } + + if (merge.getOperations() != null) { + for (MergeOperation operation : merge.getOperations()) { + operation.accept(this, context); + } + } + return null; + } + + @Override + public Void visit(MergeDelete mergeDelete, S context) { + if (mergeDelete.getAndPredicate() != null) { + mergeDelete.getAndPredicate().accept(this, context); + } + return null; + } + + @Override + public Void visit(MergeInsert mergeInsert, S context) { + if (mergeInsert.getAndPredicate() != null) { + mergeInsert.getAndPredicate().accept(this, context); + } + if (mergeInsert.getValues() != null) { + mergeInsert.getValues().accept(this, context); + } + if (mergeInsert.getWhereCondition() != null) { + mergeInsert.getWhereCondition().accept(this, context); + } + return null; + } + + @Override + public Void visit(MergeUpdate mergeUpdate, S context) { + if (mergeUpdate.getAndPredicate() != null) { + mergeUpdate.getAndPredicate().accept(this, context); + } + visitUpdateSets(mergeUpdate.getUpdateSets(), context); + if (mergeUpdate.getWhereCondition() != null) { + mergeUpdate.getWhereCondition().accept(this, context); + } + if (mergeUpdate.getDeleteWhereCondition() != null) { + mergeUpdate.getDeleteWhereCondition().accept(this, context); + } return null; } diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java index 8581a0e59..53a33e988 100644 --- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java +++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java @@ -773,10 +773,138 @@ void testJsonTable() throws JSQLParserException { @Test void testWindowExpressionWithNoRangeAndNoOffsetDoesNotThrowException() { - String sqlStr = "SELECT c, SUM(COUNT(*)) OVER (ORDER BY c ASC ROWS UNBOUNDED PRECEDING) FROM tbl GROUP BY c"; + String sqlStr = + "SELECT c, SUM(COUNT(*)) OVER (ORDER BY c ASC ROWS UNBOUNDED PRECEDING) FROM tbl GROUP BY c"; assertThatCode(() -> TablesNamesFinder.findTables(sqlStr)) .doesNotThrowAnyException(); } + @Test + void testPipedQuery() throws JSQLParserException { + String sqlStr = "FROM MY_TABLE1\n" + + "|> WHERE id IN (SELECT id FROM MY_TABLE2)\n" + + "|> LEFT JOIN (SELECT item, id FROM MY_TABLE3) AS t3 ON t3.item = item\n" + + "|> UNION ALL (SELECT * FROM MY_TABLE4)\n" + + "|> SELECT item"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2", "MY_TABLE3", "MY_TABLE4"); + } + + @Test + void testDeleteWithWithItemList() throws JSQLParserException { + String sqlStr = + "WITH cte AS (SELECT * FROM MY_TABLE2) DELETE FROM MY_TABLE1 WHERE id IN (SELECT id FROM cte)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testMergeOnConditionAndOperations() throws JSQLParserException { + String sqlStr = "MERGE INTO MY_TABLE1 t USING MY_TABLE2 s " + + "ON t.id IN (SELECT id FROM MY_TABLE3) " + + "WHEN MATCHED AND t.v > (SELECT MIN(v) FROM MY_TABLE4) THEN UPDATE SET t.v = (SELECT MAX(v) FROM MY_TABLE5) " + + "WHEN NOT MATCHED THEN INSERT (v) VALUES (1)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2", "MY_TABLE3", "MY_TABLE4", "MY_TABLE5"); + } + + @Test + void testInsertWithSetUpdateSets() throws JSQLParserException { + String sqlStr = "INSERT INTO MY_TABLE1 SET a = (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testInsertWithOnDuplicateKeyUpdate() throws JSQLParserException { + String sqlStr = + "INSERT INTO MY_TABLE1 (a) VALUES (1) ON DUPLICATE KEY UPDATE b = (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testInsertWithOnConflictDoUpdate() throws JSQLParserException { + String sqlStr = + "INSERT INTO MY_TABLE1 (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET b = (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testWithDataModifyingCte() throws JSQLParserException { + String sqlStr = + "WITH del AS (DELETE FROM MY_TABLE2) INSERT INTO MY_TABLE1 SELECT * FROM del"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testUpdateWithOutputClause() throws JSQLParserException { + String sqlStr = + "UPDATE MY_TABLE1 SET a = 1 OUTPUT (SELECT x FROM MY_TABLE2) INTO @tv"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testUpdateWithReturningClause() throws JSQLParserException { + String sqlStr = "UPDATE MY_TABLE1 SET a = 1 RETURNING (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testInsertWithReturningClause() throws JSQLParserException { + String sqlStr = + "INSERT INTO MY_TABLE1 (a) VALUES (1) RETURNING (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testDeleteWithReturningClause() throws JSQLParserException { + String sqlStr = "DELETE FROM MY_TABLE1 RETURNING (SELECT x FROM MY_TABLE2)"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testAnalyticFunctionsWithFunctionOrderBy() throws JSQLParserException { + String sqlStr = + "SELECT string_agg(name, ',' ORDER BY id) OVER (PARTITION BY grp) FROM MY_TABLE1"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1"); + } + + @Test + void testAnalyticFunctionsWithWindowOrderBy() throws JSQLParserException { + String sqlStr = + "SELECT SUM(v) OVER (ORDER BY (SELECT MAX(k) FROM MY_TABLE2)) FROM MY_TABLE1"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + + @Test + void testAnalyticFunctionsWithFilterClause() throws JSQLParserException { + String sqlStr = + "SELECT SUM(v) FILTER (WHERE id IN (SELECT id FROM MY_TABLE2)) OVER () FROM MY_TABLE1"; + + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2"); + } + }