diff --git a/README.md b/README.md
index e7a8db4521..dcc7744a27 100644
--- a/README.md
+++ b/README.md
@@ -150,6 +150,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `CHECKSUM` · `OPTIMIZE` · `REPAIR TABLE` |
| **PostgreSQL maintenance** | `VACUUM` · `ANALYZE` · `CLUSTER` · `CHECKPOINT` |
| **MySQL locks** | `LOCK TABLES` · `UNLOCK TABLES` · `LOCK INSTANCE FOR BACKUP` · `UNLOCK INSTANCE` |
+| **MySQL replication** | `CHANGE REPLICATION SOURCE TO` · `CHANGE REPLICATION FILTER` · `START`/`STOP REPLICA` · `RESET REPLICA` · `RESET BINARY LOGS AND GTIDS` · `PURGE BINARY LOGS` |
| **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` |
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 447245f4aa..7af53cc489 100644
--- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
+++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
@@ -562,6 +562,12 @@ public enum Feature {
* @see net.sf.jsqlparser.statement.PurgeStatement
*/
purge,
+ /**
+ * MySQL/MariaDB "PURGE BINARY LOGS" and MariaDB "PURGE MASTER LOGS" statements are allowed
+ *
+ * @see net.sf.jsqlparser.statement.PurgeStatement
+ */
+ purgeBinaryLogs,
/**
* PostgreSQL "REASSIGN OWNED BY ... TO ..." statement is allowed
*
@@ -782,6 +788,36 @@ public enum Feature {
* @see BackupLockStatement
*/
backupLock,
+ /**
+ * MySQL "CHANGE REPLICATION SOURCE TO" statement is allowed
+ *
+ * @see net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement
+ */
+ changeReplicationSource,
+ /**
+ * MySQL "CHANGE REPLICATION FILTER" statement is allowed
+ *
+ * @see net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement
+ */
+ changeReplicationFilter,
+ /**
+ * MySQL "START REPLICA" statement is allowed
+ *
+ * @see net.sf.jsqlparser.statement.replication.StartReplicaStatement
+ */
+ startReplica,
+ /**
+ * MySQL "STOP REPLICA" statement is allowed
+ *
+ * @see net.sf.jsqlparser.statement.replication.StopReplicaStatement
+ */
+ stopReplica,
+ /**
+ * MySQL "RESET BINARY LOGS AND GTIDS" and "RESET REPLICA" statements are allowed
+ *
+ * @see net.sf.jsqlparser.statement.replication.ResetReplicationStatement
+ */
+ resetReplication,
/**
* SQL "COMMENT ON" statement is allowed
*
diff --git a/src/main/java/net/sf/jsqlparser/statement/PurgeObjectType.java b/src/main/java/net/sf/jsqlparser/statement/PurgeObjectType.java
index 7535d34675..8e7612f233 100644
--- a/src/main/java/net/sf/jsqlparser/statement/PurgeObjectType.java
+++ b/src/main/java/net/sf/jsqlparser/statement/PurgeObjectType.java
@@ -15,7 +15,11 @@
* @author Andreas Reichel
*/
public enum PurgeObjectType {
- TABLE, INDEX, RECYCLEBIN, DBA_RECYCLEBIN, TABLESPACE;
+ TABLE, INDEX, RECYCLEBIN, DBA_RECYCLEBIN, TABLESPACE,
+ /** MySQL and MariaDB {@code PURGE BINARY LOGS} */
+ BINARY_LOGS,
+ /** MariaDB {@code PURGE MASTER LOGS}, which MySQL 8.4 removed */
+ MASTER_LOGS;
public static PurgeObjectType from(String type) {
return Enum.valueOf(PurgeObjectType.class, type.toUpperCase(Locale.ROOT));
diff --git a/src/main/java/net/sf/jsqlparser/statement/PurgeStatement.java b/src/main/java/net/sf/jsqlparser/statement/PurgeStatement.java
index a1b1f634a4..f2c736b617 100644
--- a/src/main/java/net/sf/jsqlparser/statement/PurgeStatement.java
+++ b/src/main/java/net/sf/jsqlparser/statement/PurgeStatement.java
@@ -11,7 +11,9 @@
package net.sf.jsqlparser.statement;
import java.util.Objects;
+import java.util.function.Consumer;
+import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.create.table.Index;
@@ -20,12 +22,23 @@
* @author Andreas Reichel
* @see Purge
+ * @see MySQL PURGE BINARY
+ * LOGS
*/
public class PurgeStatement extends ASTNodeAccessImpl implements Statement {
private final PurgeObjectType purgeObjectType;
private final Object object;
private String userName;
+ private LogBound logBound;
+
+ /** How {@code PURGE BINARY LOGS} gives the first log it keeps. */
+ public enum LogBound {
+ /** {@code TO 'log_name'}: the logs before the named one */
+ TO,
+ /** {@code BEFORE datetime_expr}: the logs older than a point in time */
+ BEFORE
+ }
public PurgeStatement(Table table) {
this.purgeObjectType = PurgeObjectType.TABLE;
@@ -51,15 +64,43 @@ public PurgeStatement(PurgeObjectType purgeObjectType, String tableSpaceName, St
this.userName = userName;
}
+ /**
+ * MySQL {@code PURGE {BINARY | MASTER} LOGS {TO 'log_name' | BEFORE expr}}; for {@code TO} the
+ * expression is the string literal of the log name.
+ */
+ public PurgeStatement(PurgeObjectType purgeObjectType, LogBound logBound,
+ Expression logExpression) {
+ if (purgeObjectType != PurgeObjectType.BINARY_LOGS
+ && purgeObjectType != PurgeObjectType.MASTER_LOGS) {
+ throw new IllegalArgumentException(
+ "Only BINARY LOGS and MASTER LOGS take TO or BEFORE: " + purgeObjectType);
+ }
+ this.purgeObjectType = purgeObjectType;
+ this.logBound = Objects.requireNonNull(logBound,
+ "The TO or BEFORE of the PURGE LOGS statement must not be null.");
+ this.object = Objects.requireNonNull(logExpression,
+ "The log name or point in time of the PURGE LOGS statement must not be null.");
+ }
+
@Override
public T accept(StatementVisitor statementVisitor, S context) {
return statementVisitor.visit(this, context);
}
+ public StringBuilder appendTo(StringBuilder builder) {
+ return appendTo(builder, builder::append);
+ }
+
@SuppressWarnings({"PMD.MissingBreakInSwitch", "PMD.SwitchStmtsShouldHaveDefault",
"PMD.CyclomaticComplexity"})
- public StringBuilder appendTo(StringBuilder builder) {
+ public StringBuilder appendTo(StringBuilder builder, Consumer expressions) {
builder.append("PURGE ");
+ if (logBound != null) {
+ builder.append(purgeObjectType == PurgeObjectType.BINARY_LOGS ? "BINARY" : "MASTER")
+ .append(" LOGS ").append(logBound).append(' ');
+ expressions.accept((Expression) object);
+ return builder;
+ }
switch (purgeObjectType) {
case RECYCLEBIN:
@@ -106,4 +147,17 @@ public PurgeObjectType getPurgeObjectType() {
public Object getObject() {
return object;
}
+
+ /** TO or BEFORE of {@code PURGE BINARY LOGS}, null for the other object types. */
+ public LogBound getLogBound() {
+ return logBound;
+ }
+
+ /**
+ * The log name literal of {@code TO} or the expression of {@code BEFORE}, null for the other
+ * object types. It is the same object as {@link #getObject()}.
+ */
+ public Expression getLogExpression() {
+ return logBound != null ? (Expression) object : null;
+ }
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
index f91ea876a6..1bf7ec0cf3 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
@@ -101,6 +101,11 @@
import net.sf.jsqlparser.statement.lock.BackupLockStatement;
import net.sf.jsqlparser.statement.lock.LockStatement;
import net.sf.jsqlparser.statement.lock.UnlockStatement;
+import net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement;
+import net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement;
+import net.sf.jsqlparser.statement.replication.ResetReplicationStatement;
+import net.sf.jsqlparser.statement.replication.StartReplicaStatement;
+import net.sf.jsqlparser.statement.replication.StopReplicaStatement;
import net.sf.jsqlparser.statement.maintenance.CheckpointStatement;
import net.sf.jsqlparser.statement.maintenance.ClusterStatement;
import net.sf.jsqlparser.statement.maintenance.VacuumStatement;
@@ -682,6 +687,10 @@ public Void visit(LoadDataStatement loadDataStatement, S context) {
@Override
public Void visit(PurgeStatement purgeStatement, S context) {
+ if (purgeStatement.getLogBound() != null) {
+ // removes binary log files, a server operation like the replication statements
+ return sessionOnly();
+ }
analysis.claimTopLevel();
analysis.certain(StmtFeature.MODIFIES_SCHEMA, StmtFeature.MODIFIES_DATA);
return null;
@@ -945,6 +954,32 @@ public Void visit(BackupLockStatement backupLock, S context) {
return transactionOnly();
}
+ // the replication statements change server state, like ALTER SYSTEM, and no data or schema
+ @Override
+ public Void visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
+ return sessionOnly();
+ }
+
+ @Override
+ public Void visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
+ return sessionOnly();
+ }
+
+ @Override
+ public Void visit(StartReplicaStatement startReplica, S context) {
+ return sessionOnly();
+ }
+
+ @Override
+ public Void visit(StopReplicaStatement stopReplica, S context) {
+ return sessionOnly();
+ }
+
+ @Override
+ public Void visit(ResetReplicationStatement resetReplication, S context) {
+ return sessionOnly();
+ }
+
private Void transactionOnly() {
analysis.claimTopLevel();
analysis.certain(StmtFeature.MODIFIES_TRANSACTION);
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
index bd9119530e..a99a031045 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
@@ -98,6 +98,11 @@
import net.sf.jsqlparser.statement.maintenance.VacuumStatement;
import net.sf.jsqlparser.statement.merge.Merge;
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
+import net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement;
+import net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement;
+import net.sf.jsqlparser.statement.replication.ResetReplicationStatement;
+import net.sf.jsqlparser.statement.replication.StartReplicaStatement;
+import net.sf.jsqlparser.statement.replication.StopReplicaStatement;
import net.sf.jsqlparser.statement.reindex.Reindex;
import net.sf.jsqlparser.statement.securitylabel.SecurityLabel;
import net.sf.jsqlparser.statement.select.Select;
@@ -736,6 +741,46 @@ default void visit(BackupLockStatement backupLock) {
visit(backupLock, null);
}
+ default T visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
+ return null;
+ }
+
+ default void visit(ChangeReplicationSourceStatement changeReplicationSource) {
+ visit(changeReplicationSource, null);
+ }
+
+ default T visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
+ return null;
+ }
+
+ default void visit(ChangeReplicationFilterStatement changeReplicationFilter) {
+ visit(changeReplicationFilter, null);
+ }
+
+ default T visit(StartReplicaStatement startReplica, S context) {
+ return null;
+ }
+
+ default void visit(StartReplicaStatement startReplica) {
+ visit(startReplica, null);
+ }
+
+ default T visit(StopReplicaStatement stopReplica, S context) {
+ return null;
+ }
+
+ default void visit(StopReplicaStatement stopReplica) {
+ visit(stopReplica, null);
+ }
+
+ default T visit(ResetReplicationStatement resetReplication, S context) {
+ return null;
+ }
+
+ default void visit(ResetReplicationStatement resetReplication) {
+ visit(resetReplication, null);
+ }
+
T visit(CreatePolicy createPolicy, S context);
default void visit(CreatePolicy createPolicy) {
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
index 32a947382e..46f433763c 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
@@ -103,6 +103,11 @@
import net.sf.jsqlparser.statement.merge.MergeOperationVisitor;
import net.sf.jsqlparser.statement.merge.MergeOperationVisitorAdapter;
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
+import net.sf.jsqlparser.statement.replication.ChangeReplicationFilterStatement;
+import net.sf.jsqlparser.statement.replication.ChangeReplicationSourceStatement;
+import net.sf.jsqlparser.statement.replication.ResetReplicationStatement;
+import net.sf.jsqlparser.statement.replication.StartReplicaStatement;
+import net.sf.jsqlparser.statement.replication.StopReplicaStatement;
import net.sf.jsqlparser.statement.reindex.Reindex;
import net.sf.jsqlparser.statement.securitylabel.SecurityLabel;
import net.sf.jsqlparser.statement.select.FromItemVisitor;
@@ -569,6 +574,31 @@ public T visit(BackupLockStatement backupLock, S context) {
return null;
}
+ @Override
+ public T visit(ChangeReplicationSourceStatement changeReplicationSource, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(ChangeReplicationFilterStatement changeReplicationFilter, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(StartReplicaStatement startReplica, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(StopReplicaStatement stopReplica, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(ResetReplicationStatement resetReplication, S context) {
+ return null;
+ }
+
@Override
public T visit(CreatePolicy createPolicy, S context) {
fromItemVisitor.visitFromItem(createPolicy.getTable(), context);
diff --git a/src/main/java/net/sf/jsqlparser/statement/replication/ChangeReplicationFilterStatement.java b/src/main/java/net/sf/jsqlparser/statement/replication/ChangeReplicationFilterStatement.java
new file mode 100644
index 0000000000..37ea85b41b
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/replication/ChangeReplicationFilterStatement.java
@@ -0,0 +1,71 @@
+/*-
+ * #%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.replication;
+
+import java.util.ArrayList;
+import java.util.List;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+
+/**
+ * MySQL {@code CHANGE REPLICATION FILTER filter [, filter] ... [FOR CHANNEL 'channel']}.
+ * Example:
+ * CHANGE REPLICATION FILTER REPLICATE_DO_DB = (d1, d2), REPLICATE_REWRITE_DB = ((a, b))
+ *
+ * @see MySQL
+ * CHANGE REPLICATION FILTER
+ */
+public class ChangeReplicationFilterStatement extends ASTNodeAccessImpl implements Statement {
+ private List filters = new ArrayList<>();
+ private StringValue channel;
+
+ public List getFilters() {
+ return filters;
+ }
+
+ public void setFilters(List filters) {
+ this.filters = filters;
+ }
+
+ /** The channel of FOR CHANNEL, or null when the filters apply to every channel. */
+ public StringValue getChannel() {
+ return channel;
+ }
+
+ public void setChannel(StringValue channel) {
+ this.channel = channel;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("CHANGE REPLICATION FILTER ");
+ for (int i = 0; i < filters.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ filters.get(i).appendTo(builder);
+ }
+ if (channel != null) {
+ builder.append(" FOR CHANNEL ").append(channel);
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/replication/ChangeReplicationSourceStatement.java b/src/main/java/net/sf/jsqlparser/statement/replication/ChangeReplicationSourceStatement.java
new file mode 100644
index 0000000000..d2afb55de9
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/replication/ChangeReplicationSourceStatement.java
@@ -0,0 +1,74 @@
+/*-
+ * #%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.replication;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+
+/**
+ * MySQL {@code CHANGE REPLICATION SOURCE TO option [, option] ... [FOR CHANNEL 'channel']}.
+ * Example:
+ * CHANGE REPLICATION SOURCE TO SOURCE_HOST = 'h', SOURCE_PORT = 3306 FOR CHANNEL 'c1'
+ *
+ * @see MySQL
+ * CHANGE REPLICATION SOURCE TO
+ */
+public class ChangeReplicationSourceStatement extends ASTNodeAccessImpl implements Statement {
+ private List options = new ArrayList<>();
+ private StringValue channel;
+
+ public List getOptions() {
+ return options;
+ }
+
+ public void setOptions(List options) {
+ this.options = options;
+ }
+
+ /** The channel of FOR CHANNEL, or null for the default channel. */
+ public StringValue getChannel() {
+ return channel;
+ }
+
+ public void setChannel(StringValue channel) {
+ this.channel = channel;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder, Consumer expressions) {
+ builder.append("CHANGE REPLICATION SOURCE TO ");
+ for (int i = 0; i < options.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ options.get(i).appendTo(builder, expressions);
+ }
+ if (channel != null) {
+ builder.append(" FOR CHANNEL ").append(channel);
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ return appendTo(builder, builder::append).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/replication/ReplicaThread.java b/src/main/java/net/sf/jsqlparser/statement/replication/ReplicaThread.java
new file mode 100644
index 0000000000..f66006ebc7
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/replication/ReplicaThread.java
@@ -0,0 +1,30 @@
+/*-
+ * #%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.replication;
+
+import java.util.List;
+
+/**
+ * A replication thread that MySQL's {@code START REPLICA} and {@code STOP REPLICA} name.
+ *
+ * @see MySQL START REPLICA
+ */
+public enum ReplicaThread {
+ /** The receiver thread, which reads the source's binary log into the relay log. */
+ IO_THREAD,
+ /** The applier thread, which executes the events of the relay log. */
+ SQL_THREAD;
+
+ static void appendTo(StringBuilder builder, List threads) {
+ for (int i = 0; i < threads.size(); i++) {
+ builder.append(i == 0 ? " " : ", ").append(threads.get(i));
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/replication/ReplicaUntilOption.java b/src/main/java/net/sf/jsqlparser/statement/replication/ReplicaUntilOption.java
new file mode 100644
index 0000000000..2cbcd68416
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/replication/ReplicaUntilOption.java
@@ -0,0 +1,60 @@
+/*-
+ * #%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.replication;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.statement.replication.ReplicationSourceOption.ValueType;
+
+/**
+ * A condition of the {@code UNTIL} clause of MySQL's {@code START REPLICA}. The first condition may
+ * be any kind; the ones after it give further log coordinates.
+ *
+ * @see MySQL START REPLICA
+ */
+public class ReplicaUntilOption extends ReplicationOption {
+
+ public enum Kind {
+ /** A binary log file of the source */
+ SOURCE_LOG_FILE(ValueType.STRING, true),
+ /** A position in that binary log file */
+ SOURCE_LOG_POS(ValueType.NUMBER, true),
+ /** A relay log file */
+ RELAY_LOG_FILE(ValueType.STRING, true),
+ /** A position in that relay log file */
+ RELAY_LOG_POS(ValueType.NUMBER, true),
+ /** Stop before the first transaction of a GTID set */
+ SQL_BEFORE_GTIDS(ValueType.STRING, false),
+ /** Stop after the last transaction of a GTID set */
+ SQL_AFTER_GTIDS(ValueType.STRING, false),
+ /** Stop once a multithreaded applier has no gaps */
+ SQL_AFTER_MTS_GAPS(ValueType.NONE, false);
+
+ private final ValueType valueType;
+ private final boolean logCoordinate;
+
+ Kind(ValueType valueType, boolean logCoordinate) {
+ this.valueType = valueType;
+ this.logCoordinate = logCoordinate;
+ }
+
+ public ValueType getValueType() {
+ return valueType;
+ }
+
+ /** Whether this is a log file name or position, which may follow another condition. */
+ public boolean isLogCoordinate() {
+ return logCoordinate;
+ }
+ }
+
+ public ReplicaUntilOption(String name, Kind kind, Expression value) {
+ super(name, kind, value);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/replication/ReplicationFilter.java b/src/main/java/net/sf/jsqlparser/statement/replication/ReplicationFilter.java
new file mode 100644
index 0000000000..4f883d5260
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/replication/ReplicationFilter.java
@@ -0,0 +1,153 @@
+/*-
+ * #%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.replication;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.schema.Table;
+
+/**
+ * A rule of MySQL's {@code CHANGE REPLICATION FILTER}: a filter name and a parenthesized, possibly
+ * empty list. The list holds database names, qualified table names, quoted wildcard patterns or
+ * pairs of database names, depending on {@link Kind#getValueType()}; the list of the other value
+ * types stays empty.
+ *
+ * @see MySQL
+ * CHANGE REPLICATION FILTER
+ */
+public class ReplicationFilter implements Serializable {
+ private final Kind kind;
+ private final List databases = new ArrayList<>();
+ private final List tables = new ArrayList<>();
+ private final List patterns = new ArrayList<>();
+ private final List rewrites = new ArrayList<>();
+
+ /** What the list of a filter holds. */
+ public enum ValueType {
+ /** Database names: {@code (d1, d2)} */
+ DATABASES,
+ /** Database-qualified table names: {@code (d1.t1)} */
+ TABLES,
+ /** Quoted patterns of database and table names: {@code ('d%.t%')} */
+ PATTERNS,
+ /** Pairs of database names: {@code ((from_db, to_db))} */
+ DATABASE_PAIRS
+ }
+
+ public enum Kind {
+ /** Replicate only these databases */
+ REPLICATE_DO_DB(ValueType.DATABASES),
+ /** Skip these databases */
+ REPLICATE_IGNORE_DB(ValueType.DATABASES),
+ /** Replicate only these tables */
+ REPLICATE_DO_TABLE(ValueType.TABLES),
+ /** Skip these tables */
+ REPLICATE_IGNORE_TABLE(ValueType.TABLES),
+ /** Replicate only tables matching these patterns */
+ REPLICATE_WILD_DO_TABLE(ValueType.PATTERNS),
+ /** Skip tables matching these patterns */
+ REPLICATE_WILD_IGNORE_TABLE(ValueType.PATTERNS),
+ /** Apply the changes of one database to another */
+ REPLICATE_REWRITE_DB(ValueType.DATABASE_PAIRS);
+
+ private final ValueType valueType;
+
+ Kind(ValueType valueType) {
+ this.valueType = valueType;
+ }
+
+ public ValueType getValueType() {
+ return valueType;
+ }
+ }
+
+ /** A pair of {@code REPLICATE_REWRITE_DB}: the source database and its replacement. */
+ public static class DatabaseRewrite implements Serializable {
+ private final String from;
+ private final String to;
+
+ public DatabaseRewrite(String from, String to) {
+ this.from = Objects.requireNonNull(from);
+ this.to = Objects.requireNonNull(to);
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public String getTo() {
+ return to;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + from + ", " + to + ")";
+ }
+ }
+
+ public ReplicationFilter(Kind kind) {
+ this.kind = Objects.requireNonNull(kind);
+ }
+
+ public Kind getKind() {
+ return kind;
+ }
+
+ /** The database names of REPLICATE_DO_DB and REPLICATE_IGNORE_DB. */
+ public List getDatabases() {
+ return databases;
+ }
+
+ /** The tables of REPLICATE_DO_TABLE and REPLICATE_IGNORE_TABLE. */
+ public List getTables() {
+ return tables;
+ }
+
+ /** The patterns of REPLICATE_WILD_DO_TABLE and REPLICATE_WILD_IGNORE_TABLE. */
+ public List getPatterns() {
+ return patterns;
+ }
+
+ /** The pairs of REPLICATE_REWRITE_DB. */
+ public List getRewrites() {
+ return rewrites;
+ }
+
+ /** The list that {@link Kind#getValueType()} selects. */
+ public List> getValues() {
+ switch (kind.getValueType()) {
+ case DATABASES:
+ return databases;
+ case TABLES:
+ return tables;
+ case PATTERNS:
+ return patterns;
+ default:
+ return rewrites;
+ }
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append(kind).append(" = (");
+ List> values = getValues();
+ for (int i = 0; i < values.size(); i++) {
+ builder.append(i == 0 ? "" : ", ").append(values.get(i));
+ }
+ return builder.append(')');
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/replication/ReplicationSourceOption.java b/src/main/java/net/sf/jsqlparser/statement/replication/ReplicationSourceOption.java
new file mode 100644
index 0000000000..d94cb451ff
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/replication/ReplicationSourceOption.java
@@ -0,0 +1,239 @@
+/*-
+ * #%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.replication;
+
+import java.math.BigInteger;
+import java.util.function.Consumer;
+import net.sf.jsqlparser.expression.DoubleValue;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.expression.NullValue;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.statement.create.user.UserAccount;
+
+/**
+ * An option of MySQL's {@code CHANGE REPLICATION SOURCE TO}. Its value is a string, a number,
+ * {@code NULL}, a keyword such as {@code OFF} (held as a {@link Column}) or, for
+ * {@code IGNORE_SERVER_IDS}, a parenthesized list of server IDs. {@code PRIVILEGE_CHECKS_USER}
+ * names an account, which {@link #getAccount()} returns, or {@code NULL}.
+ *
+ * @see MySQL
+ * CHANGE REPLICATION SOURCE TO
+ */
+public class ReplicationSourceOption extends ReplicationOption {
+ private final UserAccount account;
+
+ /** The values an option takes in the MySQL 8.4 grammar. */
+ public enum ValueType {
+ /** A string literal. */
+ STRING,
+ /** A string literal or {@code NULL}. */
+ STRING_OR_NULL,
+ /** An unsigned integer or decimal literal. */
+ NUMBER,
+ /** The integer literal 0 or 1. */
+ ZERO_OR_ONE,
+ /** A parenthesized, possibly empty list of numbers. */
+ NUMBER_LIST,
+ /** An account or {@code NULL}. */
+ ACCOUNT_OR_NULL,
+ /** {@code STREAM}, {@code ON}, {@code OFF} or {@code GENERATE}. */
+ PRIMARY_KEY_CHECK,
+ /** {@code OFF}, {@code LOCAL} or a UUID string. */
+ GTID_ASSIGNMENT,
+ /** No value at all. */
+ NONE;
+
+ /**
+ * Whether MySQL accepts {@code value} for an option of this type. An account is not an
+ * expression, so for {@link #ACCOUNT_OR_NULL} only {@code NULL} is checked here.
+ */
+ public boolean accepts(Expression value) {
+ switch (this) {
+ case STRING:
+ return value instanceof StringValue;
+ case STRING_OR_NULL:
+ return value instanceof StringValue || value instanceof NullValue;
+ case NUMBER:
+ return value instanceof LongValue || value instanceof DoubleValue;
+ case ZERO_OR_ONE:
+ return value instanceof LongValue && BigInteger.ONE
+ .compareTo(((LongValue) value).getBigIntegerValue()) >= 0;
+ case NUMBER_LIST:
+ return value instanceof ParenthesedExpressionList;
+ case ACCOUNT_OR_NULL:
+ return value instanceof NullValue;
+ case PRIMARY_KEY_CHECK:
+ return isKeyword(value, "STREAM", "ON", "OFF", "GENERATE");
+ case GTID_ASSIGNMENT:
+ return value instanceof StringValue || isKeyword(value, "OFF", "LOCAL");
+ default:
+ return value == null;
+ }
+ }
+
+ private static boolean isKeyword(Expression value, String... keywords) {
+ if (!(value instanceof Column)) {
+ return false;
+ }
+ String name = ((Column) value).getColumnName();
+ for (String keyword : keywords) {
+ if (keyword.equalsIgnoreCase(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /** The options of MySQL 8.4; the MASTER_ spellings were removed in 8.4. */
+ public enum Kind {
+ /** The network interface of the replica */
+ SOURCE_BIND(ValueType.STRING),
+ /** The host name or IP address of the source */
+ SOURCE_HOST(ValueType.STRING),
+ /** The replication user account */
+ SOURCE_USER(ValueType.STRING),
+ /** The password of the replication user */
+ SOURCE_PASSWORD(ValueType.STRING),
+ /** The TCP/IP port of the source */
+ SOURCE_PORT(ValueType.NUMBER),
+ /** The account that privilege checks of the applier use, or NULL */
+ PRIVILEGE_CHECKS_USER(ValueType.ACCOUNT_OR_NULL),
+ /** Whether the channel accepts only row-based events */
+ REQUIRE_ROW_FORMAT(ValueType.NUMBER),
+ /** The primary key check of the channel */
+ REQUIRE_TABLE_PRIMARY_KEY_CHECK(ValueType.PRIMARY_KEY_CHECK),
+ /** How anonymous transactions get a GTID */
+ ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS(ValueType.GTID_ASSIGNMENT),
+ /** The binary log file to read from */
+ SOURCE_LOG_FILE(ValueType.STRING),
+ /** The position to read from in the binary log file */
+ SOURCE_LOG_POS(ValueType.NUMBER),
+ /** Whether the replica finds its position by GTID */
+ SOURCE_AUTO_POSITION(ValueType.NUMBER),
+ /** The relay log file the applier starts from */
+ RELAY_LOG_FILE(ValueType.STRING),
+ /** The position the applier starts from in the relay log file */
+ RELAY_LOG_POS(ValueType.NUMBER),
+ /** The heartbeat interval in seconds */
+ SOURCE_HEARTBEAT_PERIOD(ValueType.NUMBER),
+ /** The seconds between reconnection attempts */
+ SOURCE_CONNECT_RETRY(ValueType.NUMBER),
+ /** The number of reconnection attempts */
+ SOURCE_RETRY_COUNT(ValueType.NUMBER),
+ /** Whether the channel fails over to another source */
+ SOURCE_CONNECTION_AUTO_FAILOVER(ValueType.ZERO_OR_ONE),
+ /** The seconds the replica lags behind the source */
+ SOURCE_DELAY(ValueType.NUMBER),
+ /** The permitted compression algorithms */
+ SOURCE_COMPRESSION_ALGORITHMS(ValueType.STRING),
+ /** The zstd compression level */
+ SOURCE_ZSTD_COMPRESSION_LEVEL(ValueType.NUMBER),
+ /** Whether the connection uses SSL */
+ SOURCE_SSL(ValueType.NUMBER),
+ /** The certificate authority file */
+ SOURCE_SSL_CA(ValueType.STRING),
+ /** The directory of certificate authority files */
+ SOURCE_SSL_CAPATH(ValueType.STRING),
+ /** The certificate file */
+ SOURCE_SSL_CERT(ValueType.STRING),
+ /** The certificate revocation list file */
+ SOURCE_SSL_CRL(ValueType.STRING),
+ /** The directory of certificate revocation list files */
+ SOURCE_SSL_CRLPATH(ValueType.STRING),
+ /** The private key file */
+ SOURCE_SSL_KEY(ValueType.STRING),
+ /** The permitted ciphers */
+ SOURCE_SSL_CIPHER(ValueType.STRING),
+ /** Whether the replica verifies the certificate of the source */
+ SOURCE_SSL_VERIFY_SERVER_CERT(ValueType.NUMBER),
+ /** The permitted TLS protocols */
+ SOURCE_TLS_VERSION(ValueType.STRING),
+ /** The permitted TLSv1.3 cipher suites, or NULL */
+ SOURCE_TLS_CIPHERSUITES(ValueType.STRING_OR_NULL),
+ /** The file of the RSA public key of the source */
+ SOURCE_PUBLIC_KEY_PATH(ValueType.STRING),
+ /** Whether the replica requests the RSA public key of the source */
+ GET_SOURCE_PUBLIC_KEY(ValueType.NUMBER),
+ /** The network namespace of the connection */
+ NETWORK_NAMESPACE(ValueType.STRING),
+ /** The server IDs whose events the replica ignores */
+ IGNORE_SERVER_IDS(ValueType.NUMBER_LIST),
+ /** Whether the channel keeps no file names and positions */
+ GTID_ONLY(ValueType.ZERO_OR_ONE);
+
+ private final ValueType valueType;
+
+ Kind(ValueType valueType) {
+ this.valueType = valueType;
+ }
+
+ public ValueType getValueType() {
+ return valueType;
+ }
+ }
+
+ /** The values of {@code REQUIRE_TABLE_PRIMARY_KEY_CHECK}. */
+ public enum PrimaryKeyCheck {
+ STREAM, ON, OFF, GENERATE
+ }
+
+ /** The values of {@code ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS}; UUID is a quoted UUID. */
+ public enum GtidAssignment {
+ OFF, LOCAL, UUID
+ }
+
+ public ReplicationSourceOption(String name, Kind kind, Expression value) {
+ super(name, kind, value);
+ this.account = null;
+ }
+
+ /** {@code PRIVILEGE_CHECKS_USER = account}. */
+ public ReplicationSourceOption(String name, UserAccount account) {
+ super(name, Kind.PRIVILEGE_CHECKS_USER, null);
+ this.account = account;
+ }
+
+ /** The account of {@code PRIVILEGE_CHECKS_USER}, null for {@code NULL} and other options. */
+ public UserAccount getAccount() {
+ return account;
+ }
+
+ public PrimaryKeyCheck getPrimaryKeyCheck() {
+ return getKind() == Kind.REQUIRE_TABLE_PRIMARY_KEY_CHECK
+ ? enumValue(PrimaryKeyCheck.class)
+ : null;
+ }
+
+ public GtidAssignment getGtidAssignment() {
+ if (getKind() != Kind.ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS) {
+ return null;
+ }
+ return getValue() instanceof StringValue ? GtidAssignment.UUID
+ : enumValue(GtidAssignment.class);
+ }
+
+ @Override
+ public String getValueText() {
+ return account != null ? account.toString() : super.getValueText();
+ }
+
+ @Override
+ public void appendTo(StringBuilder sql, Consumer expressions) {
+ if (account == null) {
+ super.appendTo(sql, expressions);
+ } else {
+ sql.append(getName()).append(" = ").append(account);
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/replication/ResetReplicationStatement.java b/src/main/java/net/sf/jsqlparser/statement/replication/ResetReplicationStatement.java
new file mode 100644
index 0000000000..4a4d316ba9
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/replication/ResetReplicationStatement.java
@@ -0,0 +1,142 @@
+/*-
+ * #%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.replication;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+
+/**
+ * MySQL {@code RESET reset_option [, reset_option] ...} with the replication options
+ * {@code BINARY LOGS AND GTIDS [TO n]} and {@code REPLICA [ALL] [FOR CHANNEL 'channel']}. The
+ * PostgreSQL {@code RESET} of a configuration parameter remains a
+ * {@link net.sf.jsqlparser.statement.ResetStatement}.
+ * Example:
+ * RESET REPLICA ALL FOR CHANNEL 'c1', BINARY LOGS AND GTIDS TO 1234
+ *
+ * @see MySQL RESET
+ * @see MySQL
+ * RESET BINARY LOGS AND GTIDS
+ * @see MySQL RESET REPLICA
+ */
+public class ResetReplicationStatement extends ASTNodeAccessImpl implements Statement {
+ private List