From 107c08ddd6749384a3663b351b007c4b1a5cac9e Mon Sep 17 00:00:00 2001 From: dengliming Date: Wed, 4 Mar 2026 00:41:22 +0800 Subject: [PATCH 1/4] feat: add support for Oracle INSERT ALL/FIRST with WHEN branches --- .../jsqlparser/statement/insert/Insert.java | 77 +++++++ .../sf/jsqlparser/util/TablesNamesFinder.java | 21 +- .../util/deparser/InsertDeParser.java | 57 ++++++ .../validation/validator/InsertValidator.java | 26 ++- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 190 ++++++++++++++---- src/site/sphinx/unsupported.rst | 13 -- .../statement/insert/InsertTest.java | 76 ++++++- .../util/TablesNamesFinderTest.java | 19 ++ 8 files changed, 427 insertions(+), 52 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java index 6c53346d8..e2a545012 100644 --- a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java +++ b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java @@ -55,6 +55,9 @@ public class Insert implements Statement { private InsertConflictAction conflictAction; private InsertDuplicateAction duplicateAction; private Alias rowAlias; + private boolean oracleMultiInsert = false; + private boolean oracleMultiInsertFirst = false; + private List oracleMultiInsertBranches; public List getDuplicateUpdateSets() { if (duplicateAction != null) { @@ -97,6 +100,11 @@ public T accept(StatementVisitor statementVisitor, S context) { } public Table getTable() { + if (table == null && oracleMultiInsertBranches != null && !oracleMultiInsertBranches.isEmpty() + && oracleMultiInsertBranches.get(0).getClauses() != null + && !oracleMultiInsertBranches.get(0).getClauses().isEmpty()) { + return oracleMultiInsertBranches.get(0).getClauses().get(0).getTable(); + } return table; } @@ -270,6 +278,30 @@ public Insert withConflictAction(InsertConflictAction conflictAction) { return this; } + public boolean isOracleMultiInsert() { + return oracleMultiInsert; + } + + public void setOracleMultiInsert(boolean oracleMultiInsert) { + this.oracleMultiInsert = oracleMultiInsert; + } + + public boolean isOracleMultiInsertFirst() { + return oracleMultiInsertFirst; + } + + public void setOracleMultiInsertFirst(boolean oracleMultiInsertFirst) { + this.oracleMultiInsertFirst = oracleMultiInsertFirst; + } + + public List getOracleMultiInsertBranches() { + return oracleMultiInsertBranches; + } + + public void setOracleMultiInsertBranches(List oracleMultiInsertBranches) { + this.oracleMultiInsertBranches = oracleMultiInsertBranches; + } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public String toString() { @@ -295,6 +327,18 @@ public String toString() { if (modifierIgnore) { sql.append("IGNORE "); } + if (oracleMultiInsert) { + sql.append(oracleMultiInsertFirst ? "FIRST" : "ALL"); + if (oracleMultiInsertBranches != null && !oracleMultiInsertBranches.isEmpty()) { + for (OracleMultiInsertBranch branch : oracleMultiInsertBranches) { + appendOracleMultiInsertBranch(sql, branch); + } + } + if (select != null) { + sql.append(" ").append(select); + } + return sql.toString(); + } if (overwrite) { sql.append("OVERWRITE "); } else { @@ -424,4 +468,37 @@ public Alias getRowAlias() { public void setRowAlias(Alias rowAlias) { this.rowAlias = rowAlias; } + + public Insert withOracleMultiInsert(boolean oracleMultiInsert) { + this.setOracleMultiInsert(oracleMultiInsert); + return this; + } + + public Insert withOracleMultiInsertFirst(boolean oracleMultiInsertFirst) { + this.setOracleMultiInsertFirst(oracleMultiInsertFirst); + return this; + } + + public Insert withOracleMultiInsertBranches( + List oracleMultiInsertBranches) { + this.setOracleMultiInsertBranches(oracleMultiInsertBranches); + return this; + } + + private void appendOracleMultiInsertBranch(StringBuilder sql, + OracleMultiInsertBranch branch) { + if (branch == null || branch.getClauses() == null || branch.getClauses().isEmpty()) { + return; + } + + if (branch.getWhenExpression() != null) { + sql.append(" WHEN ").append(branch.getWhenExpression()).append(" THEN"); + } else if (branch.isElseClause()) { + sql.append(" ELSE"); + } + + for (OracleMultiInsertClause clause : branch.getClauses()) { + sql.append(" ").append(clause); + } + } } diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index e19524076..27e5c5264 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -105,6 +105,8 @@ 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.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; @@ -1051,7 +1053,24 @@ public void visit(Update update) { @Override public Void visit(Insert insert, S context) { - visit(insert.getTable(), context); + if (insert.isOracleMultiInsert() && insert.getOracleMultiInsertBranches() != null) { + for (OracleMultiInsertBranch branch : insert.getOracleMultiInsertBranches()) { + if (branch.getWhenExpression() != null) { + branch.getWhenExpression().accept(this, context); + } + if (branch.getClauses() == null) { + continue; + } + for (OracleMultiInsertClause clause : branch.getClauses()) { + visit(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); diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java index 41fd3fb22..8b942c150 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java @@ -14,6 +14,8 @@ import net.sf.jsqlparser.schema.Partition; import net.sf.jsqlparser.statement.insert.ConflictActionType; import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.OracleMultiInsertBranch; +import net.sf.jsqlparser.statement.insert.OracleMultiInsertClause; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.SelectVisitor; import net.sf.jsqlparser.statement.select.WithItem; @@ -63,6 +65,19 @@ public void deParse(Insert insert) { if (insert.isModifierIgnore()) { builder.append("IGNORE "); } + if (insert.isOracleMultiInsert()) { + builder.append(insert.isOracleMultiInsertFirst() ? "FIRST" : "ALL"); + if (insert.getOracleMultiInsertBranches() != null) { + for (OracleMultiInsertBranch branch : insert.getOracleMultiInsertBranches()) { + appendOracleMultiInsertBranch(branch); + } + } + if (insert.getSelect() != null) { + builder.append(" "); + insert.getSelect().accept(selectVisitor, null); + } + return; + } if (insert.isOverwrite()) { builder.append("OVERWRITE "); } else { @@ -158,4 +173,46 @@ public SelectVisitor getSelectVisitor() { public void setSelectVisitor(SelectVisitor visitor) { selectVisitor = visitor; } + + private void appendOracleIntoClause(OracleMultiInsertClause clause) { + builder.append("INTO ").append(clause.getTable().toString()); + if (clause.getColumns() != null && !clause.getColumns().isEmpty()) { + builder.append(" ("); + for (Iterator iter = clause.getColumns().iterator(); iter.hasNext();) { + Column column = iter.next(); + builder.append(column.getColumnName()); + if (iter.hasNext()) { + builder.append(", "); + } + } + builder.append(")"); + } + if (clause.getSelect() != null) { + builder.append(" "); + clause.getSelect().accept(selectVisitor, null); + } + } + + private void appendOracleMultiInsertBranch(OracleMultiInsertBranch branch) { + if (branch == null || branch.getClauses() == null || branch.getClauses().isEmpty()) { + return; + } + + if (branch.getWhenExpression() != null) { + builder.append(" WHEN "); + if (expressionVisitor != null) { + branch.getWhenExpression().accept(expressionVisitor, null); + } else { + builder.append(branch.getWhenExpression().toString()); + } + builder.append(" THEN"); + } else if (branch.isElseClause()) { + builder.append(" ELSE"); + } + + for (OracleMultiInsertClause clause : branch.getClauses()) { + builder.append(" "); + appendOracleIntoClause(clause); + } + } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java index c1186ba1a..e68b8e930 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java @@ -11,6 +11,8 @@ import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.insert.OracleMultiInsertBranch; +import net.sf.jsqlparser.statement.insert.OracleMultiInsertClause; import net.sf.jsqlparser.statement.select.Values; import net.sf.jsqlparser.statement.update.UpdateSet; import net.sf.jsqlparser.util.validation.ValidationCapability; @@ -41,8 +43,28 @@ public void validate(Insert insert) { Feature.insertReturningExpressionList); } - validateOptionalFromItem(insert.getTable()); - validateOptionalExpressions(insert.getColumns()); + if (insert.isOracleMultiInsert() && insert.getOracleMultiInsertBranches() != null) { + ExpressionValidator v = getValidator(ExpressionValidator.class); + for (OracleMultiInsertBranch branch : insert.getOracleMultiInsertBranches()) { + if (branch.getWhenExpression() != null) { + branch.getWhenExpression().accept(v, null); + } + if (branch.getClauses() == null) { + continue; + } + for (OracleMultiInsertClause clause : branch.getClauses()) { + validateOptionalFromItem(clause.getTable()); + validateOptionalExpressions(clause.getColumns()); + if (clause.getSelect() instanceof Values) { + clause.getSelect().accept(getValidator(StatementValidator.class), null); + validateOptionalExpressions(clause.getSelect().as(Values.class).getExpressions()); + } + } + } + } else { + validateOptionalFromItem(insert.getTable()); + validateOptionalExpressions(insert.getColumns()); + } if (insert.getSelect() instanceof Values) { insert.getSelect().accept(getValidator(StatementValidator.class), null); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 800bc0b61..06f364fea 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -2819,6 +2819,10 @@ Insert Insert(): InsertConflictAction conflictAction = null; InsertDuplicateAction duplicateAction = null; + Token multiInsertToken = null; + List oracleMultiInsertBranches = new ArrayList(); + OracleMultiInsertClause oracleMultiInsertClause = null; + OracleMultiInsertBranch oracleMultiInsertBranch = null; } { { insert.setOracleHint(getOracleHint()); } @@ -2831,50 +2835,101 @@ Insert Insert(): } ] [ LOOKAHEAD(2) { modifierIgnore = true; }] - [ LOOKAHEAD(2) ( - { insert.setOverwrite(true); insert.setTableKeyword(true); } - | [ LOOKAHEAD(2) { insert.setTableKeyword(true); }] + ( + LOOKAHEAD(2, ( | ) ( | )) + ( + (multiInsertToken = | multiInsertToken = ) { + insert.setOracleMultiInsert(true); + insert.setOracleMultiInsertFirst(multiInsertToken.kind == K_FIRST); + } + ( + { + oracleMultiInsertBranch = new OracleMultiInsertBranch(); + } + oracleMultiInsertClause = OracleMultiInsertClause() { + oracleMultiInsertBranch.addClause(oracleMultiInsertClause); + table = oracleMultiInsertClause.getTable(); + } + ( + oracleMultiInsertClause = OracleMultiInsertClause() { + oracleMultiInsertBranch.addClause(oracleMultiInsertClause); + } + )* + { + oracleMultiInsertBranches.add(oracleMultiInsertBranch); + } + | + ( + ( + oracleMultiInsertBranch = OracleMultiInsertWhenBranch() { + if (table == null && !oracleMultiInsertBranch.getClauses().isEmpty()) { + table = oracleMultiInsertBranch.getClauses().get(0).getTable(); + } + oracleMultiInsertBranches.add(oracleMultiInsertBranch); + } + )+ + [ + oracleMultiInsertBranch = OracleMultiInsertElseBranch() { + if (table == null && !oracleMultiInsertBranch.getClauses().isEmpty()) { + table = oracleMultiInsertBranch.getClauses().get(0).getTable(); + } + oracleMultiInsertBranches.add(oracleMultiInsertBranch); + } + ] + ) + ) + select = Select() { + insert.setOracleMultiInsertBranches(oracleMultiInsertBranches); + } ) - ] table=Table() - [ LOOKAHEAD(2) "(" partitions=Partitions() ")" ] + | + ( + [ LOOKAHEAD(2) ( + { insert.setOverwrite(true); insert.setTableKeyword(true); } + | [ LOOKAHEAD(2) { insert.setTableKeyword(true); }] + ) + ] table=Table() + [ LOOKAHEAD(2) "(" partitions=Partitions() ")" ] - [ LOOKAHEAD(2) [ { useAs = true; } ] name=RelObjectNameWithoutValue() { table.setAlias(new Alias(name,useAs)); }] + [ LOOKAHEAD(2) [ { useAs = true; } ] name=RelObjectNameWithoutValue() { table.setAlias(new Alias(name,useAs)); }] - [ LOOKAHEAD(2) "(" columns=ColumnList() ")" ] + [ LOOKAHEAD(2) "(" columns=ColumnList() ")" ] - [ LOOKAHEAD(2) { insert.setOverriding(true); } ] + [ LOOKAHEAD(2) { insert.setOverriding(true); } ] - [ outputClause = OutputClause() { insert.setOutputClause(outputClause); } ] + [ outputClause = OutputClause() { insert.setOutputClause(outputClause); } ] - ( - { insert.setOnlyDefaultValues(true); } - | - ( - updateSets = UpdateSets() { insert.withSetUpdateSets(updateSets); useSet = true; } - ) - | - select = Select() - ) + ( + { insert.setOnlyDefaultValues(true); } + | + ( + updateSets = UpdateSets() { insert.withSetUpdateSets(updateSets); useSet = true; } + ) + | + select = Select() + ) - [ LOOKAHEAD(2, { select instanceof Values || useSet }) rowAlias = Alias() { - if (select instanceof Values) { - select.setAlias(rowAlias); - } else { - insert.setRowAlias(rowAlias); - } - } ] + [ LOOKAHEAD(2, { select instanceof Values || useSet }) rowAlias = Alias() { + if (select instanceof Values) { + select.setAlias(rowAlias); + } else { + insert.setRowAlias(rowAlias); + } + } ] - [ LOOKAHEAD(2) - duplicateAction = InsertDuplicateAction() { insert.setDuplicateAction(duplicateAction); } - ] + [ LOOKAHEAD(2) + duplicateAction = InsertDuplicateAction() { insert.setDuplicateAction(duplicateAction); } + ] - [ - - [ conflictTarget = InsertConflictTarget() ] - conflictAction = InsertConflictAction() { insert.withConflictTarget(conflictTarget).setConflictAction(conflictAction); } - ] + [ + + [ conflictTarget = InsertConflictTarget() ] + conflictAction = InsertConflictAction() { insert.withConflictTarget(conflictTarget).setConflictAction(conflictAction); } + ] - [ returningClause = ReturningClause() { insert.setReturningClause(returningClause); } ] + [ returningClause = ReturningClause() { insert.setReturningClause(returningClause); } ] + ) + ) { if (!columns.isEmpty()) { @@ -2891,6 +2946,73 @@ Insert Insert(): } } +OracleMultiInsertClause OracleMultiInsertClause(): +{ + OracleMultiInsertClause clause = new OracleMultiInsertClause(); + Table clauseTable = null; + ExpressionList clauseColumns = new ExpressionList(); + Select clauseSelect = null; +} +{ + clauseTable=Table() + [ LOOKAHEAD(2) "(" clauseColumns=ColumnList() ")" ] + clauseSelect = Select() + { + if (!clauseColumns.isEmpty()) { + clause.setColumns(clauseColumns); + } + return clause.withTable(clauseTable).withSelect(clauseSelect); + } +} + +OracleMultiInsertBranch OracleMultiInsertWhenBranch(): +{ + Expression whenExpression = null; + List clauses = null; +} +{ + whenExpression = Expression() + clauses = OracleMultiInsertClauseList() + { + return new OracleMultiInsertBranch() + .withWhenExpression(whenExpression) + .withClauses(clauses); + } +} + +OracleMultiInsertBranch OracleMultiInsertElseBranch(): +{ + List clauses = null; +} +{ + + clauses = OracleMultiInsertClauseList() + { + return new OracleMultiInsertBranch() + .withElseClause(true) + .withClauses(clauses); + } +} + +List OracleMultiInsertClauseList(): +{ + List clauses = new ArrayList(); + OracleMultiInsertClause clause = null; +} +{ + clause = OracleMultiInsertClause() { + clauses.add(clause); + } + ( + clause = OracleMultiInsertClause() { + clauses.add(clause); + } + )* + { + return clauses; + } +} + InsertConflictTarget InsertConflictTarget(): { String indexColumnName; diff --git a/src/site/sphinx/unsupported.rst b/src/site/sphinx/unsupported.rst index b0ad1bc0c..c231dbe81 100644 --- a/src/site/sphinx/unsupported.rst +++ b/src/site/sphinx/unsupported.rst @@ -16,18 +16,6 @@ We would like to recommend writing portable, standard compliant SQL in general. dbms_output.put_line('The number is ' || num); END; - - -- Oracle `INSERT ALL ...` is not supported - - .. code-block:: sql - - INSERT ALL - INTO mytable (column1, column2, column_n) VALUES (expr1, expr2, expr_n) - INTO mytable (column1, column2, column_n) VALUES (expr1, expr2, expr_n) - INTO mytable (column1, column2, column_n) VALUES (expr1, expr2, expr_n) - SELECT * FROM dual; - - DDL statements While *JSQLParser* provides a lot of generic support for DDL statements, it is possible that certain RDBMS specific syntax (especially about indices, encodings, compression) won't be supported. @@ -42,4 +30,3 @@ We would like to recommend writing portable, standard compliant SQL in general. - diff --git a/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java b/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java index cd73f7dd8..2c2759546 100644 --- a/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java @@ -239,14 +239,86 @@ public void execute() throws Throwable { } @Test - @Disabled public void testOracleInsertMultiRowValue() throws JSQLParserException { String sqlStr = "INSERT ALL\n" + " INTO suppliers (supplier_id, supplier_name) VALUES (1000, 'IBM')\n" + " INTO suppliers (supplier_id, supplier_name) VALUES (2000, 'Microsoft')\n" + " INTO suppliers (supplier_id, supplier_name) VALUES (3000, 'Google')\n" + "SELECT * FROM dual;"; - assertSqlCanBeParsedAndDeparsed(sqlStr, true); + Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true); + assertTrue(insert.isOracleMultiInsert()); + assertFalse(insert.isOracleMultiInsertFirst()); + assertEquals(1, insert.getOracleMultiInsertBranches().size()); + assertEquals(3, insert.getOracleMultiInsertBranches().get(0).getClauses().size()); + assertEquals("suppliers", + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable().toString()); + assertEquals("supplier_id, supplier_name", + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getColumns().toString()); + assertEquals("VALUES (1000, 'IBM')", + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getSelect().toString()); + assertEquals("SELECT * FROM dual", insert.getSelect().toString()); + } + + @Test + public void testOracleInsertAllWithJdbcParameters() throws JSQLParserException { + String sqlStr = "INSERT ALL INTO spm_message (xx, xx) VALUES (?, ?) SELECT * FROM dual"; + Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true); + assertTrue(insert.isOracleMultiInsert()); + assertFalse(insert.isOracleMultiInsertFirst()); + assertEquals(1, insert.getOracleMultiInsertBranches().size()); + assertNull(insert.getOracleMultiInsertBranches().get(0).getWhenExpression()); + assertFalse(insert.getOracleMultiInsertBranches().get(0).isElseClause()); + assertEquals(1, insert.getOracleMultiInsertBranches().get(0).getClauses().size()); + assertEquals("spm_message", + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable().toString()); + assertEquals("VALUES (?, ?)", + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getSelect().toString()); + } + + @Test + public void testOracleInsertAllWithWhenElse() throws JSQLParserException { + String sqlStr = + "INSERT ALL WHEN qty > 10 THEN INTO big_orders (id) VALUES (id) " + + "WHEN qty > 0 THEN INTO small_orders (id) VALUES (id) " + + "ELSE INTO invalid_orders (id) VALUES (id) " + + "SELECT id, qty FROM orders"; + + Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true); + assertTrue(insert.isOracleMultiInsert()); + assertFalse(insert.isOracleMultiInsertFirst()); + assertEquals(3, insert.getOracleMultiInsertBranches().size()); + assertEquals("qty > 10", + insert.getOracleMultiInsertBranches().get(0).getWhenExpression().toString()); + assertEquals("qty > 0", + insert.getOracleMultiInsertBranches().get(1).getWhenExpression().toString()); + assertTrue(insert.getOracleMultiInsertBranches().get(2).isElseClause()); + assertEquals(1, insert.getOracleMultiInsertBranches().get(0).getClauses().size()); + assertEquals(1, insert.getOracleMultiInsertBranches().get(1).getClauses().size()); + assertEquals(1, insert.getOracleMultiInsertBranches().get(2).getClauses().size()); + } + + @Test + public void testOracleInsertFirstWithWhenMultipleInto() throws JSQLParserException { + String sqlStr = + "INSERT FIRST WHEN region = 'APAC' THEN INTO apac_orders (id) VALUES (id) " + + "INTO apac_audit (id) VALUES (id) " + + "ELSE INTO other_orders (id) VALUES (id) " + + "SELECT id, region FROM orders"; + + Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true); + assertTrue(insert.isOracleMultiInsert()); + assertTrue(insert.isOracleMultiInsertFirst()); + assertEquals(2, insert.getOracleMultiInsertBranches().size()); + assertEquals(2, insert.getOracleMultiInsertBranches().get(0).getClauses().size()); + assertEquals("region = 'APAC'", + insert.getOracleMultiInsertBranches().get(0).getWhenExpression().toString()); + assertTrue(insert.getOracleMultiInsertBranches().get(1).isElseClause()); + assertEquals("apac_orders", + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable().toString()); + assertEquals("apac_audit", + insert.getOracleMultiInsertBranches().get(0).getClauses().get(1).getTable().toString()); + assertEquals("other_orders", + insert.getOracleMultiInsertBranches().get(1).getClauses().get(0).getTable().toString()); } @Test diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java index ff629a2e8..98a07693a 100644 --- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java +++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java @@ -116,6 +116,25 @@ public void testGetTablesFromInsertValues() throws Exception { assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1"); } + @Test + public void testGetTablesFromOracleInsertAll() throws Exception { + String sqlStr = + "INSERT ALL INTO MY_TABLE1 (a) VALUES (1) INTO MY_TABLE2 (a) VALUES (2) SELECT * FROM dual"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2", "dual"); + } + + @Test + public void testGetTablesFromOracleInsertAllWhenElse() throws Exception { + String sqlStr = + "INSERT ALL WHEN EXISTS (SELECT 1 FROM CHECK_TABLE c WHERE c.id = s.id) " + + "THEN INTO MY_TABLE1 (a) VALUES (a) " + + "ELSE INTO MY_TABLE2 (a) VALUES (a) " + + "SELECT a, id FROM SOURCE_TABLE s"; + assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("MY_TABLE1", + "MY_TABLE2", "CHECK_TABLE", "SOURCE_TABLE"); + } + @Test public void testGetTablesFromReplace() throws Exception { String sqlStr = "REPLACE INTO MY_TABLE1 (a) VALUES ((SELECT a from MY_TABLE2 WHERE a = 1))"; From 9287a084ff360f6cc9933449786ea3875ab76070 Mon Sep 17 00:00:00 2001 From: dengliming Date: Wed, 4 Mar 2026 00:41:43 +0800 Subject: [PATCH 2/4] feat: add support for Oracle INSERT ALL/FIRST with WHEN branches --- .../insert/OracleMultiInsertBranch.java | 75 ++++++++++++++++ .../insert/OracleMultiInsertClause.java | 88 +++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertBranch.java create mode 100644 src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertClause.java diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertBranch.java b/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertBranch.java new file mode 100644 index 000000000..4e9d8ef3e --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertBranch.java @@ -0,0 +1,75 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.insert; + +import net.sf.jsqlparser.expression.Expression; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class OracleMultiInsertBranch implements Serializable { + + private Expression whenExpression; + private boolean elseClause; + private List clauses = new ArrayList<>(); + + public Expression getWhenExpression() { + return whenExpression; + } + + public void setWhenExpression(Expression whenExpression) { + this.whenExpression = whenExpression; + if (whenExpression != null) { + this.elseClause = false; + } + } + + public boolean isElseClause() { + return elseClause; + } + + public void setElseClause(boolean elseClause) { + this.elseClause = elseClause; + if (elseClause) { + this.whenExpression = null; + } + } + + public List getClauses() { + return clauses; + } + + public void setClauses(List clauses) { + this.clauses = clauses == null ? new ArrayList<>() : clauses; + } + + public void addClause(OracleMultiInsertClause clause) { + if (clause == null) { + return; + } + clauses.add(clause); + } + + public OracleMultiInsertBranch withWhenExpression(Expression whenExpression) { + this.setWhenExpression(whenExpression); + return this; + } + + public OracleMultiInsertBranch withElseClause(boolean elseClause) { + this.setElseClause(elseClause); + return this; + } + + public OracleMultiInsertBranch withClauses(List clauses) { + this.setClauses(clauses); + return this; + } +} diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertClause.java b/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertClause.java new file mode 100644 index 000000000..fb5589416 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertClause.java @@ -0,0 +1,88 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.insert; + +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.Select; + +import java.io.Serializable; +import java.util.Iterator; + +public class OracleMultiInsertClause implements Serializable { + + private Table table; + private ExpressionList columns; + private Select select; + + public Table getTable() { + return table; + } + + public void setTable(Table table) { + this.table = table; + } + + public ExpressionList getColumns() { + return columns; + } + + public void setColumns(ExpressionList columns) { + this.columns = columns; + } + + public Select getSelect() { + return select; + } + + public void setSelect(Select select) { + this.select = select; + } + + @Override + public String toString() { + StringBuilder sql = new StringBuilder("INTO "); + sql.append(table); + + if (columns != null && !columns.isEmpty()) { + sql.append(" ("); + for (Iterator iter = columns.iterator(); iter.hasNext();) { + Column column = iter.next(); + sql.append(column.getColumnName()); + if (iter.hasNext()) { + sql.append(", "); + } + } + sql.append(")"); + } + + if (select != null) { + sql.append(" ").append(select); + } + + return sql.toString(); + } + + public OracleMultiInsertClause withTable(Table table) { + this.setTable(table); + return this; + } + + public OracleMultiInsertClause withColumns(ExpressionList columns) { + this.setColumns(columns); + return this; + } + + public OracleMultiInsertClause withSelect(Select select) { + this.setSelect(select); + return this; + } +} From d2a802e8800b4d5f06b5bef609f7b644c020beb6 Mon Sep 17 00:00:00 2001 From: dengliming Date: Wed, 4 Mar 2026 00:42:42 +0800 Subject: [PATCH 3/4] fix --- .../jsqlparser/statement/insert/Insert.java | 17 ++++++------- .../insert/OracleMultiInsertBranch.java | 3 +-- .../insert/OracleMultiInsertClause.java | 5 ++-- .../sf/jsqlparser/util/TablesNamesFinder.java | 11 ++++----- .../util/deparser/InsertDeParser.java | 3 +-- .../validation/validator/InsertValidator.java | 3 ++- .../statement/insert/InsertTest.java | 24 ++++++++++++------- .../util/TablesNamesFinderTest.java | 21 ++++++++-------- 8 files changed, 46 insertions(+), 41 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java index e2a545012..ee2a71f7a 100644 --- a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java +++ b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java @@ -9,6 +9,11 @@ */ package net.sf.jsqlparser.statement.insert; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.OracleHint; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; @@ -26,12 +31,6 @@ import net.sf.jsqlparser.statement.select.WithItem; import net.sf.jsqlparser.statement.update.UpdateSet; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; - @SuppressWarnings({"PMD.CyclomaticComplexity"}) public class Insert implements Statement { @@ -100,7 +99,8 @@ public T accept(StatementVisitor statementVisitor, S context) { } public Table getTable() { - if (table == null && oracleMultiInsertBranches != null && !oracleMultiInsertBranches.isEmpty() + if (table == null && oracleMultiInsertBranches != null + && !oracleMultiInsertBranches.isEmpty() && oracleMultiInsertBranches.get(0).getClauses() != null && !oracleMultiInsertBranches.get(0).getClauses().isEmpty()) { return oracleMultiInsertBranches.get(0).getClauses().get(0).getTable(); @@ -298,7 +298,8 @@ public List getOracleMultiInsertBranches() { return oracleMultiInsertBranches; } - public void setOracleMultiInsertBranches(List oracleMultiInsertBranches) { + public void setOracleMultiInsertBranches( + List oracleMultiInsertBranches) { this.oracleMultiInsertBranches = oracleMultiInsertBranches; } diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertBranch.java b/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertBranch.java index 4e9d8ef3e..64cdb1696 100644 --- a/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertBranch.java +++ b/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertBranch.java @@ -9,11 +9,10 @@ */ package net.sf.jsqlparser.statement.insert; -import net.sf.jsqlparser.expression.Expression; - import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import net.sf.jsqlparser.expression.Expression; public class OracleMultiInsertBranch implements Serializable { diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertClause.java b/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertClause.java index fb5589416..1a7bdc6d3 100644 --- a/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertClause.java +++ b/src/main/java/net/sf/jsqlparser/statement/insert/OracleMultiInsertClause.java @@ -9,14 +9,13 @@ */ package net.sf.jsqlparser.statement.insert; +import java.io.Serializable; +import java.util.Iterator; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.Select; -import java.io.Serializable; -import java.util.Iterator; - public class OracleMultiInsertClause implements Serializable { private Table table; diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 27e5c5264..66405c983 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -9,6 +9,11 @@ */ package net.sf.jsqlparser.util; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; @@ -140,12 +145,6 @@ import net.sf.jsqlparser.statement.update.UpdateSet; import net.sf.jsqlparser.statement.upsert.Upsert; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - /** * Find all used tables within an select statement. diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java index 8b942c150..901e32865 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.util.deparser; +import java.util.Iterator; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Partition; @@ -20,8 +21,6 @@ import net.sf.jsqlparser.statement.select.SelectVisitor; import net.sf.jsqlparser.statement.select.WithItem; -import java.util.Iterator; - public class InsertDeParser extends AbstractDeParser { private ExpressionVisitor expressionVisitor; diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java index e68b8e930..99f6ad3ea 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/InsertValidator.java @@ -57,7 +57,8 @@ public void validate(Insert insert) { validateOptionalExpressions(clause.getColumns()); if (clause.getSelect() instanceof Values) { clause.getSelect().accept(getValidator(StatementValidator.class), null); - validateOptionalExpressions(clause.getSelect().as(Values.class).getExpressions()); + validateOptionalExpressions( + clause.getSelect().as(Values.class).getExpressions()); } } } diff --git a/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java b/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java index 2c2759546..1e5c684a1 100644 --- a/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java @@ -251,11 +251,14 @@ public void testOracleInsertMultiRowValue() throws JSQLParserException { assertEquals(1, insert.getOracleMultiInsertBranches().size()); assertEquals(3, insert.getOracleMultiInsertBranches().get(0).getClauses().size()); assertEquals("suppliers", - insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable().toString()); + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable() + .toString()); assertEquals("supplier_id, supplier_name", - insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getColumns().toString()); + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getColumns() + .toString()); assertEquals("VALUES (1000, 'IBM')", - insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getSelect().toString()); + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getSelect() + .toString()); assertEquals("SELECT * FROM dual", insert.getSelect().toString()); } @@ -270,9 +273,11 @@ public void testOracleInsertAllWithJdbcParameters() throws JSQLParserException { assertFalse(insert.getOracleMultiInsertBranches().get(0).isElseClause()); assertEquals(1, insert.getOracleMultiInsertBranches().get(0).getClauses().size()); assertEquals("spm_message", - insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable().toString()); + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable() + .toString()); assertEquals("VALUES (?, ?)", - insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getSelect().toString()); + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getSelect() + .toString()); } @Test @@ -314,11 +319,14 @@ public void testOracleInsertFirstWithWhenMultipleInto() throws JSQLParserExcepti insert.getOracleMultiInsertBranches().get(0).getWhenExpression().toString()); assertTrue(insert.getOracleMultiInsertBranches().get(1).isElseClause()); assertEquals("apac_orders", - insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable().toString()); + insert.getOracleMultiInsertBranches().get(0).getClauses().get(0).getTable() + .toString()); assertEquals("apac_audit", - insert.getOracleMultiInsertBranches().get(0).getClauses().get(1).getTable().toString()); + insert.getOracleMultiInsertBranches().get(0).getClauses().get(1).getTable() + .toString()); assertEquals("other_orders", - insert.getOracleMultiInsertBranches().get(1).getClauses().get(0).getTable().toString()); + insert.getOracleMultiInsertBranches().get(1).getClauses().get(0).getTable() + .toString()); } @Test diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java index 98a07693a..1180417fb 100644 --- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java +++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java @@ -9,6 +9,16 @@ */ package net.sf.jsqlparser.util; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.OracleHint; import net.sf.jsqlparser.parser.CCJSqlParserUtil; @@ -21,17 +31,6 @@ import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - public class TablesNamesFinderTest { @Test From b66245b7e82f0e496f3eae0c9222fd3049ef3335 Mon Sep 17 00:00:00 2001 From: dengliming Date: Wed, 4 Mar 2026 00:54:46 +0800 Subject: [PATCH 4/4] polish --- .../jsqlparser/statement/insert/Insert.java | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java index ee2a71f7a..4920aec62 100644 --- a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java +++ b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java @@ -307,6 +307,17 @@ public void setOracleMultiInsertBranches( @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public String toString() { StringBuilder sql = new StringBuilder(); + appendWithItems(sql); + appendInsertPrefix(sql); + if (appendOracleMultiInsert(sql)) { + return sql.toString(); + } + appendInsertTargetAndValues(sql); + appendInsertActions(sql); + return sql.toString(); + } + + private void appendWithItems(StringBuilder sql) { if (withItemsList != null && !withItemsList.isEmpty()) { sql.append("WITH "); for (Iterator> iter = withItemsList.iterator(); iter.hasNext();) { @@ -318,6 +329,9 @@ public String toString() { sql.append(" "); } } + } + + private void appendInsertPrefix(StringBuilder sql) { sql.append("INSERT "); if (oracleHint != null) { sql.append(oracleHint).append(" "); @@ -328,18 +342,26 @@ public String toString() { if (modifierIgnore) { sql.append("IGNORE "); } - if (oracleMultiInsert) { - sql.append(oracleMultiInsertFirst ? "FIRST" : "ALL"); - if (oracleMultiInsertBranches != null && !oracleMultiInsertBranches.isEmpty()) { - for (OracleMultiInsertBranch branch : oracleMultiInsertBranches) { - appendOracleMultiInsertBranch(sql, branch); - } - } - if (select != null) { - sql.append(" ").append(select); + } + + private boolean appendOracleMultiInsert(StringBuilder sql) { + if (!oracleMultiInsert) { + return false; + } + + sql.append(oracleMultiInsertFirst ? "FIRST" : "ALL"); + if (oracleMultiInsertBranches != null && !oracleMultiInsertBranches.isEmpty()) { + for (OracleMultiInsertBranch branch : oracleMultiInsertBranches) { + appendOracleMultiInsertBranch(sql, branch); } - return sql.toString(); } + if (select != null) { + sql.append(" ").append(select); + } + return true; + } + + private void appendInsertTargetAndValues(StringBuilder sql) { if (overwrite) { sql.append("OVERWRITE "); } else { @@ -383,10 +405,12 @@ public String toString() { if (select != null) { sql.append(select); } + } + private void appendInsertActions(StringBuilder sql) { if (setUpdateSets != null && !setUpdateSets.isEmpty()) { sql.append("SET "); - sql = UpdateSet.appendUpdateSetsTo(sql, setUpdateSets); + UpdateSet.appendUpdateSetsTo(sql, setUpdateSets); if (rowAlias != null) { sql.append(" ").append(rowAlias); } @@ -409,8 +433,6 @@ public String toString() { if (returningClause != null) { returningClause.appendTo(sql); } - - return sql.toString(); } public Insert withWithItemsList(List> withList) {