From 628e25ab80d14ba46e2beae35e79b8bee1eb941d Mon Sep 17 00:00:00 2001 From: minleejae Date: Tue, 13 Oct 2026 13:23:07 +0900 Subject: [PATCH] feat: parse MySQL CHECK, OPTIMIZE and REPAIR TABLE statements 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 --- README.md | 1 + .../sf/jsqlparser/parser/feature/Feature.java | 8 + .../statement/StatementFeatureVisitor.java | 14 ++ .../statement/StatementVisitor.java | 8 + .../statement/StatementVisitorAdapter.java | 5 + .../statement/TableMaintenanceStatement.java | 185 ++++++++++++++++++ .../sf/jsqlparser/util/TablesNamesFinder.java | 12 ++ .../util/deparser/StatementDeParser.java | 7 + .../validation/feature/MariaDbVersion.java | 3 + .../util/validation/feature/MySqlVersion.java | 3 + .../validator/StatementValidator.java | 12 ++ .../validator/TableMaintenanceValidator.java | 26 +++ .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 89 ++++++++- src/site/sphinx/index.rst | 2 + src/site/sphinx/usage.rst | 24 +++ .../TableMaintenanceStatementTest.java | 180 +++++++++++++++++ .../TableMaintenanceValidatorTest.java | 42 ++++ 17 files changed, 620 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/sf/jsqlparser/statement/TableMaintenanceStatement.java create mode 100644 src/main/java/net/sf/jsqlparser/util/validation/validator/TableMaintenanceValidator.java create mode 100644 src/test/java/net/sf/jsqlparser/statement/TableMaintenanceStatementTest.java create mode 100644 src/test/java/net/sf/jsqlparser/util/validation/validator/TableMaintenanceValidatorTest.java diff --git a/README.md b/README.md index 6df4adb091..d39e81ea68 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J | **ClickHouse column selection** | `COLUMNS('regexp')` select items with chained `APPLY`, `EXCEPT`, and `REPLACE` transformers | | **DML** | `INSERT` · `UPDATE` · `UPSERT` · `MERGE` · `DELETE` · `TRUNCATE TABLE` | | **DDL** | `CREATE …` · `ALTER …` · `DROP …` | +| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `OPTIMIZE` · `REPAIR TABLE` | | **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` | | **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` | | **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` | diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index 9e0bd5ce10..243194bcb7 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -26,6 +26,7 @@ import net.sf.jsqlparser.statement.SetStatement; import net.sf.jsqlparser.statement.ShowColumnsStatement; import net.sf.jsqlparser.statement.ShowStatement; +import net.sf.jsqlparser.statement.TableMaintenanceStatement; import net.sf.jsqlparser.statement.UseStatement; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.sequence.AlterSequence; @@ -458,6 +459,13 @@ public enum Feature { */ analyze, + /** + * MySQL/MariaDB "CHECK TABLE", "OPTIMIZE TABLE" and "REPAIR TABLE" statements are allowed + * + * @see TableMaintenanceStatement + */ + tableMaintenance, + /** * SQL "TRUNCATE" statement is allowed * diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java index d1eac1029a..91b2721249 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java @@ -714,6 +714,20 @@ public Void visit(Analyze analyze, S context) { return null; } + @Override + public Void visit(TableMaintenanceStatement statement, S context) { + analysis.claimTopLevel(); + analysis.certain(StmtFeature.READS_DATA, StmtFeature.RETURNS_RESULT_SET); + if (statement.getOperation() != TableMaintenanceStatement.Operation.CHECK) { + // OPTIMIZE rebuilds table storage; REPAIR may also rewrite or discard rows + analysis.certain(StmtFeature.MODIFIES_SCHEMA); + if (statement.getOperation() == TableMaintenanceStatement.Operation.REPAIR) { + analysis.possible(StmtFeature.MODIFIES_DATA); + } + } + return null; + } + private Void schemaOnly() { analysis.claimTopLevel(); analysis.certain(StmtFeature.MODIFIES_SCHEMA); diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java index bc5c37b47d..bf0bf3e8b5 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java @@ -269,6 +269,14 @@ default void visit(DoStatement statement) { this.visit(statement, null); } + default T visit(TableMaintenanceStatement statement, S context) { + return null; + } + + default void visit(TableMaintenanceStatement statement) { + this.visit(statement, null); + } + default void visit(Execute execute) { this.visit(execute, null); } diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java index 91d772e677..8a80763ca3 100644 --- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java @@ -622,6 +622,11 @@ public T visit(Analyze analyze, S context) { return null; } + @Override + public T visit(TableMaintenanceStatement statement, S context) { + return null; + } + @Override public T visit(SavepointStatement savepointStatement, S context) { // @todo: do something usefully here diff --git a/src/main/java/net/sf/jsqlparser/statement/TableMaintenanceStatement.java b/src/main/java/net/sf/jsqlparser/statement/TableMaintenanceStatement.java new file mode 100644 index 0000000000..d056b699de --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/TableMaintenanceStatement.java @@ -0,0 +1,185 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import net.sf.jsqlparser.schema.Table; + +/** + * A MySQL/MariaDB {@code CHECK TABLE}, {@code OPTIMIZE TABLE} or {@code REPAIR TABLE} statement. + * {@code ANALYZE TABLE} remains {@link net.sf.jsqlparser.statement.analyze.Analyze}. + */ +public class TableMaintenanceStatement implements Statement { + + public enum Operation { + CHECK, OPTIMIZE, REPAIR; + + /** Whether this operation accepts the option after its table list. */ + public boolean allows(Option option) { + switch (this) { + case CHECK: + return option != Option.USE_FRM; + case REPAIR: + return option == Option.QUICK || option == Option.EXTENDED + || option == Option.USE_FRM; + default: + return false; + } + } + + /** Whether this operation accepts {@code NO_WRITE_TO_BINLOG} or {@code LOCAL}. */ + public boolean allowsModifier() { + return this != CHECK; + } + } + + public enum Modifier { + NO_WRITE_TO_BINLOG, LOCAL + } + + public enum Option { + FOR_UPGRADE, QUICK, FAST, MEDIUM, EXTENDED, CHANGED, USE_FRM; + + /** Returns the SQL spelling, for example {@code FOR UPGRADE}. */ + public String getKeyword() { + return this == FOR_UPGRADE ? "FOR UPGRADE" : name(); + } + + /** Returns the single-word option spelled by {@code word}, or null. */ + public static Option fromKeyword(String word) { + for (Option option : values()) { + if (option != FOR_UPGRADE && option.name().equalsIgnoreCase(word)) { + return option; + } + } + return null; + } + } + + private Operation operation; + private Modifier modifier; + private boolean tablesKeyword; + private final List tables = new ArrayList<>(); + private final List
getTables() { + return Collections.unmodifiableList(tables); + } + + public void setTables(Collection tables) { + this.tables.clear(); + if (tables != null) { + this.tables.addAll(tables); + } + } + + /** Returns the options in source order; repeated options are retained as written. */ + public List