Skip to content

Commit c201923

Browse files
authored
feat: parse MySQL FLUSH, KILL, RESET PERSIST and HANDLER (#2901)
FLUSH, KILL and HANDLER failed in every dialect, RESET PERSIST alone reset a parameter named PERSIST and RESET PERSIST name failed. FlushStatement keeps the binlog modifier, the flush options in source order (FlushOption, with the channel of RELAY LOGS) or the FLUSH TABLES form with its tables and WITH READ LOCK or FOR EXPORT. KillStatement keeps CONNECTION or QUERY and the identifier expression. ResetStatement gains isPersist() and isIfExists(), and the variable name may have a component or DEFAULT prefix. HandlerStatement keeps OPEN with the table and its alias, READ with the index, key comparison and values or direction, WHERE and LIMIT, and CLOSE; as in MySQL, a READ takes no subquery. The forms parse with the MySQL dialect and without one; other dialects keep rejecting them. HANDLER becomes a non-reserved keyword, and TablesNamesFinder no longer throws for RESET. The new statements and CHECK, CHECKSUM, OPTIMIZE and REPAIR TABLE now start a statement, so unsupported statement capture no longer takes them. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent fa1877f commit c201923

27 files changed

Lines changed: 2387 additions & 23 deletions

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
152152
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
153153
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
154154
| **PostgreSQL session statements** | `DISCARD` · `LISTEN` · `UNLISTEN` · `SHOW`/`RESET TIME ZONE`/`TRANSACTION ISOLATION LEVEL`/`SESSION AUTHORIZATION` · `COPY` options from before 9.0 |
155+
| **MySQL server administration** | `FLUSH` · `KILL` · `RESET PERSIST` · `HANDLER … OPEN`/`READ`/`CLOSE` |
155156
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
156157
| **Tablespaces** | MySQL and PostgreSQL `CREATE TABLESPACE` · `ALTER TABLESPACE` |
157158
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |

‎src/main/java/net/sf/jsqlparser/parser/RoutineBodyBoundary.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ private void acceptStatementToken(Token token, Token next) {
248248
if (statementStart) {
249249
statementKind = kind;
250250
}
251-
if (statementKind == K_DECLARE && keyword(token, "HANDLER")) {
251+
if (statementKind == K_DECLARE && token.kind == K_HANDLER) {
252252
handlerAction = true;
253253
}
254254
statementStart = false;

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import net.sf.jsqlparser.statement.TableMaintenanceStatement;
3333
import net.sf.jsqlparser.statement.UseStatement;
3434
import net.sf.jsqlparser.statement.XaStatement;
35+
import net.sf.jsqlparser.statement.admin.FlushStatement;
36+
import net.sf.jsqlparser.statement.admin.KillStatement;
3537
import net.sf.jsqlparser.statement.alter.Alter;
3638
import net.sf.jsqlparser.statement.alter.AlterEventTrigger;
3739
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
@@ -55,6 +57,7 @@
5557
import net.sf.jsqlparser.statement.drop.Drop;
5658
import net.sf.jsqlparser.statement.execute.Execute;
5759
import net.sf.jsqlparser.statement.grant.Grant;
60+
import net.sf.jsqlparser.statement.handler.HandlerStatement;
5861
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
5962
import net.sf.jsqlparser.statement.lock.UnlockStatement;
6063
import net.sf.jsqlparser.statement.merge.Merge;
@@ -494,6 +497,27 @@ public enum Feature {
494497
*/
495498
tableMaintenance,
496499

500+
/**
501+
* MySQL "FLUSH" statement is allowed
502+
*
503+
* @see FlushStatement
504+
*/
505+
flush,
506+
507+
/**
508+
* MySQL "KILL" statement is allowed
509+
*
510+
* @see KillStatement
511+
*/
512+
kill,
513+
514+
/**
515+
* MySQL "HANDLER ... OPEN", "HANDLER ... READ" and "HANDLER ... CLOSE" statements are allowed
516+
*
517+
* @see HandlerStatement
518+
*/
519+
handler,
520+
497521
/**
498522
* PostgreSQL "VACUUM" statement is allowed
499523
*
@@ -987,6 +1011,12 @@ public enum Feature {
9871011
* @see net.sf.jsqlparser.statement.notify.ListenStatement
9881012
*/
9891013
listen,
1014+
/**
1015+
* MySQL "RESET PERSIST" statement is allowed
1016+
*
1017+
* @see ResetStatement#isPersist()
1018+
*/
1019+
resetPersist,
9901020
/**
9911021
* @see Pivot
9921022
*/

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

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,18 @@
1111

1212
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1313

14+
/**
15+
* PostgreSQL {@code RESET configuration_parameter}, {@code RESET ALL} and {@code RESET TIME ZONE},
16+
* and MySQL {@code RESET PERSIST [[IF EXISTS] system_var_name]}, which {@link #isPersist()} marks.
17+
*
18+
* @see <a href="https://www.postgresql.org/docs/current/sql-reset.html">PostgreSQL RESET</a>
19+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/reset-persist.html">MySQL RESET PERSIST</a>
20+
*/
1421
public final class ResetStatement extends ASTNodeAccessImpl implements Statement {
1522

1623
private String name = "";
24+
private boolean persist;
25+
private boolean ifExists;
1726

1827
public ResetStatement() {
1928
// empty constructor
@@ -27,6 +36,10 @@ public void add(String name) {
2736
this.name = name;
2837
}
2938

39+
/**
40+
* Returns the parameter or, for RESET PERSIST, the system variable; null when RESET PERSIST
41+
* names none and removes every persisted variable.
42+
*/
3043
public String getName() {
3144
return name;
3245
}
@@ -35,10 +48,52 @@ public void setName(String name) {
3548
this.name = name;
3649
}
3750

51+
/** Whether this is MySQL RESET PERSIST, which removes persisted global system variables. */
52+
public boolean isPersist() {
53+
return persist;
54+
}
55+
56+
public void setPersist(boolean persist) {
57+
this.persist = persist;
58+
}
59+
60+
public ResetStatement withPersist(boolean persist) {
61+
setPersist(persist);
62+
return this;
63+
}
64+
65+
/** Whether RESET PERSIST has IF EXISTS before the variable name. */
66+
public boolean isIfExists() {
67+
return ifExists;
68+
}
69+
70+
public void setIfExists(boolean ifExists) {
71+
this.ifExists = ifExists;
72+
}
73+
74+
public ResetStatement withIfExists(boolean ifExists) {
75+
setIfExists(ifExists);
76+
return this;
77+
}
78+
79+
public StringBuilder appendTo(StringBuilder builder) {
80+
builder.append("RESET ");
81+
if (!persist) {
82+
return builder.append(name);
83+
}
84+
builder.append("PERSIST");
85+
if (ifExists) {
86+
builder.append(" IF EXISTS");
87+
}
88+
if (name != null && !name.isEmpty()) {
89+
builder.append(' ').append(name);
90+
}
91+
return builder;
92+
}
93+
3894
@Override
3995
public String toString() {
40-
StringBuilder b = new StringBuilder("RESET ").append(name);
41-
return b.toString();
96+
return appendTo(new StringBuilder()).toString();
4297
}
4398

4499
@Override

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@
9898
import net.sf.jsqlparser.statement.insert.Insert;
9999
import net.sf.jsqlparser.statement.insert.InsertBulk;
100100
import net.sf.jsqlparser.statement.load.LoadDataStatement;
101+
import net.sf.jsqlparser.statement.admin.FlushStatement;
102+
import net.sf.jsqlparser.statement.admin.KillStatement;
103+
import net.sf.jsqlparser.statement.handler.HandlerStatement;
101104
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
102105
import net.sf.jsqlparser.statement.lock.LockStatement;
103106
import net.sf.jsqlparser.statement.lock.UnlockStatement;
@@ -784,6 +787,40 @@ public <S> Void visit(TableMaintenanceStatement statement, S context) {
784787
return null;
785788
}
786789

790+
@Override
791+
public <S> Void visit(FlushStatement flush, S context) {
792+
// a server operation like ALTER SYSTEM FLUSH; WITH READ LOCK and FOR EXPORT also lock
793+
analysis.claimTopLevel();
794+
analysis.certain(StmtFeature.MODIFIES_SESSION);
795+
if (flush.getTableLock() != null) {
796+
analysis.certain(StmtFeature.MODIFIES_TRANSACTION);
797+
}
798+
return null;
799+
}
800+
801+
@Override
802+
public <S> Void visit(KillStatement kill, S context) {
803+
// ends another session or its statement, like ALTER SYSTEM KILL SESSION
804+
analysis.claimTopLevel();
805+
analysis.certain(StmtFeature.MODIFIES_SESSION);
806+
return null;
807+
}
808+
809+
@Override
810+
public <S> Void visit(HandlerStatement handler, S context) {
811+
if (handler.getAction() != HandlerStatement.Action.READ) {
812+
// OPEN and CLOSE change the handlers of the session
813+
analysis.claimTopLevel();
814+
analysis.certain(StmtFeature.MODIFIES_SESSION);
815+
return null;
816+
}
817+
if (analysis.claimTopLevel()) {
818+
analysis.certain(StmtFeature.RETURNS_RESULT_SET);
819+
}
820+
analysis.certain(StmtFeature.READS_DATA);
821+
return super.visit(handler, context);
822+
}
823+
787824
@Override
788825
public <S> Void visit(VacuumStatement vacuum, S context) {
789826
// reclaims storage and, with ANALYZE, rewrites statistics; FULL rewrites the tables

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@
9191
import net.sf.jsqlparser.statement.insert.Insert;
9292
import net.sf.jsqlparser.statement.insert.InsertBulk;
9393
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
94+
import net.sf.jsqlparser.statement.admin.FlushStatement;
95+
import net.sf.jsqlparser.statement.admin.KillStatement;
96+
import net.sf.jsqlparser.statement.handler.HandlerStatement;
9497
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
9598
import net.sf.jsqlparser.statement.lock.LockStatement;
9699
import net.sf.jsqlparser.statement.lock.UnlockStatement;
@@ -409,6 +412,30 @@ default void visit(TableMaintenanceStatement statement) {
409412
this.visit(statement, null);
410413
}
411414

415+
default <S> T visit(FlushStatement flush, S context) {
416+
return null;
417+
}
418+
419+
default void visit(FlushStatement flush) {
420+
this.visit(flush, null);
421+
}
422+
423+
default <S> T visit(KillStatement kill, S context) {
424+
return null;
425+
}
426+
427+
default void visit(KillStatement kill) {
428+
this.visit(kill, null);
429+
}
430+
431+
default <S> T visit(HandlerStatement handler, S context) {
432+
return null;
433+
}
434+
435+
default void visit(HandlerStatement handler) {
436+
this.visit(handler, null);
437+
}
438+
412439
default <S> T visit(VacuumStatement vacuum, S context) {
413440
return null;
414441
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@
9494
import net.sf.jsqlparser.statement.insert.InsertBulk;
9595
import net.sf.jsqlparser.statement.insert.InsertConflictAction;
9696
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
97+
import net.sf.jsqlparser.statement.admin.FlushStatement;
98+
import net.sf.jsqlparser.statement.admin.KillStatement;
99+
import net.sf.jsqlparser.statement.handler.HandlerStatement;
97100
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
98101
import net.sf.jsqlparser.statement.lock.LockStatement;
99102
import net.sf.jsqlparser.statement.lock.UnlockStatement;
@@ -752,6 +755,24 @@ public <S> T visit(TableMaintenanceStatement statement, S context) {
752755
return null;
753756
}
754757

758+
@Override
759+
public <S> T visit(FlushStatement flush, S context) {
760+
return null;
761+
}
762+
763+
@Override
764+
public <S> T visit(KillStatement kill, S context) {
765+
expressionVisitor.visitExpression(kill.getProcesslistId(), context);
766+
return null;
767+
}
768+
769+
@Override
770+
public <S> T visit(HandlerStatement handler, S context) {
771+
expressionVisitor.visitExpression(handler.getKeyValues(), context);
772+
expressionVisitor.visitExpression(handler.getWhere(), context);
773+
return null;
774+
}
775+
755776
@Override
756777
public <S> T visit(VacuumStatement vacuum, S context) {
757778
return null;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.admin;
11+
12+
import java.util.Locale;
13+
import java.util.Objects;
14+
import net.sf.jsqlparser.expression.StringValue;
15+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
16+
17+
/**
18+
* One flush option of MySQL {@code FLUSH}, such as {@code BINARY LOGS} or
19+
* {@code RELAY LOGS FOR CHANNEL 'c1'}.
20+
*
21+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/flush.html">MySQL FLUSH</a>
22+
*/
23+
public class FlushOption extends ASTNodeAccessImpl {
24+
private Kind kind;
25+
private StringValue channel;
26+
27+
/** The flush options of MySQL 8.4. */
28+
public enum Kind {
29+
BINARY_LOGS, ENGINE_LOGS, ERROR_LOGS, GENERAL_LOGS, LOGS, OPTIMIZER_COSTS, PRIVILEGES, RELAY_LOGS, SLOW_LOGS, STATUS, USER_RESOURCES;
30+
31+
/** Returns the SQL spelling, for example {@code BINARY LOGS}. */
32+
public String getKeyword() {
33+
return this == OPTIMIZER_COSTS || this == USER_RESOURCES ? name()
34+
: name().replace('_', ' ');
35+
}
36+
37+
/**
38+
* Returns the option spelled by the keyword, ignoring case, or null.
39+
*
40+
* @param keyword the words of the option separated by one space, such as
41+
* {@code binary logs}
42+
*/
43+
public static Kind fromKeyword(String keyword) {
44+
String upper = keyword == null ? null : keyword.toUpperCase(Locale.ROOT);
45+
for (Kind kind : values()) {
46+
if (kind.getKeyword().equals(upper)) {
47+
return kind;
48+
}
49+
}
50+
return null;
51+
}
52+
}
53+
54+
public FlushOption(Kind kind) {
55+
setKind(kind);
56+
}
57+
58+
public Kind getKind() {
59+
return kind;
60+
}
61+
62+
public void setKind(Kind kind) {
63+
this.kind = Objects.requireNonNull(kind);
64+
}
65+
66+
public FlushOption withKind(Kind kind) {
67+
setKind(kind);
68+
return this;
69+
}
70+
71+
/** Returns the channel of {@code RELAY LOGS FOR CHANNEL 'name'}, or null. */
72+
public StringValue getChannel() {
73+
return channel;
74+
}
75+
76+
public void setChannel(StringValue channel) {
77+
this.channel = channel;
78+
}
79+
80+
public FlushOption withChannel(StringValue channel) {
81+
setChannel(channel);
82+
return this;
83+
}
84+
85+
public StringBuilder appendTo(StringBuilder builder) {
86+
builder.append(kind.getKeyword());
87+
if (channel != null) {
88+
builder.append(" FOR CHANNEL ").append(channel);
89+
}
90+
return builder;
91+
}
92+
93+
@Override
94+
public String toString() {
95+
return appendTo(new StringBuilder()).toString();
96+
}
97+
}

0 commit comments

Comments
 (0)