Skip to content

Commit 0986304

Browse files
authored
feat: parse MySQL CHECK, OPTIMIZE and REPAIR TABLE statements (#2788)
Model the MySQL/MariaDB table maintenance statements as TableMaintenanceStatement with its operation, NO_WRITE_TO_BINLOG/LOCAL modifier, TABLE/TABLES spelling, table list and per-operation options. Options outside an operation's definition are rejected, and the option words remain ordinary identifiers elsewhere. Wire the statement into the deparser, table finder, validator and statement feature analysis, and keep a following CHECK TABLE statement out of CREATE DOMAIN's CHECK constraint loop. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent dc50c7d commit 0986304

17 files changed

Lines changed: 620 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
143143
| **ClickHouse column selection** | `COLUMNS('regexp')` select items with chained `APPLY`, `EXCEPT`, and `REPLACE` transformers |
144144
| **DML** | `INSERT` · `UPDATE` · `UPSERT` · `MERGE` · `DELETE` · `TRUNCATE TABLE` |
145145
| **DDL** | `CREATE …` · `ALTER …` · `DROP …` |
146+
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `OPTIMIZE` · `REPAIR TABLE` |
146147
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
147148
| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` |
148149
| **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` |

‎src/main/java/net/sf/jsqlparser/parser/feature/Feature.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import net.sf.jsqlparser.statement.SetStatement;
2727
import net.sf.jsqlparser.statement.ShowColumnsStatement;
2828
import net.sf.jsqlparser.statement.ShowStatement;
29+
import net.sf.jsqlparser.statement.TableMaintenanceStatement;
2930
import net.sf.jsqlparser.statement.UseStatement;
3031
import net.sf.jsqlparser.statement.alter.Alter;
3132
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
@@ -458,6 +459,13 @@ public enum Feature {
458459
*/
459460
analyze,
460461

462+
/**
463+
* MySQL/MariaDB "CHECK TABLE", "OPTIMIZE TABLE" and "REPAIR TABLE" statements are allowed
464+
*
465+
* @see TableMaintenanceStatement
466+
*/
467+
tableMaintenance,
468+
461469
/**
462470
* SQL "TRUNCATE" statement is allowed
463471
*

‎src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,20 @@ public <S> Void visit(Analyze analyze, S context) {
714714
return null;
715715
}
716716

717+
@Override
718+
public <S> Void visit(TableMaintenanceStatement statement, S context) {
719+
analysis.claimTopLevel();
720+
analysis.certain(StmtFeature.READS_DATA, StmtFeature.RETURNS_RESULT_SET);
721+
if (statement.getOperation() != TableMaintenanceStatement.Operation.CHECK) {
722+
// OPTIMIZE rebuilds table storage; REPAIR may also rewrite or discard rows
723+
analysis.certain(StmtFeature.MODIFIES_SCHEMA);
724+
if (statement.getOperation() == TableMaintenanceStatement.Operation.REPAIR) {
725+
analysis.possible(StmtFeature.MODIFIES_DATA);
726+
}
727+
}
728+
return null;
729+
}
730+
717731
private Void schemaOnly() {
718732
analysis.claimTopLevel();
719733
analysis.certain(StmtFeature.MODIFIES_SCHEMA);

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,14 @@ default void visit(DoStatement statement) {
269269
this.visit(statement, null);
270270
}
271271

272+
default <S> T visit(TableMaintenanceStatement statement, S context) {
273+
return null;
274+
}
275+
276+
default void visit(TableMaintenanceStatement statement) {
277+
this.visit(statement, null);
278+
}
279+
272280
default void visit(Execute execute) {
273281
this.visit(execute, null);
274282
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,11 @@ public <S> T visit(Analyze analyze, S context) {
622622
return null;
623623
}
624624

625+
@Override
626+
public <S> T visit(TableMaintenanceStatement statement, S context) {
627+
return null;
628+
}
629+
625630
@Override
626631
public <S> T visit(SavepointStatement savepointStatement, S context) {
627632
// @todo: do something usefully here
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import java.util.ArrayList;
13+
import java.util.Collection;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import java.util.stream.Collectors;
17+
import net.sf.jsqlparser.schema.Table;
18+
19+
/**
20+
* A MySQL/MariaDB {@code CHECK TABLE}, {@code OPTIMIZE TABLE} or {@code REPAIR TABLE} statement.
21+
* {@code ANALYZE TABLE} remains {@link net.sf.jsqlparser.statement.analyze.Analyze}.
22+
*/
23+
public class TableMaintenanceStatement implements Statement {
24+
25+
public enum Operation {
26+
CHECK, OPTIMIZE, REPAIR;
27+
28+
/** Whether this operation accepts the option after its table list. */
29+
public boolean allows(Option option) {
30+
switch (this) {
31+
case CHECK:
32+
return option != Option.USE_FRM;
33+
case REPAIR:
34+
return option == Option.QUICK || option == Option.EXTENDED
35+
|| option == Option.USE_FRM;
36+
default:
37+
return false;
38+
}
39+
}
40+
41+
/** Whether this operation accepts {@code NO_WRITE_TO_BINLOG} or {@code LOCAL}. */
42+
public boolean allowsModifier() {
43+
return this != CHECK;
44+
}
45+
}
46+
47+
public enum Modifier {
48+
NO_WRITE_TO_BINLOG, LOCAL
49+
}
50+
51+
public enum Option {
52+
FOR_UPGRADE, QUICK, FAST, MEDIUM, EXTENDED, CHANGED, USE_FRM;
53+
54+
/** Returns the SQL spelling, for example {@code FOR UPGRADE}. */
55+
public String getKeyword() {
56+
return this == FOR_UPGRADE ? "FOR UPGRADE" : name();
57+
}
58+
59+
/** Returns the single-word option spelled by {@code word}, or null. */
60+
public static Option fromKeyword(String word) {
61+
for (Option option : values()) {
62+
if (option != FOR_UPGRADE && option.name().equalsIgnoreCase(word)) {
63+
return option;
64+
}
65+
}
66+
return null;
67+
}
68+
}
69+
70+
private Operation operation;
71+
private Modifier modifier;
72+
private boolean tablesKeyword;
73+
private final List<Table> tables = new ArrayList<>();
74+
private final List<Option> options = new ArrayList<>();
75+
76+
public TableMaintenanceStatement() {
77+
// empty constructor
78+
}
79+
80+
public TableMaintenanceStatement(Operation operation) {
81+
this.operation = operation;
82+
}
83+
84+
@Override
85+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
86+
return statementVisitor.visit(this, context);
87+
}
88+
89+
public Operation getOperation() {
90+
return operation;
91+
}
92+
93+
public void setOperation(Operation operation) {
94+
this.operation = operation;
95+
}
96+
97+
public Modifier getModifier() {
98+
return modifier;
99+
}
100+
101+
public void setModifier(Modifier modifier) {
102+
this.modifier = modifier;
103+
}
104+
105+
/** Whether the statement is spelled with {@code TABLES} instead of {@code TABLE}. */
106+
public boolean isTablesKeyword() {
107+
return tablesKeyword;
108+
}
109+
110+
public void setTablesKeyword(boolean tablesKeyword) {
111+
this.tablesKeyword = tablesKeyword;
112+
}
113+
114+
public List<Table> getTables() {
115+
return Collections.unmodifiableList(tables);
116+
}
117+
118+
public void setTables(Collection<? extends Table> tables) {
119+
this.tables.clear();
120+
if (tables != null) {
121+
this.tables.addAll(tables);
122+
}
123+
}
124+
125+
/** Returns the options in source order; repeated options are retained as written. */
126+
public List<Option> getOptions() {
127+
return Collections.unmodifiableList(options);
128+
}
129+
130+
public void setOptions(Collection<Option> options) {
131+
this.options.clear();
132+
if (options != null) {
133+
this.options.addAll(options);
134+
}
135+
}
136+
137+
@Override
138+
public String toString() {
139+
StringBuilder builder = new StringBuilder().append(operation);
140+
if (modifier != null) {
141+
builder.append(' ').append(modifier);
142+
}
143+
builder.append(tablesKeyword ? " TABLES " : " TABLE ")
144+
.append(tables.stream().map(Table::toString).collect(Collectors.joining(", ")));
145+
for (Option option : options) {
146+
builder.append(' ').append(option.getKeyword());
147+
}
148+
return builder.toString();
149+
}
150+
151+
public TableMaintenanceStatement withOperation(Operation operation) {
152+
setOperation(operation);
153+
return this;
154+
}
155+
156+
public TableMaintenanceStatement withModifier(Modifier modifier) {
157+
setModifier(modifier);
158+
return this;
159+
}
160+
161+
public TableMaintenanceStatement withTablesKeyword(boolean tablesKeyword) {
162+
setTablesKeyword(tablesKeyword);
163+
return this;
164+
}
165+
166+
public TableMaintenanceStatement withTables(Collection<? extends Table> tables) {
167+
setTables(tables);
168+
return this;
169+
}
170+
171+
public TableMaintenanceStatement addTables(Table... tables) {
172+
Collections.addAll(this.tables, tables);
173+
return this;
174+
}
175+
176+
public TableMaintenanceStatement withOptions(Collection<Option> options) {
177+
setOptions(options);
178+
return this;
179+
}
180+
181+
public TableMaintenanceStatement addOptions(Option... options) {
182+
Collections.addAll(this.options, options);
183+
return this;
184+
}
185+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
import net.sf.jsqlparser.statement.StartTransaction;
107107
import net.sf.jsqlparser.statement.Statement;
108108
import net.sf.jsqlparser.statement.StatementVisitor;
109+
import net.sf.jsqlparser.statement.TableMaintenanceStatement;
109110
import net.sf.jsqlparser.statement.Statements;
110111
import net.sf.jsqlparser.statement.UnsupportedStatement;
111112
import net.sf.jsqlparser.statement.UseStatement;
@@ -1646,6 +1647,17 @@ public void visit(Analyze analyze) {
16461647
StatementVisitor.super.visit(analyze);
16471648
}
16481649

1650+
@Override
1651+
public <S> Void visit(TableMaintenanceStatement statement, S context) {
1652+
statement.getTables().forEach(table -> visit(table, context));
1653+
return null;
1654+
}
1655+
1656+
@Override
1657+
public void visit(TableMaintenanceStatement statement) {
1658+
StatementVisitor.super.visit(statement);
1659+
}
1660+
16491661
@Override
16501662
public <S> Void visit(Drop drop, S context) {
16511663
drop.visitTables(table -> visit(table, context));

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import net.sf.jsqlparser.statement.UnsupportedStatement;
7878
import net.sf.jsqlparser.statement.UseStatement;
7979
import net.sf.jsqlparser.statement.SetIdentityInsertStatement;
80+
import net.sf.jsqlparser.statement.TableMaintenanceStatement;
8081
import net.sf.jsqlparser.statement.alter.Alter;
8182
import net.sf.jsqlparser.statement.alter.AlterSession;
8283
import net.sf.jsqlparser.statement.alter.AlterSystemStatement;
@@ -334,6 +335,12 @@ public <S> StringBuilder visit(Analyze analyzer, S context) {
334335
return builder;
335336
}
336337

338+
@Override
339+
public <S> StringBuilder visit(TableMaintenanceStatement statement, S context) {
340+
builder.append(statement);
341+
return builder;
342+
}
343+
337344
@Override
338345
public <S> StringBuilder visit(Alter alter, S context) {
339346
AlterDeParser alterDeParser = new AlterDeParser(builder, expressionDeParser);

‎src/main/java/net/sf/jsqlparser/util/validation/feature/MariaDbVersion.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ public enum MariaDbVersion implements Version {
7979
// https://mariadb.com/kb/en/truncate-table/
8080
Feature.truncate,
8181

82+
// https://mariadb.com/kb/en/table-statements/
83+
Feature.tableMaintenance,
84+
8285
// https://mariadb.com/kb/en/call/
8386
Feature.execute, Feature.executeCall,
8487

‎src/main/java/net/sf/jsqlparser/util/validation/feature/MySqlVersion.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public enum MySqlVersion implements Version {
7575
// https://dev.mysql.com/doc/refman/8.0/en/truncate-table.html
7676
Feature.truncate,
7777

78+
// https://dev.mysql.com/doc/refman/8.0/en/table-maintenance-statements.html
79+
Feature.tableMaintenance,
80+
7881
// https://dev.mysql.com/doc/refman/8.0/en/call.html
7982
Feature.execute, Feature.executeCall,
8083

0 commit comments

Comments
 (0)