Skip to content

Commit 0c9e4c7

Browse files
committed
feat: parse the MySQL replication statements
CHANGE REPLICATION SOURCE TO, CHANGE REPLICATION FILTER, START REPLICA, STOP REPLICA, RESET REPLICA, RESET BINARY LOGS AND GTIDS and PURGE BINARY LOGS failed to parse, so a replica setup script failed at its first statement. The new statements of net.sf.jsqlparser.statement.replication follow the MySQL 8.4 grammar. ReplicationSourceOption extends ReplicationOption, and each option kind says which value it takes: a string, a number, NULL, 0 or 1, a keyword, the server ID list or an account. ReplicationFilter keeps database names, tables, patterns or rewrite pairs, and the statements share one FOR CHANNEL production. RESET reads MySQL's comma list into ResetReplicationStatement, apart from ResetStatement. PurgeStatement gains BINARY LOGS TO and BEFORE, which MariaDB also reads, with its MASTER LOGS; as in MySQL, BEFORE takes no subquery. Without a dialect a bare RESET REPLICA stays a ResetStatement, and other dialects keep their readings. STOP becomes a non-reserved keyword and remains usable as a name, also in Oracle's PURGE TABLESPACE. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent e27929f commit 0c9e4c7

27 files changed

Lines changed: 2435 additions & 13 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 replication** | `CHANGE REPLICATION SOURCE TO` · `CHANGE REPLICATION FILTER` · `START`/`STOP REPLICA` · `RESET REPLICA` · `RESET BINARY LOGS AND GTIDS` · `PURGE BINARY LOGS` |
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/feature/Feature.java‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,12 @@ public enum Feature {
564564
* @see net.sf.jsqlparser.statement.PurgeStatement
565565
*/
566566
purge,
567+
/**
568+
* MySQL/MariaDB "PURGE BINARY LOGS" and MariaDB "PURGE MASTER LOGS" statements are allowed
569+
*
570+
* @see net.sf.jsqlparser.statement.PurgeStatement
571+
*/
572+
purgeBinaryLogs,
567573
/**
568574
* PostgreSQL "REASSIGN OWNED BY ... TO ..." statement is allowed
569575
*
@@ -796,6 +802,36 @@ public enum Feature {
796802
* @see BackupLockStatement
797803
*/
798804
backupLock,
805+
/**
806+
* MySQL "CHANGE REPLICATION SOURCE TO" statement is allowed
807+
*
808+
* @see net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement
809+
*/
810+
changeReplicationSource,
811+
/**
812+
* MySQL "CHANGE REPLICATION FILTER" statement is allowed
813+
*
814+
* @see net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement
815+
*/
816+
changeReplicationFilter,
817+
/**
818+
* MySQL "START REPLICA" statement is allowed
819+
*
820+
* @see net.sf.jsqlparser.statement.replication.StartReplicaStatement
821+
*/
822+
startReplica,
823+
/**
824+
* MySQL "STOP REPLICA" statement is allowed
825+
*
826+
* @see net.sf.jsqlparser.statement.replication.StopReplicaStatement
827+
*/
828+
stopReplica,
829+
/**
830+
* MySQL "RESET BINARY LOGS AND GTIDS" and "RESET REPLICA" statements are allowed
831+
*
832+
* @see net.sf.jsqlparser.statement.replication.ResetReplicationStatement
833+
*/
834+
resetReplication,
799835
/**
800836
* SQL "COMMENT ON" statement is allowed
801837
*

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
* @author <a href="mailto:andreas@manticore-projects.com">Andreas Reichel</a>
1616
*/
1717
public enum PurgeObjectType {
18-
TABLE, INDEX, RECYCLEBIN, DBA_RECYCLEBIN, TABLESPACE;
18+
TABLE, INDEX, RECYCLEBIN, DBA_RECYCLEBIN, TABLESPACE,
19+
/** MySQL and MariaDB {@code PURGE BINARY LOGS} */
20+
BINARY_LOGS,
21+
/** MariaDB {@code PURGE MASTER LOGS}, which MySQL 8.4 removed */
22+
MASTER_LOGS;
1923

2024
public static PurgeObjectType from(String type) {
2125
return Enum.valueOf(PurgeObjectType.class, type.toUpperCase(Locale.ROOT));

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

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
package net.sf.jsqlparser.statement;
1212

1313
import java.util.Objects;
14+
import java.util.function.Consumer;
1415

16+
import net.sf.jsqlparser.expression.Expression;
1517
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1618
import net.sf.jsqlparser.schema.Table;
1719
import net.sf.jsqlparser.statement.create.table.Index;
@@ -20,12 +22,23 @@
2022
* @author <a href="mailto:andreas@manticore-projects.com">Andreas Reichel</a>
2123
* @see <a href=
2224
* "https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9018.htm">Purge</a>
25+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/purge-binary-logs.html">MySQL PURGE BINARY
26+
* LOGS</a>
2327
*/
2428

2529
public class PurgeStatement extends ASTNodeAccessImpl implements Statement {
2630
private final PurgeObjectType purgeObjectType;
2731
private final Object object;
2832
private String userName;
33+
private LogBound logBound;
34+
35+
/** How {@code PURGE BINARY LOGS} gives the first log it keeps. */
36+
public enum LogBound {
37+
/** {@code TO 'log_name'}: the logs before the named one */
38+
TO,
39+
/** {@code BEFORE datetime_expr}: the logs older than a point in time */
40+
BEFORE
41+
}
2942

3043
public PurgeStatement(Table table) {
3144
this.purgeObjectType = PurgeObjectType.TABLE;
@@ -51,15 +64,43 @@ public PurgeStatement(PurgeObjectType purgeObjectType, String tableSpaceName, St
5164
this.userName = userName;
5265
}
5366

67+
/**
68+
* MySQL {@code PURGE {BINARY | MASTER} LOGS {TO 'log_name' | BEFORE expr}}; for {@code TO} the
69+
* expression is the string literal of the log name.
70+
*/
71+
public PurgeStatement(PurgeObjectType purgeObjectType, LogBound logBound,
72+
Expression logExpression) {
73+
if (purgeObjectType != PurgeObjectType.BINARY_LOGS
74+
&& purgeObjectType != PurgeObjectType.MASTER_LOGS) {
75+
throw new IllegalArgumentException(
76+
"Only BINARY LOGS and MASTER LOGS take TO or BEFORE: " + purgeObjectType);
77+
}
78+
this.purgeObjectType = purgeObjectType;
79+
this.logBound = Objects.requireNonNull(logBound,
80+
"The TO or BEFORE of the PURGE LOGS statement must not be null.");
81+
this.object = Objects.requireNonNull(logExpression,
82+
"The log name or point in time of the PURGE LOGS statement must not be null.");
83+
}
84+
5485
@Override
5586
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
5687
return statementVisitor.visit(this, context);
5788
}
5889

90+
public StringBuilder appendTo(StringBuilder builder) {
91+
return appendTo(builder, builder::append);
92+
}
93+
5994
@SuppressWarnings({"PMD.MissingBreakInSwitch", "PMD.SwitchStmtsShouldHaveDefault",
6095
"PMD.CyclomaticComplexity"})
61-
public StringBuilder appendTo(StringBuilder builder) {
96+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressions) {
6297
builder.append("PURGE ");
98+
if (logBound != null) {
99+
builder.append(purgeObjectType == PurgeObjectType.BINARY_LOGS ? "BINARY" : "MASTER")
100+
.append(" LOGS ").append(logBound).append(' ');
101+
expressions.accept((Expression) object);
102+
return builder;
103+
}
63104

64105
switch (purgeObjectType) {
65106
case RECYCLEBIN:
@@ -106,4 +147,17 @@ public PurgeObjectType getPurgeObjectType() {
106147
public Object getObject() {
107148
return object;
108149
}
150+
151+
/** TO or BEFORE of {@code PURGE BINARY LOGS}, null for the other object types. */
152+
public LogBound getLogBound() {
153+
return logBound;
154+
}
155+
156+
/**
157+
* The log name literal of {@code TO} or the expression of {@code BEFORE}, null for the other
158+
* object types. It is the same object as {@link #getObject()}.
159+
*/
160+
public Expression getLogExpression() {
161+
return logBound != null ? (Expression) object : null;
162+
}
109163
}

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@
101101
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
102102
import net.sf.jsqlparser.statement.lock.LockStatement;
103103
import net.sf.jsqlparser.statement.lock.UnlockStatement;
104+
import net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement;
105+
import net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement;
106+
import net.sf.jsqlparser.statement.replication.ResetReplicationStatement;
107+
import net.sf.jsqlparser.statement.replication.StartReplicaStatement;
108+
import net.sf.jsqlparser.statement.replication.StopReplicaStatement;
104109
import net.sf.jsqlparser.statement.maintenance.CheckpointStatement;
105110
import net.sf.jsqlparser.statement.maintenance.ClusterStatement;
106111
import net.sf.jsqlparser.statement.maintenance.VacuumStatement;
@@ -683,6 +688,10 @@ public <S> Void visit(LoadDataStatement loadDataStatement, S context) {
683688

684689
@Override
685690
public <S> Void visit(PurgeStatement purgeStatement, S context) {
691+
if (purgeStatement.getLogBound() != null) {
692+
// removes binary log files, a server operation like the replication statements
693+
return sessionOnly();
694+
}
686695
analysis.claimTopLevel();
687696
analysis.certain(StmtFeature.MODIFIES_SCHEMA, StmtFeature.MODIFIES_DATA);
688697
return null;
@@ -958,6 +967,32 @@ public <S> Void visit(BackupLockStatement backupLock, S context) {
958967
return transactionOnly();
959968
}
960969

970+
// the replication statements change server state, like ALTER SYSTEM, and no data or schema
971+
@Override
972+
public <S> Void visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
973+
return sessionOnly();
974+
}
975+
976+
@Override
977+
public <S> Void visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
978+
return sessionOnly();
979+
}
980+
981+
@Override
982+
public <S> Void visit(StartReplicaStatement startReplica, S context) {
983+
return sessionOnly();
984+
}
985+
986+
@Override
987+
public <S> Void visit(StopReplicaStatement stopReplica, S context) {
988+
return sessionOnly();
989+
}
990+
991+
@Override
992+
public <S> Void visit(ResetReplicationStatement resetReplication, S context) {
993+
return sessionOnly();
994+
}
995+
961996
private Void transactionOnly() {
962997
analysis.claimTopLevel();
963998
analysis.certain(StmtFeature.MODIFIES_TRANSACTION);

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@
9999
import net.sf.jsqlparser.statement.maintenance.VacuumStatement;
100100
import net.sf.jsqlparser.statement.merge.Merge;
101101
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
102+
import net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement;
103+
import net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement;
104+
import net.sf.jsqlparser.statement.replication.ResetReplicationStatement;
105+
import net.sf.jsqlparser.statement.replication.StartReplicaStatement;
106+
import net.sf.jsqlparser.statement.replication.StopReplicaStatement;
102107
import net.sf.jsqlparser.statement.reindex.Reindex;
103108
import net.sf.jsqlparser.statement.securitylabel.SecurityLabel;
104109
import net.sf.jsqlparser.statement.select.Select;
@@ -753,6 +758,46 @@ default void visit(BackupLockStatement backupLock) {
753758
visit(backupLock, null);
754759
}
755760

761+
default <S> T visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
762+
return null;
763+
}
764+
765+
default void visit(ChangeReplicationSourceStatement changeReplicationSource) {
766+
visit(changeReplicationSource, null);
767+
}
768+
769+
default <S> T visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
770+
return null;
771+
}
772+
773+
default void visit(ChangeReplicationFilterStatement changeReplicationFilter) {
774+
visit(changeReplicationFilter, null);
775+
}
776+
777+
default <S> T visit(StartReplicaStatement startReplica, S context) {
778+
return null;
779+
}
780+
781+
default void visit(StartReplicaStatement startReplica) {
782+
visit(startReplica, null);
783+
}
784+
785+
default <S> T visit(StopReplicaStatement stopReplica, S context) {
786+
return null;
787+
}
788+
789+
default void visit(StopReplicaStatement stopReplica) {
790+
visit(stopReplica, null);
791+
}
792+
793+
default <S> T visit(ResetReplicationStatement resetReplication, S context) {
794+
return null;
795+
}
796+
797+
default void visit(ResetReplicationStatement resetReplication) {
798+
visit(resetReplication, null);
799+
}
800+
756801
<S> T visit(CreatePolicy createPolicy, S context);
757802

758803
default void visit(CreatePolicy createPolicy) {

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@
104104
import net.sf.jsqlparser.statement.merge.MergeOperationVisitor;
105105
import net.sf.jsqlparser.statement.merge.MergeOperationVisitorAdapter;
106106
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
107+
import net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement;
108+
import net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement;
109+
import net.sf.jsqlparser.statement.replication.ResetReplicationStatement;
110+
import net.sf.jsqlparser.statement.replication.StartReplicaStatement;
111+
import net.sf.jsqlparser.statement.replication.StopReplicaStatement;
107112
import net.sf.jsqlparser.statement.reindex.Reindex;
108113
import net.sf.jsqlparser.statement.securitylabel.SecurityLabel;
109114
import net.sf.jsqlparser.statement.select.FromItem;
@@ -587,6 +592,31 @@ public <S> T visit(BackupLockStatement backupLock, S context) {
587592
return null;
588593
}
589594

595+
@Override
596+
public <S> T visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
597+
return null;
598+
}
599+
600+
@Override
601+
public <S> T visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
602+
return null;
603+
}
604+
605+
@Override
606+
public <S> T visit(StartReplicaStatement startReplica, S context) {
607+
return null;
608+
}
609+
610+
@Override
611+
public <S> T visit(StopReplicaStatement stopReplica, S context) {
612+
return null;
613+
}
614+
615+
@Override
616+
public <S> T visit(ResetReplicationStatement resetReplication, S context) {
617+
return null;
618+
}
619+
590620
@Override
591621
public <S> T visit(CreatePolicy createPolicy, S context) {
592622
fromItemVisitor.visitFromItem(createPolicy.getTable(), context);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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.replication;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import net.sf.jsqlparser.expression.StringValue;
15+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
16+
import net.sf.jsqlparser.statement.Statement;
17+
import net.sf.jsqlparser.statement.StatementVisitor;
18+
19+
/**
20+
* MySQL {@code CHANGE REPLICATION FILTER filter [, filter] ... [FOR CHANNEL 'channel']}.<br>
21+
* Example:<br>
22+
* CHANGE REPLICATION FILTER REPLICATE_DO_DB = (d1, d2), REPLICATE_REWRITE_DB = ((a, b))<br>
23+
*
24+
* @see <a href="https://dev.mysql.com/doc/refman/8.4/en/change-replication-filter.html">MySQL
25+
* CHANGE REPLICATION FILTER</a>
26+
*/
27+
public class ChangeReplicationFilterStatement extends ASTNodeAccessImpl implements Statement {
28+
private List<ReplicationFilter> filters = new ArrayList<>();
29+
private StringValue channel;
30+
31+
public List<ReplicationFilter> getFilters() {
32+
return filters;
33+
}
34+
35+
public void setFilters(List<ReplicationFilter> filters) {
36+
this.filters = filters;
37+
}
38+
39+
/** The channel of FOR CHANNEL, or null when the filters apply to every channel. */
40+
public StringValue getChannel() {
41+
return channel;
42+
}
43+
44+
public void setChannel(StringValue channel) {
45+
this.channel = channel;
46+
}
47+
48+
public StringBuilder appendTo(StringBuilder builder) {
49+
builder.append("CHANGE REPLICATION FILTER ");
50+
for (int i = 0; i < filters.size(); i++) {
51+
if (i > 0) {
52+
builder.append(", ");
53+
}
54+
filters.get(i).appendTo(builder);
55+
}
56+
if (channel != null) {
57+
builder.append(" FOR CHANNEL ").append(channel);
58+
}
59+
return builder;
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return appendTo(new StringBuilder()).toString();
65+
}
66+
67+
@Override
68+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
69+
return statementVisitor.visit(this, context);
70+
}
71+
}

0 commit comments

Comments
 (0)