From bc9370b3be83568d12092c97ccc34b621338c2e7 Mon Sep 17 00:00:00 2001 From: Minjae Lee Date: Fri, 4 Sep 2026 18:42:31 +0900 Subject: [PATCH 1/2] feat(parser): model MySQL DROP object lists --- .../statement/StatementVisitorAdapter.java | 2 +- .../sf/jsqlparser/statement/drop/Drop.java | 63 +++++++++++++++++-- .../sf/jsqlparser/util/TablesNamesFinder.java | 2 +- .../util/deparser/DropDeParser.java | 4 +- .../validation/validator/DropValidator.java | 2 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 7 +++ .../jsqlparser/statement/drop/DropTest.java | 44 +++++++++++++ 7 files changed, 115 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java index 38a32ea1f5..4193645b45 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java @@ -239,7 +239,7 @@ public T visit(ParenthesedInsert insert, S context) { @Override public T visit(Drop drop, S context) { if (drop.getType().equalsIgnoreCase("table")) { - fromItemVisitor.visitFromItem(drop.getName(), context); + drop.getNames().forEach(name -> fromItemVisitor.visitFromItem(name, context)); } // @todo: handle schemas diff --git a/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java b/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java index a7ac362535..f12e07a5ce 100644 --- a/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java +++ b/src/main/java/net/sf/jsqlparser/statement/drop/Drop.java @@ -14,8 +14,10 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.Statement; @@ -24,8 +26,13 @@ public class Drop implements Statement { + public enum ObjectType { + DATABASE, EVENT, FUNCTION, INDEX, PROCEDURE, SCHEMA, SEQUENCE, SERVER, TABLE, TABLESPACE, TRIGGER, VIEW, OTHER + } + private String type; - private Table name; + private ObjectType objectType = ObjectType.OTHER; + private final List names = new ArrayList<>(); private List parameters; private Map> typeToParameters = new HashMap<>(); private boolean ifExists = false; @@ -46,11 +53,25 @@ public T accept(StatementVisitor statementVisitor, S context) { } public Table getName() { - return name; + return names.isEmpty() ? null : names.get(0); + } + + public void setName(Table name) { + names.clear(); + if (name != null) { + names.add(name); + } + } + + public List
getNames() { + return Collections.unmodifiableList(names); } - public void setName(Table string) { - name = string; + public void setNames(Collection names) { + this.names.clear(); + if (names != null) { + this.names.addAll(names); + } } public List getParameters() { @@ -67,6 +88,22 @@ public String getType() { public void setType(String string) { type = string; + try { + objectType = ObjectType.valueOf(string.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException | NullPointerException ignored) { + objectType = ObjectType.OTHER; + } + } + + public ObjectType getObjectType() { + return objectType; + } + + public void setObjectType(ObjectType objectType) { + this.objectType = objectType == null ? ObjectType.OTHER : objectType; + if (this.objectType != ObjectType.OTHER) { + this.type = this.objectType.name(); + } } public boolean isIfExists() { @@ -112,7 +149,8 @@ public String toString() { + (isUsingTemporary ? "TEMPORARY " : "") + (materialized ? "MATERIALIZED " : "") + type + " " - + (ifExists ? "IF EXISTS " : "") + name.toString(); + + (ifExists ? "IF EXISTS " : "") + names.stream().map(Table::toString) + .collect(Collectors.joining(", ")); if (type.equals("FUNCTION")) { sql += formatFuncParams(getParamsByType("FUNCTION")); @@ -149,6 +187,21 @@ public Drop withName(Table name) { return this; } + public Drop withNames(Collection names) { + setNames(names); + return this; + } + + public Drop addNames(Table... names) { + Collections.addAll(this.names, names); + return this; + } + + public Drop withObjectType(ObjectType objectType) { + setObjectType(objectType); + return this; + } + public Drop withParameters(List parameters) { this.setParameters(parameters); return this; diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index b3d1463d03..89f8845e62 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -1502,7 +1502,7 @@ public void visit(Analyze analyze) { @Override public Void visit(Drop drop, S context) { - visit(drop.getName(), context); + drop.getNames().forEach(name -> visit(name, context)); return null; } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java index 64c39b5051..32823a484c 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.util.deparser; +import java.util.stream.Collectors; import net.sf.jsqlparser.statement.drop.Drop; import net.sf.jsqlparser.statement.select.PlainSelect; @@ -32,7 +33,8 @@ public void deParse(Drop drop) { builder.append(" IF EXISTS"); } - builder.append(" ").append(drop.getName()); + builder.append(" ").append(drop.getNames().stream().map(Object::toString) + .collect(Collectors.joining(", "))); if (drop.getType().equals("FUNCTION")) { builder.append(Drop.formatFuncParams(drop.getParamsByType("FUNCTION"))); diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java index 04ea1cf8f9..ec87a52d48 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java @@ -47,7 +47,7 @@ public void validate(Drop drop) { NamedObject named = NamedObject.forName(type); if (Arrays.asList(NamedObject.table, NamedObject.view).contains(named)) { - validateName(named, drop.getName().getFullyQualifiedName()); + drop.getNames().forEach(name -> validateName(named, name.getFullyQualifiedName())); } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 52994191b4..6dcea91aa3 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -12400,10 +12400,16 @@ Drop Drop(): | tk= | + tk= + | + tk= + | tk= | tk= | + tk= + | tk= ) { drop.setType(tk.image); } @@ -12411,6 +12417,7 @@ Drop Drop(): [ LOOKAHEAD(2) {drop.setIfExists(true);} ] name = Table() { drop.setName(name); } + ( "," name = Table() { drop.addNames(name); } )* [ LOOKAHEAD(2) funcArgs = FuncArgsList() ] ( ( diff --git a/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java b/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java index a692273f26..a73a4b9bbd 100644 --- a/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java @@ -10,6 +10,7 @@ package net.sf.jsqlparser.statement.drop; import java.io.StringReader; +import java.util.List; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.parser.CCJSqlParserUtil; @@ -169,4 +170,47 @@ public void testDropTableFollowedByLockTableIssue2490() throws JSQLParserExcepti "DROP TABLE t1; LOCK TABLE t2 IN SHARE MODE;"); assertEquals(2, statements.size()); } + + @Test + public void testMySqlDropObjectTypes() throws JSQLParserException { + Drop database = (Drop) assertSqlCanBeParsedAndDeparsed( + "DROP DATABASE IF EXISTS database_1"); + assertEquals(Drop.ObjectType.DATABASE, database.getObjectType()); + + Drop procedure = (Drop) assertSqlCanBeParsedAndDeparsed( + "DROP PROCEDURE IF EXISTS procedure_1"); + assertEquals(Drop.ObjectType.PROCEDURE, procedure.getObjectType()); + + Drop trigger = (Drop) assertSqlCanBeParsedAndDeparsed( + "DROP TRIGGER IF EXISTS schema_1.trigger_1"); + assertEquals(Drop.ObjectType.TRIGGER, trigger.getObjectType()); + assertEquals("schema_1.trigger_1", trigger.getName().getFullyQualifiedName()); + } + + @Test + public void testMySqlDropMultipleTables() throws JSQLParserException { + String sql = "DROP TABLE IF EXISTS table_1, table_2, table_3 RESTRICT"; + Drop drop = (Drop) assertSqlCanBeParsedAndDeparsed(sql); + + assertEquals(List.of("table_1", "table_2", "table_3"), drop.getNames().stream() + .map(Table::getFullyQualifiedName).toList()); + assertEquals("table_1", drop.getName().getFullyQualifiedName()); + assertEquals(List.of("RESTRICT"), drop.getParameters()); + + assertDeparse(new Drop().withObjectType(Drop.ObjectType.TABLE).withIfExists(true) + .withNames(List.of(new Table("table_1"), new Table("table_2"), + new Table("table_3"))) + .addParameters("RESTRICT"), sql); + } + + @Test + public void testMySqlDropMultipleViews() throws JSQLParserException { + Drop drop = (Drop) assertSqlCanBeParsedAndDeparsed( + "DROP VIEW IF EXISTS view_1, view_2 CASCADE"); + + assertEquals(Drop.ObjectType.VIEW, drop.getObjectType()); + assertEquals(List.of("view_1", "view_2"), drop.getNames().stream() + .map(Table::getFullyQualifiedName).toList()); + assertEquals(List.of("CASCADE"), drop.getParameters()); + } } From 53b97b844b7cf63d107539dcb0fdab03b2b5f33d Mon Sep 17 00:00:00 2001 From: Minjae Lee Date: Fri, 4 Sep 2026 19:08:17 +0900 Subject: [PATCH 2/2] test: keep DROP assertions Java 11 compatible --- src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java b/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java index a73a4b9bbd..a2a25ab6f4 100644 --- a/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/drop/DropTest.java @@ -11,6 +11,7 @@ import java.io.StringReader; import java.util.List; +import java.util.stream.Collectors; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.parser.CCJSqlParserUtil; @@ -193,7 +194,7 @@ public void testMySqlDropMultipleTables() throws JSQLParserException { Drop drop = (Drop) assertSqlCanBeParsedAndDeparsed(sql); assertEquals(List.of("table_1", "table_2", "table_3"), drop.getNames().stream() - .map(Table::getFullyQualifiedName).toList()); + .map(Table::getFullyQualifiedName).collect(Collectors.toList())); assertEquals("table_1", drop.getName().getFullyQualifiedName()); assertEquals(List.of("RESTRICT"), drop.getParameters()); @@ -210,7 +211,7 @@ public void testMySqlDropMultipleViews() throws JSQLParserException { assertEquals(Drop.ObjectType.VIEW, drop.getObjectType()); assertEquals(List.of("view_1", "view_2"), drop.getNames().stream() - .map(Table::getFullyQualifiedName).toList()); + .map(Table::getFullyQualifiedName).collect(Collectors.toList())); assertEquals(List.of("CASCADE"), drop.getParameters()); } }