Skip to content

Commit 5796619

Browse files
authored
feat: preserve DROP INDEX owner tables in the AST (#2640)
* feat: preserve DROP INDEX owner tables in the AST * refactor: split DROP target and parameter rendering
1 parent d51c209 commit 5796619

9 files changed

Lines changed: 306 additions & 57 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
@@ -331,10 +331,7 @@ public <S> T visit(ParenthesedInsert insert, S context) {
331331

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

339336
return null;
340337
}

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

Lines changed: 96 additions & 22 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,51 @@ 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 ");
200+
}
201+
if (materialized) {
202+
builder.append("MATERIALIZED ");
203+
}
204+
builder.append(type).append(ifExists ? " IF EXISTS " : " ");
205+
appendNames(builder, tablePrinter);
206+
if ("FUNCTION".equals(type)) {
207+
builder.append(formatFuncParams(getParamsByType("FUNCTION")));
161208
}
209+
appendParameters(builder, tablePrinter);
210+
return builder;
211+
}
212+
213+
private void appendNames(StringBuilder builder, Consumer<Table> tablePrinter) {
214+
for (int i = 0; i < names.size(); i++) {
215+
if (i > 0) {
216+
builder.append(", ");
217+
}
218+
if (objectType == ObjectType.TABLE || objectType == ObjectType.VIEW) {
219+
tablePrinter.accept(names.get(i));
220+
} else {
221+
builder.append(names.get(i));
222+
}
223+
}
224+
}
162225

163-
return sql;
226+
private void appendParameters(StringBuilder builder, Consumer<Table> tablePrinter) {
227+
int size = parameters == null ? 0 : parameters.size();
228+
for (int i = 0; i <= size; i++) {
229+
if (table != null && i == tablePosition) {
230+
builder.append(" ON ");
231+
tablePrinter.accept(table);
232+
}
233+
if (i < size) {
234+
builder.append(' ').append(parameters.get(i));
235+
}
236+
}
164237
}
165238

166239
public List<String> getParamsByType(String type) {
@@ -208,14 +281,15 @@ public Drop withParameters(List<String> parameters) {
208281
}
209282

210283
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);
284+
return addParameters(java.util.Arrays.asList(parameters));
214285
}
215286

287+
/** Appends trailing tokens without discarding a structured ON table. */
216288
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);
289+
if (this.parameters == null) {
290+
this.parameters = new ArrayList<>();
291+
}
292+
this.parameters.addAll(parameters);
293+
return this;
220294
}
221295
}

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

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

15541554
@Override
15551555
public <S> Void visit(Drop drop, S context) {
1556-
drop.getNames().forEach(name -> visit(name, context));
1556+
drop.visitTables(table -> visit(table, context));
15571557
return null;
15581558
}
15591559

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
@@ -1632,6 +1632,12 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
16321632
}
16331633
}
16341634

1635+
private void requireNoDropIndexOwner(Table owner) throws ParseException {
1636+
if (owner != null) {
1637+
throw new ParseException("Duplicate DROP INDEX ON clause");
1638+
}
1639+
}
1640+
16351641
private static boolean hasStructuredColumnOption(List<ColumnOption> options) {
16361642
for (ColumnOption option : options) {
16371643
if (option.getKind() != ColumnOption.Kind.OTHER) {
@@ -14857,11 +14863,32 @@ List<String> FuncArgsList():
1485714863
}
1485814864
}
1485914865

14866+
/** DROP names are lexer-separated identifiers, not strings to split again on dots. */
14867+
Table DropObjectName() #TableName:
14868+
{
14869+
ObjectNames names;
14870+
Token literal;
14871+
Table table;
14872+
String timeTravel = null;
14873+
}
14874+
{
14875+
(
14876+
names=RelObjectNames() [ LOOKAHEAD(2) timeTravel=TimeTravelBeforeAlias() ]
14877+
{ table = names.getNames().size() == 1 ? new Table(names.getNames().get(0), false)
14878+
: new Table(names.getNames()); table.setTimeTravel(timeTravel); }
14879+
|
14880+
literal=<S_CHAR_LITERAL> { table = new Table(literal.image, false); }
14881+
)
14882+
{ linkAST(table, jjtThis); return table; }
14883+
}
14884+
1486014885
Drop Drop():
1486114886
{
1486214887
Drop drop = new Drop();
1486314888
Token tk = null;
1486414889
Table name;
14890+
Table indexTable = null;
14891+
int indexTablePosition = 0;
1486514892
List<String> dropArgs = new ArrayList<String>();
1486614893
List<String> funcArgs = null;
1486714894
List<String> indexOption;
@@ -14897,8 +14924,8 @@ Drop Drop():
1489714924

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

14900-
name = Table() { drop.setName(name); }
14901-
( "," name = Table() { drop.addNames(name); } )*
14927+
name = DropObjectName() { drop.setName(name); }
14928+
( "," name = DropObjectName() { drop.addNames(name); } )*
1490214929
[ LOOKAHEAD(2) funcArgs = FuncArgsList() ]
1490314930
(
1490414931
LOOKAHEAD({ getToken(1).kind == K_ALGORITHM
@@ -14913,7 +14940,15 @@ Drop Drop():
1491314940
) { dropArgs.add(tk.image); }
1491414941
|
1491514942
(
14916-
<K_ON> name = Table() { dropArgs.add("ON"); dropArgs.add(name.toString()); }
14943+
<K_ON> name = DropObjectName() {
14944+
if (drop.getObjectType() == Drop.ObjectType.INDEX) {
14945+
requireNoDropIndexOwner(indexTable);
14946+
indexTable = name;
14947+
indexTablePosition = dropArgs.size();
14948+
} else {
14949+
dropArgs.add("ON"); dropArgs.add(name.toString());
14950+
}
14951+
}
1491714952
)
1491814953
|
1491914954
// The lock_option of DROP INDEX. LOCK also starts a LOCK TABLE statement, so it is only
@@ -14925,6 +14960,9 @@ Drop Drop():
1492514960
if (dropArgs.size() > 0) {
1492614961
drop.setParameters(dropArgs);
1492714962
}
14963+
if (indexTable != null) {
14964+
drop.setTable(indexTable, indexTablePosition);
14965+
}
1492814966
if (drop.getType().equals("FUNCTION")) {
1492914967
drop.getTypeToParameters().put("FUNCTION", funcArgs);
1493014968
}

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)