Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
| **DDL** | `CREATE …` · `ALTER …` · `DROP …` |
| **Oracle vector types** | `VECTOR(*, *)` · `VECTOR(*, FLOAT32)` · `VECTOR(3, *)`, including dense and sparse storage formats |
| **PostgreSQL ownership** | `DROP OWNED` · `REASSIGN OWNED` |
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `OPTIMIZE` · `REPAIR TABLE` |
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `CHECKSUM` · `OPTIMIZE` · `REPAIR TABLE` |
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
| **Tablespaces** | MySQL and PostgreSQL `CREATE TABLESPACE` · `ALTER TABLESPACE` |
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@ public enum Feature {
analyze,

/**
* MySQL/MariaDB "CHECK TABLE", "OPTIMIZE TABLE" and "REPAIR TABLE" statements are allowed
* MySQL/MariaDB "CHECK TABLE", "CHECKSUM TABLE", "OPTIMIZE TABLE" and "REPAIR TABLE" statements
* are allowed
*
* @see TableMaintenanceStatement
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,8 @@ public <S> Void visit(Analyze analyze, S context) {
public <S> Void visit(TableMaintenanceStatement statement, S context) {
analysis.claimTopLevel();
analysis.certain(StmtFeature.READS_DATA, StmtFeature.RETURNS_RESULT_SET);
if (statement.getOperation() != TableMaintenanceStatement.Operation.CHECK) {
if (statement.getOperation() == TableMaintenanceStatement.Operation.OPTIMIZE
|| statement.getOperation() == TableMaintenanceStatement.Operation.REPAIR) {
// OPTIMIZE rebuilds table storage; REPAIR may also rewrite or discard rows
analysis.certain(StmtFeature.MODIFIES_SCHEMA);
if (statement.getOperation() == TableMaintenanceStatement.Operation.REPAIR) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@
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}.
* A MySQL/MariaDB {@code CHECK TABLE}, {@code CHECKSUM TABLE}, {@code OPTIMIZE TABLE} or
* {@code REPAIR TABLE} statement. {@code ANALYZE TABLE} remains
* {@link net.sf.jsqlparser.statement.analyze.Analyze}.
*/
public class TableMaintenanceStatement extends ASTNodeAccessImpl implements Statement {

public enum Operation {
CHECK, OPTIMIZE, REPAIR;
CHECK, CHECKSUM, 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 CHECKSUM:
return option == Option.QUICK || option == Option.EXTENDED;
case REPAIR:
return option == Option.QUICK || option == Option.EXTENDED
|| option == Option.USE_FRM;
Expand All @@ -41,7 +44,7 @@ public boolean allows(Option option) {

/** Whether this operation accepts {@code NO_WRITE_TO_BINLOG} or {@code LOCAL}. */
public boolean allowsModifier() {
return this != CHECK;
return this == OPTIMIZE || this == REPAIR;
}
}

Expand Down
42 changes: 42 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/analyze/Analyze.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
Expand All @@ -30,12 +31,19 @@ public enum HistogramAction {
UPDATE, DROP
}

/** Whether MySQL updates a histogram automatically when it updates the table statistics. */
public enum HistogramUpdateMode {
AUTO, MANUAL
}

private final List<Table> tables = new ArrayList<>();
private boolean tableKeyword;
private Modifier modifier;
private HistogramAction histogramAction;
private final List<Column> histogramColumns = new ArrayList<>();
private Integer histogramBucketCount;
private StringValue histogramData;
private HistogramUpdateMode histogramUpdateMode;

@Override
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
Expand Down Expand Up @@ -107,6 +115,24 @@ public void setHistogramBucketCount(Integer histogramBucketCount) {
this.histogramBucketCount = histogramBucketCount;
}

/** The JSON histogram of {@code UPDATE HISTOGRAM ... USING DATA}, or null. */
public StringValue getHistogramData() {
return histogramData;
}

public void setHistogramData(StringValue histogramData) {
this.histogramData = histogramData;
}

/** The {@code AUTO UPDATE} or {@code MANUAL UPDATE} of {@code UPDATE HISTOGRAM}, or null. */
public HistogramUpdateMode getHistogramUpdateMode() {
return histogramUpdateMode;
}

public void setHistogramUpdateMode(HistogramUpdateMode histogramUpdateMode) {
this.histogramUpdateMode = histogramUpdateMode;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder("ANALYZE");
Expand All @@ -125,6 +151,12 @@ public String toString() {
if (histogramBucketCount != null) {
builder.append(" WITH ").append(histogramBucketCount).append(" BUCKETS");
}
if (histogramData != null) {
builder.append(" USING DATA ").append(histogramData);
}
if (histogramUpdateMode != null) {
builder.append(" ").append(histogramUpdateMode).append(" UPDATE");
}
}
return builder.toString();
}
Expand Down Expand Up @@ -173,4 +205,14 @@ public Analyze withHistogramBucketCount(Integer histogramBucketCount) {
setHistogramBucketCount(histogramBucketCount);
return this;
}

public Analyze withHistogramData(StringValue histogramData) {
setHistogramData(histogramData);
return this;
}

public Analyze withHistogramUpdateMode(HistogramUpdateMode histogramUpdateMode) {
setHistogramUpdateMode(histogramUpdateMode);
return this;
}
}
51 changes: 38 additions & 13 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -2498,17 +2498,18 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
}

/**
* Whether a MySQL CHECK, OPTIMIZE or REPAIR TABLE statement starts here. TABLE or TABLES must
* follow, so other uses of these words are unaffected.
* Whether a MySQL CHECK, CHECKSUM, OPTIMIZE or REPAIR TABLE statement starts here. TABLE or
* TABLES must follow, so other uses of these words are unaffected.
*/
private boolean isTableMaintenanceAhead() {
int kind = getToken(1).kind;
if (kind != K_CHECK && kind != K_OPTIMIZE && kind != K_REPAIR) {
if (kind != K_CHECK && kind != K_CHECKSUM && kind != K_OPTIMIZE && kind != K_REPAIR) {
return false;
}
int offset = 2;
int modifier = getToken(2).kind;
if (kind != K_CHECK && (modifier == K_NO_WRITE_TO_BINLOG || modifier == K_LOCAL)) {
if ((kind == K_OPTIMIZE || kind == K_REPAIR)
&& (modifier == K_NO_WRITE_TO_BINLOG || modifier == K_LOCAL)) {
offset = 3;
}
int table = getToken(offset).kind;
Expand Down Expand Up @@ -4033,6 +4034,7 @@ String NonReservedWordPart1() :
| tk=<K_CHANGE:"CHANGE">
| tk=<K_CHANGES:"CHANGES">
| tk=<K_CHECKPOINT:"CHECKPOINT">
| tk=<K_CHECKSUM:"CHECKSUM">
| tk=<K_CHAR:"CHAR">
| tk=<K_CLOSE:"CLOSE">
| tk=<K_CLOUD:"CLOUD">
Expand Down Expand Up @@ -19086,7 +19088,7 @@ TableOption MySqlAdditionalTableOption():
value=MySqlSizeNumber()
|
LOOKAHEAD({ isKeywordAhead("CHECKSUM") || isKeywordAhead("DELAY_KEY_WRITE") })
token=<S_IDENTIFIER> { name = token.image.toUpperCase(Locale.ROOT); }
( token=<K_CHECKSUM> | token=<S_IDENTIFIER> ) { name = token.image.toUpperCase(Locale.ROOT); }
[ "=" { useEquals = true; } ]
token=<S_LONG> { value = token.image; }
|
Expand Down Expand Up @@ -19556,8 +19558,8 @@ ColDataType.TypeModifier MySqlTypeModifier():
}

/**
* Parses MySQL/MariaDB CHECK, OPTIMIZE and REPAIR TABLE statements. Options are accepted only for
* the operation that defines them, without reserving the option words.
* Parses MySQL/MariaDB CHECK, CHECKSUM, OPTIMIZE and REPAIR TABLE statements. Options are accepted
* only for the operation that defines them, without reserving the option words.
*/
TableMaintenanceStatement TableMaintenanceStatement():
{
Expand All @@ -19571,6 +19573,7 @@ TableMaintenanceStatement TableMaintenanceStatement():
{
(
<K_CHECK> { operation = TableMaintenanceStatement.Operation.CHECK; }
| <K_CHECKSUM> { operation = TableMaintenanceStatement.Operation.CHECKSUM; }
| <K_OPTIMIZE> { operation = TableMaintenanceStatement.Operation.OPTIMIZE; }
| <K_REPAIR> { operation = TableMaintenanceStatement.Operation.REPAIR; }
)
Expand Down Expand Up @@ -19598,7 +19601,11 @@ TableMaintenanceStatement TableMaintenanceStatement():
)
{ statement.addOptions(option); }
)*
{ return statement; }
{
requireDdlSyntax(operation != TableMaintenanceStatement.Operation.CHECKSUM
|| statement.getOptions().size() <= 1, "CHECKSUM TABLE takes QUICK or EXTENDED, not both");
return statement;
}
}

Analyze Analyze():
Expand All @@ -19609,6 +19616,7 @@ Analyze Analyze():
Token modifier = null;
Token histogramAction = null;
Token bucketCount = null;
Token data;
}
{
<K_ANALYZE>
Expand All @@ -19630,11 +19638,28 @@ Analyze Analyze():
<K_HISTOGRAM> <K_ON>
column=Column() { analyze.addHistogramColumns(column); }
( "," column=Column() { analyze.addHistogramColumns(column); } )*
[
LOOKAHEAD(3)
<K_WITH> bucketCount=<S_LONG> <K_BUCKETS>
{ analyze.setHistogramBucketCount(Integer.valueOf(bucketCount.image)); }
]
(
// MySQL 8.4 sets a histogram from JSON instead of sampling the column
LOOKAHEAD(2) <K_USING> <K_DATA> data=<S_CHAR_LITERAL> {
requireDdlSyntax(histogramAction.kind == K_UPDATE, "USING DATA requires UPDATE HISTOGRAM");
analyze.setHistogramData(stringValue(data));
}
|
[
LOOKAHEAD(3)
<K_WITH> bucketCount=<S_LONG> <K_BUCKETS>
{ analyze.setHistogramBucketCount(Integer.valueOf(bucketCount.image)); }
]
[
LOOKAHEAD({ (getToken(1).kind == K_AUTO || isKeywordAhead("MANUAL"))
&& getToken(2).kind == K_UPDATE })
( <K_AUTO> { analyze.setHistogramUpdateMode(Analyze.HistogramUpdateMode.AUTO); }
| ContextualKeyword("MANUAL") { analyze.setHistogramUpdateMode(Analyze.HistogramUpdateMode.MANUAL); } )
<K_UPDATE> {
requireDdlSyntax(histogramAction.kind == K_UPDATE, "AUTO UPDATE and MANUAL UPDATE require UPDATE HISTOGRAM");
}
]
)
]

{
Expand Down
2 changes: 1 addition & 1 deletion src/site/sphinx/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ One grammar covers the SQL standard plus all major RDBMS. Missing syntax gets ad
* - **PostgreSQL ownership**
- ``DROP OWNED`` · ``REASSIGN OWNED``
* - **MySQL table maintenance**
- ``ANALYZE`` · ``CHECK`` · ``OPTIMIZE`` · ``REPAIR TABLE``
- ``ANALYZE`` · ``CHECK`` · ``CHECKSUM`` · ``OPTIMIZE`` · ``REPAIR TABLE``
* - **MySQL account management**
- ``CREATE``/``ALTER``/``RENAME USER`` · ``CREATE ROLE`` · ``SET ROLE`` · ``SET DEFAULT ROLE`` · ``SET PASSWORD``
* - **Tablespaces**
Expand Down
13 changes: 10 additions & 3 deletions src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2458,14 +2458,15 @@ MySQL `ALTER FUNCTION <https://dev.mysql.com/doc/refman/8.4/en/alter-function.ht
MySQL table maintenance statements
----------------------------------

``CHECK TABLE``, ``OPTIMIZE TABLE`` and ``REPAIR TABLE`` parse to
``CHECK TABLE``, ``CHECKSUM TABLE``, ``OPTIMIZE TABLE`` and ``REPAIR TABLE`` parse to
``TableMaintenanceStatement`` in every dialect; ``ANALYZE TABLE`` remains ``Analyze``.
``getOperation()`` identifies the statement, ``getModifier()`` returns
``NO_WRITE_TO_BINLOG`` or ``LOCAL`` for OPTIMIZE and REPAIR, and ``isTablesKeyword()``
records the ``TABLES`` spelling. ``getOptions()`` keeps the trailing options in source
order. CHECK accepts ``FOR UPGRADE``, ``QUICK``, ``FAST``, ``MEDIUM``, ``EXTENDED`` and
``CHANGED``; REPAIR accepts ``QUICK``, ``EXTENDED`` and ``USE_FRM``. Other combinations
are rejected, and the option words remain ordinary identifiers elsewhere.
``CHANGED``; CHECKSUM accepts one of ``QUICK`` and ``EXTENDED``; REPAIR accepts ``QUICK``,
``EXTENDED`` and ``USE_FRM``. Other combinations are rejected, and the option words and
``CHECKSUM`` remain ordinary identifiers elsewhere.

.. code-block:: java

Expand All @@ -2479,6 +2480,12 @@ Table visitors and metadata validation use the table list. Validators report the
analysis reports ``READS_DATA`` and ``RETURNS_RESULT_SET``; OPTIMIZE and REPAIR also
report ``MODIFIES_SCHEMA``, and REPAIR may report ``MODIFIES_DATA``.

``ANALYZE TABLE ... UPDATE HISTOGRAM ON`` keeps ``WITH n BUCKETS``
(``getHistogramBucketCount()``), MySQL 8.4's ``AUTO UPDATE`` or ``MANUAL UPDATE``
(``getHistogramUpdateMode()``) and ``USING DATA 'json'`` (``getHistogramData()``, a
``StringValue``). ``USING DATA`` excludes the other two options, and ``DROP HISTOGRAM``
takes none of them.

MySQL account management statements
-----------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,27 @@ void checkOptionsKeepSourceOrder() throws JSQLParserException {
assertEquals(List.of("t1", "t2"), tableNames(statement));
}

@Test
void checksumTableWithOption() throws JSQLParserException {
TableMaintenanceStatement statement = parse("CHECKSUM TABLE t1, db.t2 QUICK");

assertEquals(Operation.CHECKSUM, statement.getOperation());
assertNull(statement.getModifier());
assertEquals(List.of("t1", "db.t2"), tableNames(statement));
assertEquals(List.of(Option.QUICK), statement.getOptions());
}

@ParameterizedTest
@ValueSource(strings = {"CHECKSUM TABLE t", "CHECKSUM TABLE t EXTENDED",
"CHECKSUM TABLES t1, t2"})
void checksumTableForms(String sql) throws JSQLParserException {
assertEquals(Operation.CHECKSUM, parse(sql).getOperation());
for (Dialect dialect : List.of(Dialect.MYSQL, Dialect.MARIADB)) {
assertEquals(sql, CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(dialect))
.toString());
}
}

@Test
void keywordsAreCaseInsensitive() throws JSQLParserException {
Statement statement = CCJSqlParserUtil.parse("check tables t medium for upgrade");
Expand All @@ -110,7 +131,13 @@ void keywordsAreCaseInsensitive() throws JSQLParserException {
"REPAIR TABLE t FAST",
"REPAIR TABLE t FOR UPGRADE",
"OPTIMIZE t",
"REPAIR TABLE"
"REPAIR TABLE",
"CHECKSUM NO_WRITE_TO_BINLOG TABLE t",
"CHECKSUM LOCAL TABLE t",
"CHECKSUM TABLE t FAST",
"CHECKSUM TABLE t FOR UPGRADE",
"CHECKSUM TABLE t QUICK EXTENDED",
"CHECKSUM t"
})
void rejectsSyntaxOutsideTheStatementDefinition(String sql) {
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql));
Expand Down Expand Up @@ -149,6 +176,10 @@ void wordsRemainUsableOutsideTheStatement() throws JSQLParserException {
assertSqlCanBeParsedAndDeparsed("ALTER TABLE t OPTIMIZE PARTITION p1");
assertSqlCanBeParsedAndDeparsed("ALTER TABLE t CHECK PARTITION p1");
assertSqlCanBeParsedAndDeparsed("ALTER TABLE t REPAIR PARTITION p1");
assertSqlCanBeParsedAndDeparsed(
"SELECT checksum, CHECKSUM(*), CHECKSUM(a, b) FROM checksum");
assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (checksum INT) CHECKSUM = 1");
assertSqlCanBeParsedAndDeparsed("ALTER TABLE t CHECKSUM = 0, ADD COLUMN checksum INT");
}

@Test
Expand All @@ -169,6 +200,11 @@ void reportsStatementFeatures() throws JSQLParserException {
assertThat(check.getCertain()).containsExactlyInAnyOrder(StmtFeature.READS_DATA,
StmtFeature.RETURNS_RESULT_SET);

StatementFeatures checksum = CCJSqlParserUtil.parse("CHECKSUM TABLE t").getFeatures();
assertThat(checksum.getCertain()).containsExactlyInAnyOrder(StmtFeature.READS_DATA,
StmtFeature.RETURNS_RESULT_SET);
assertFalse(checksum.may(StmtFeature.MODIFIES_SCHEMA));

StatementFeatures optimize = CCJSqlParserUtil.parse("OPTIMIZE TABLE t").getFeatures();
assertThat(optimize.getCertain()).contains(StmtFeature.MODIFIES_SCHEMA);
assertFalse(optimize.may(StmtFeature.MODIFIES_DATA));
Expand Down
Loading
Loading