Skip to content

Commit 00aed16

Browse files
committed
feat: preserve DROP INDEX owner tables in the AST
1 parent 6312f9e commit 00aed16

9 files changed

Lines changed: 299 additions & 58 deletions

File tree

src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,7 @@ public <S> T visit(ParenthesedInsert insert, S context) {
330330

331331
@Override
332332
public <S> T visit(Drop drop, S context) {
333-
if (drop.getType().equalsIgnoreCase("table")) {
334-
drop.getNames().forEach(name -> fromItemVisitor.visitFromItem(name, context));
335-
}
336-
// @todo: handle schemas
333+
drop.visitTables(table -> fromItemVisitor.visitFromItem(table, context));
337334

338335
return null;
339336
}

src/main/java/net/sf/jsqlparser/statement/drop/Drop.java

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
import java.util.List;
1717
import java.util.Locale;
1818
import java.util.Map;
19-
import java.util.Optional;
20-
import java.util.stream.Collectors;
19+
import java.util.function.Consumer;
2120

2221
import net.sf.jsqlparser.schema.Table;
2322
import net.sf.jsqlparser.statement.Statement;
@@ -34,6 +33,8 @@ public enum ObjectType {
3433
private ObjectType objectType = ObjectType.OTHER;
3534
private final List<Table> names = new ArrayList<>();
3635
private List<String> parameters;
36+
private Table table;
37+
private int tablePosition;
3738
private Map<String, List<String>> typeToParameters = new HashMap<>();
3839
private boolean ifExists = false;
3940
private boolean materialized = false;
@@ -74,12 +75,55 @@ public void setNames(Collection<? extends Table> names) {
7475
}
7576
}
7677

78+
/** Returns legacy tokens, including ON and its owner. Structured owners produce a snapshot. */
7779
public List<String> getParameters() {
78-
return parameters;
80+
if (table == null) {
81+
return parameters;
82+
}
83+
List<String> tokens = parameters == null ? new ArrayList<>() : new ArrayList<>(parameters);
84+
tokens.add(tablePosition, "ON");
85+
tokens.add(tablePosition + 1, table.toString());
86+
return tokens;
87+
}
88+
89+
/** The table explicitly named by DROP INDEX ... ON, or null when SQL does not name one. */
90+
public Table getTable() {
91+
return table;
92+
}
93+
94+
public void setTable(Table table) {
95+
setTable(table, this.table == null ? 0 : tablePosition);
96+
}
97+
98+
/** Places ON among the remaining parameter tokens, preserving their source order. */
99+
public void setTable(Table table, int parameterPosition) {
100+
int size = parameters == null ? 0 : parameters.size();
101+
if (parameterPosition < 0 || parameterPosition > size) {
102+
throw new IllegalArgumentException("Invalid ON table position: " + parameterPosition);
103+
}
104+
this.table = table;
105+
tablePosition = table == null ? 0 : parameterPosition;
106+
}
107+
108+
public Drop withTable(Table table) {
109+
setTable(table);
110+
return this;
79111
}
80112

113+
/** Visits table/view targets and explicit index owners without resolving catalog objects. */
114+
public void visitTables(Consumer<Table> visitor) {
115+
if (objectType == ObjectType.TABLE || objectType == ObjectType.VIEW) {
116+
names.forEach(visitor);
117+
} else if (objectType == ObjectType.INDEX && table != null) {
118+
visitor.accept(table);
119+
}
120+
}
121+
122+
/** Replaces all legacy parameters and clears the structured ON table. */
81123
public void setParameters(List<String> list) {
82124
parameters = list;
125+
table = null;
126+
tablePosition = 0;
83127
}
84128

85129
public String getType() {
@@ -145,22 +189,43 @@ public void setTypeToParameters(Map<String, List<String>> typeToParameters) {
145189

146190
@Override
147191
public String toString() {
148-
String sql = "DROP "
149-
+ (isUsingTemporary ? "TEMPORARY " : "")
150-
+ (materialized ? "MATERIALIZED " : "")
151-
+ type + " "
152-
+ (ifExists ? "IF EXISTS " : "") + names.stream().map(Table::toString)
153-
.collect(Collectors.joining(", "));
154-
155-
if (type.equals("FUNCTION")) {
156-
sql += formatFuncParams(getParamsByType("FUNCTION"));
157-
}
192+
StringBuilder builder = new StringBuilder();
193+
return appendTo(builder, builder::append).toString();
194+
}
158195

159-
if (parameters != null && !parameters.isEmpty()) {
160-
sql += " " + PlainSelect.getStringList(parameters, false, false);
196+
public StringBuilder appendTo(StringBuilder builder, Consumer<Table> tablePrinter) {
197+
builder.append("DROP ");
198+
if (isUsingTemporary) {
199+
builder.append("TEMPORARY ");
161200
}
162-
163-
return sql;
201+
if (materialized) {
202+
builder.append("MATERIALIZED ");
203+
}
204+
builder.append(type).append(ifExists ? " IF EXISTS " : " ");
205+
for (int i = 0; i < names.size(); i++) {
206+
if (i > 0) {
207+
builder.append(", ");
208+
}
209+
if (objectType == ObjectType.TABLE || objectType == ObjectType.VIEW) {
210+
tablePrinter.accept(names.get(i));
211+
} else {
212+
builder.append(names.get(i));
213+
}
214+
}
215+
if ("FUNCTION".equals(type)) {
216+
builder.append(formatFuncParams(getParamsByType("FUNCTION")));
217+
}
218+
int size = parameters == null ? 0 : parameters.size();
219+
for (int i = 0; i <= size; i++) {
220+
if (table != null && i == tablePosition) {
221+
builder.append(" ON ");
222+
tablePrinter.accept(table);
223+
}
224+
if (i < size) {
225+
builder.append(' ').append(parameters.get(i));
226+
}
227+
}
228+
return builder;
164229
}
165230

166231
public List<String> getParamsByType(String type) {
@@ -208,14 +273,15 @@ public Drop withParameters(List<String> parameters) {
208273
}
209274

210275
public Drop addParameters(String... parameters) {
211-
List<String> collection = Optional.ofNullable(getParameters()).orElseGet(ArrayList::new);
212-
Collections.addAll(collection, parameters);
213-
return this.withParameters(collection);
276+
return addParameters(java.util.Arrays.asList(parameters));
214277
}
215278

279+
/** Appends trailing tokens without discarding a structured ON table. */
216280
public Drop addParameters(Collection<String> parameters) {
217-
List<String> collection = Optional.ofNullable(getParameters()).orElseGet(ArrayList::new);
218-
collection.addAll(parameters);
219-
return this.withParameters(collection);
281+
if (this.parameters == null) {
282+
this.parameters = new ArrayList<>();
283+
}
284+
this.parameters.addAll(parameters);
285+
return this;
220286
}
221287
}

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1536,7 +1536,7 @@ public void visit(Analyze analyze) {
15361536

15371537
@Override
15381538
public <S> Void visit(Drop drop, S context) {
1539-
drop.getNames().forEach(name -> visit(name, context));
1539+
drop.visitTables(table -> visit(table, context));
15401540
return null;
15411541
}
15421542

src/main/java/net/sf/jsqlparser/util/deparser/DropDeParser.java

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,24 @@
99
*/
1010
package net.sf.jsqlparser.util.deparser;
1111

12-
import java.util.stream.Collectors;
12+
import java.util.function.Consumer;
13+
import net.sf.jsqlparser.schema.Table;
1314
import net.sf.jsqlparser.statement.drop.Drop;
14-
import net.sf.jsqlparser.statement.select.PlainSelect;
1515

1616
public class DropDeParser extends AbstractDeParser<Drop> {
17+
private final Consumer<Table> tablePrinter;
1718

1819
public DropDeParser(StringBuilder buffer) {
20+
this(buffer, buffer::append);
21+
}
22+
23+
public DropDeParser(StringBuilder buffer, Consumer<Table> tablePrinter) {
1924
super(buffer);
25+
this.tablePrinter = tablePrinter;
2026
}
2127

2228
@Override
2329
public void deParse(Drop drop) {
24-
builder.append("DROP ");
25-
if (drop.isUsingTemporary()) {
26-
builder.append("TEMPORARY ");
27-
}
28-
if (drop.isMaterialized()) {
29-
builder.append("MATERIALIZED ");
30-
}
31-
builder.append(drop.getType());
32-
if (drop.isIfExists()) {
33-
builder.append(" IF EXISTS");
34-
}
35-
36-
builder.append(" ").append(drop.getNames().stream().map(Object::toString)
37-
.collect(Collectors.joining(", ")));
38-
39-
if (drop.getType().equals("FUNCTION")) {
40-
builder.append(Drop.formatFuncParams(drop.getParamsByType("FUNCTION")));
41-
}
42-
43-
if (drop.getParameters() != null && !drop.getParameters().isEmpty()) {
44-
builder.append(" ")
45-
.append(PlainSelect.getStringList(drop.getParameters(), false, false));
46-
}
30+
drop.appendTo(builder, tablePrinter);
4731
}
48-
4932
}

src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ public <S> StringBuilder visit(Delete delete, S context) {
210210

211211
@Override
212212
public <S> StringBuilder visit(Drop drop, S context) {
213-
DropDeParser dropDeParser = new DropDeParser(builder);
213+
DropDeParser dropDeParser =
214+
new DropDeParser(builder, table -> table.accept(selectDeParser, context));
214215
dropDeParser.deParse(drop);
215216
return builder;
216217
}

src/main/java/net/sf/jsqlparser/util/validation/validator/DropValidator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public void validate(Drop drop) {
4545
Feature.dropSequenceIfExists);
4646
}
4747

48+
if (drop.getTable() != null) {
49+
validateName(NamedObject.table, drop.getTable().getFullyQualifiedName());
50+
}
4851
NamedObject named = NamedObject.forName(type);
4952
if (Arrays.asList(NamedObject.table, NamedObject.view).contains(named)) {
5053
drop.getNames().forEach(name -> validateName(named, name.getFullyQualifiedName()));

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,6 +1619,12 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
16191619
}
16201620
}
16211621

1622+
private void requireNoDropIndexOwner(Table owner) throws ParseException {
1623+
if (owner != null) {
1624+
throw new ParseException("Duplicate DROP INDEX ON clause");
1625+
}
1626+
}
1627+
16221628
private static boolean hasStructuredColumnOption(List<ColumnOption> options) {
16231629
for (ColumnOption option : options) {
16241630
if (option.getKind() != ColumnOption.Kind.OTHER) {
@@ -14430,11 +14436,32 @@ List<String> FuncArgsList():
1443014436
}
1443114437
}
1443214438

14439+
/** DROP names are lexer-separated identifiers, not strings to split again on dots. */
14440+
Table DropObjectName() #TableName:
14441+
{
14442+
ObjectNames names;
14443+
Token literal;
14444+
Table table;
14445+
String timeTravel = null;
14446+
}
14447+
{
14448+
(
14449+
names=RelObjectNames() [ LOOKAHEAD(2) timeTravel=TimeTravelBeforeAlias() ]
14450+
{ table = names.getNames().size() == 1 ? new Table(names.getNames().get(0), false)
14451+
: new Table(names.getNames()); table.setTimeTravel(timeTravel); }
14452+
|
14453+
literal=<S_CHAR_LITERAL> { table = new Table(literal.image, false); }
14454+
)
14455+
{ linkAST(table, jjtThis); return table; }
14456+
}
14457+
1443314458
Drop Drop():
1443414459
{
1443514460
Drop drop = new Drop();
1443614461
Token tk = null;
1443714462
Table name;
14463+
Table indexTable = null;
14464+
int indexTablePosition = 0;
1443814465
List<String> dropArgs = new ArrayList<String>();
1443914466
List<String> funcArgs = null;
1444014467
List<String> indexOption;
@@ -14470,8 +14497,8 @@ Drop Drop():
1447014497

1447114498
[ LOOKAHEAD(2) <K_IF> <K_EXISTS> {drop.setIfExists(true);} ]
1447214499

14473-
name = Table() { drop.setName(name); }
14474-
( "," name = Table() { drop.addNames(name); } )*
14500+
name = DropObjectName() { drop.setName(name); }
14501+
( "," name = DropObjectName() { drop.addNames(name); } )*
1447514502
[ LOOKAHEAD(2) funcArgs = FuncArgsList() ]
1447614503
(
1447714504
LOOKAHEAD({ getToken(1).kind == K_ALGORITHM
@@ -14486,7 +14513,15 @@ Drop Drop():
1448614513
) { dropArgs.add(tk.image); }
1448714514
|
1448814515
(
14489-
<K_ON> name = Table() { dropArgs.add("ON"); dropArgs.add(name.toString()); }
14516+
<K_ON> name = DropObjectName() {
14517+
if (drop.getObjectType() == Drop.ObjectType.INDEX) {
14518+
requireNoDropIndexOwner(indexTable);
14519+
indexTable = name;
14520+
indexTablePosition = dropArgs.size();
14521+
} else {
14522+
dropArgs.add("ON"); dropArgs.add(name.toString());
14523+
}
14524+
}
1449014525
)
1449114526
|
1449214527
// The lock_option of DROP INDEX. LOCK also starts a LOCK TABLE statement, so it is only
@@ -14498,6 +14533,9 @@ Drop Drop():
1449814533
if (dropArgs.size() > 0) {
1449914534
drop.setParameters(dropArgs);
1450014535
}
14536+
if (indexTable != null) {
14537+
drop.setTable(indexTable, indexTablePosition);
14538+
}
1450114539
if (drop.getType().equals("FUNCTION")) {
1450214540
drop.getTypeToParameters().put("FUNCTION", funcArgs);
1450314541
}

src/site/sphinx/usage.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,27 @@ The fastest way to learn the object model is to look at it. Paste your SQL into
231231
Read that as a map: each line is a getter away. ``select.getSelectItems()``, ``select.getFromItem()``, ``select.getWhere()``. Once the tree gets deeper than a couple of levels, stop casting by hand and use :ref:`Use the Visitor Patterns`.
232232

233233

234+
DROP INDEX owners
235+
-----------------
236+
237+
``DROP INDEX ix ON app.t`` exposes ``app.t`` through ``Drop.getTable()``;
238+
``getName()`` continues to identify the index. The owner is populated for the
239+
single-index ON form used by MySQL and SQL Server. A DROP INDEX without ON,
240+
as in PostgreSQL, has a null owner: resolving the index's table requires a catalog.
241+
SQL Server's multi-owner list and WITH options are outside this extension.
242+
243+
``getParameters()`` includes ON and the rendered table as a legacy token snapshot
244+
when a structured owner exists. Mutate ``getTable()`` or use ``setTable()`` to
245+
update the owner; ``addParameters()`` appends options without losing it.
246+
``setTable(null)`` removes ON, while ``setParameters()`` replaces the entire
247+
parameter clause with raw tokens and clears the structured owner. Raw parameter
248+
setters do not parse SQL or infer a table.
249+
250+
Table discovery and statement visitors traverse DROP TABLE/VIEW targets and
251+
explicit index owners, without reporting catalog-only object names as tables.
252+
The statement deparser delegates real tables to its configured select deparser.
253+
MySQL ALGORITHM/LOCK tokens retain their order and optional equals signs.
254+
234255
Inspect PostgreSQL schema statements
235256
------------------------------------
236257

0 commit comments

Comments
 (0)