Skip to content

Commit 6362141

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 a30a679 commit 6362141

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
@@ -150,6 +150,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
150150
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `CHECKSUM` · `OPTIMIZE` · `REPAIR TABLE` |
151151
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
152152
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
153+
| **MySQL replication** | `CHANGE REPLICATION SOURCE TO` · `CHANGE REPLICATION FILTER` · `START`/`STOP REPLICA` · `RESET REPLICA` · `RESET BINARY LOGS AND GTIDS` · `PURGE BINARY LOGS` |
153154
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
154155
| **Tablespaces** | MySQL and PostgreSQL `CREATE TABLESPACE` · `ALTER TABLESPACE` |
155156
| **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
@@ -562,6 +562,12 @@ public enum Feature {
562562
* @see net.sf.jsqlparser.statement.PurgeStatement
563563
*/
564564
purge,
565+
/**
566+
* MySQL/MariaDB "PURGE BINARY LOGS" and MariaDB "PURGE MASTER LOGS" statements are allowed
567+
*
568+
* @see net.sf.jsqlparser.statement.PurgeStatement
569+
*/
570+
purgeBinaryLogs,
565571
/**
566572
* PostgreSQL "REASSIGN OWNED BY ... TO ..." statement is allowed
567573
*
@@ -782,6 +788,36 @@ public enum Feature {
782788
* @see BackupLockStatement
783789
*/
784790
backupLock,
791+
/**
792+
* MySQL "CHANGE REPLICATION SOURCE TO" statement is allowed
793+
*
794+
* @see net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement
795+
*/
796+
changeReplicationSource,
797+
/**
798+
* MySQL "CHANGE REPLICATION FILTER" statement is allowed
799+
*
800+
* @see net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement
801+
*/
802+
changeReplicationFilter,
803+
/**
804+
* MySQL "START REPLICA" statement is allowed
805+
*
806+
* @see net.sf.jsqlparser.statement.replication.StartReplicaStatement
807+
*/
808+
startReplica,
809+
/**
810+
* MySQL "STOP REPLICA" statement is allowed
811+
*
812+
* @see net.sf.jsqlparser.statement.replication.StopReplicaStatement
813+
*/
814+
stopReplica,
815+
/**
816+
* MySQL "RESET BINARY LOGS AND GTIDS" and "RESET REPLICA" statements are allowed
817+
*
818+
* @see net.sf.jsqlparser.statement.replication.ResetReplicationStatement
819+
*/
820+
resetReplication,
785821
/**
786822
* SQL "COMMENT ON" statement is allowed
787823
*

‎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;
@@ -682,6 +687,10 @@ public <S> Void visit(LoadDataStatement loadDataStatement, S context) {
682687

683688
@Override
684689
public <S> Void visit(PurgeStatement purgeStatement, S context) {
690+
if (purgeStatement.getLogBound() != null) {
691+
// removes binary log files, a server operation like the replication statements
692+
return sessionOnly();
693+
}
685694
analysis.claimTopLevel();
686695
analysis.certain(StmtFeature.MODIFIES_SCHEMA, StmtFeature.MODIFIES_DATA);
687696
return null;
@@ -945,6 +954,32 @@ public <S> Void visit(BackupLockStatement backupLock, S context) {
945954
return transactionOnly();
946955
}
947956

957+
// the replication statements change server state, like ALTER SYSTEM, and no data or schema
958+
@Override
959+
public <S> Void visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
960+
return sessionOnly();
961+
}
962+
963+
@Override
964+
public <S> Void visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
965+
return sessionOnly();
966+
}
967+
968+
@Override
969+
public <S> Void visit(StartReplicaStatement startReplica, S context) {
970+
return sessionOnly();
971+
}
972+
973+
@Override
974+
public <S> Void visit(StopReplicaStatement stopReplica, S context) {
975+
return sessionOnly();
976+
}
977+
978+
@Override
979+
public <S> Void visit(ResetReplicationStatement resetReplication, S context) {
980+
return sessionOnly();
981+
}
982+
948983
private Void transactionOnly() {
949984
analysis.claimTopLevel();
950985
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
@@ -98,6 +98,11 @@
9898
import net.sf.jsqlparser.statement.maintenance.VacuumStatement;
9999
import net.sf.jsqlparser.statement.merge.Merge;
100100
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
101+
import net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement;
102+
import net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement;
103+
import net.sf.jsqlparser.statement.replication.ResetReplicationStatement;
104+
import net.sf.jsqlparser.statement.replication.StartReplicaStatement;
105+
import net.sf.jsqlparser.statement.replication.StopReplicaStatement;
101106
import net.sf.jsqlparser.statement.reindex.Reindex;
102107
import net.sf.jsqlparser.statement.securitylabel.SecurityLabel;
103108
import net.sf.jsqlparser.statement.select.Select;
@@ -736,6 +741,46 @@ default void visit(BackupLockStatement backupLock) {
736741
visit(backupLock, null);
737742
}
738743

744+
default <S> T visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
745+
return null;
746+
}
747+
748+
default void visit(ChangeReplicationSourceStatement changeReplicationSource) {
749+
visit(changeReplicationSource, null);
750+
}
751+
752+
default <S> T visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
753+
return null;
754+
}
755+
756+
default void visit(ChangeReplicationFilterStatement changeReplicationFilter) {
757+
visit(changeReplicationFilter, null);
758+
}
759+
760+
default <S> T visit(StartReplicaStatement startReplica, S context) {
761+
return null;
762+
}
763+
764+
default void visit(StartReplicaStatement startReplica) {
765+
visit(startReplica, null);
766+
}
767+
768+
default <S> T visit(StopReplicaStatement stopReplica, S context) {
769+
return null;
770+
}
771+
772+
default void visit(StopReplicaStatement stopReplica) {
773+
visit(stopReplica, null);
774+
}
775+
776+
default <S> T visit(ResetReplicationStatement resetReplication, S context) {
777+
return null;
778+
}
779+
780+
default void visit(ResetReplicationStatement resetReplication) {
781+
visit(resetReplication, null);
782+
}
783+
739784
<S> T visit(CreatePolicy createPolicy, S context);
740785

741786
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
@@ -103,6 +103,11 @@
103103
import net.sf.jsqlparser.statement.merge.MergeOperationVisitor;
104104
import net.sf.jsqlparser.statement.merge.MergeOperationVisitorAdapter;
105105
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
106+
import net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement;
107+
import net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement;
108+
import net.sf.jsqlparser.statement.replication.ResetReplicationStatement;
109+
import net.sf.jsqlparser.statement.replication.StartReplicaStatement;
110+
import net.sf.jsqlparser.statement.replication.StopReplicaStatement;
106111
import net.sf.jsqlparser.statement.reindex.Reindex;
107112
import net.sf.jsqlparser.statement.securitylabel.SecurityLabel;
108113
import net.sf.jsqlparser.statement.select.FromItemVisitor;
@@ -569,6 +574,31 @@ public <S> T visit(BackupLockStatement backupLock, S context) {
569574
return null;
570575
}
571576

577+
@Override
578+
public <S> T visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
579+
return null;
580+
}
581+
582+
@Override
583+
public <S> T visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
584+
return null;
585+
}
586+
587+
@Override
588+
public <S> T visit(StartReplicaStatement startReplica, S context) {
589+
return null;
590+
}
591+
592+
@Override
593+
public <S> T visit(StopReplicaStatement stopReplica, S context) {
594+
return null;
595+
}
596+
597+
@Override
598+
public <S> T visit(ResetReplicationStatement resetReplication, S context) {
599+
return null;
600+
}
601+
572602
@Override
573603
public <S> T visit(CreatePolicy createPolicy, S context) {
574604
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)