diff --git a/README.md b/README.md index 6df4adb09..d39e81ea6 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 9e0bd5ce1..243194bcb 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 d1eac1029..91b272124 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 bc5c37b47..bf0bf3e8b 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 91d772e67..8a80763ca 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 000000000..d056b699d --- /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