Skip to content

feat: parse the MySQL replication statements - #2906

Open
minleejae wants to merge 1 commit into
JSQLParser:masterfrom
minleejae:feat/mysql-replication-20261107
Open

minleejae wants to merge 1 commit into
JSQLParser:masterfrom
minleejae:feat/mysql-replication-20261107

Conversation

@minleejae

Copy link
Copy Markdown
Contributor

Purpose

The MySQL statements that set up and run replication failed to parse, so a replica setup script
failed at its first statement:

CHANGE REPLICATION SOURCE TO SOURCE_HOST = 'h', SOURCE_PORT = 3306, SOURCE_AUTO_POSITION = 1
    FOR CHANNEL 'c1';
CHANGE REPLICATION FILTER REPLICATE_DO_DB = (d1, d2), REPLICATE_REWRITE_DB = ((a, b));
START REPLICA SQL_THREAD UNTIL SQL_BEFORE_GTIDS = '3E11FA47-71CA-11E1-9E33-C80AA9429562:11-56';
STOP REPLICA IO_THREAD;
RESET REPLICA ALL FOR CHANNEL 'c1';
RESET BINARY LOGS AND GTIDS TO 1234;
PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;

CHANGE and STOP were not statement starts, START only began START TRANSACTION,
RESET only took a parameter name (RESET REPLICA ALL failed at ALL), and PURGE only knew
Oracle's objects.

Current design (As-is)

  • ResetStatement is the PostgreSQL RESET name | ALL | TIME ZONE. RESET REPLICA happens to
    parse as the reset of a parameter named REPLICA.
  • PurgeStatement models Oracle's PURGE TABLE | INDEX | RECYCLEBIN | DBA_RECYCLEBIN | TABLESPACE, keyed by PurgeObjectType.
  • ReplicationOption<K> is the base of the PostgreSQL publication and subscription options: a
    spelled name, a typed kind and an Expression value.
  • There is no model for the replication source options, the replication filters, the replica
    threads or FOR CHANNEL.

Target design (To-be)

  • CHANGE REPLICATION SOURCE TO. ChangeReplicationSourceStatement holds
    ReplicationSourceOptions, which extend ReplicationOption<Kind>. Kind lists the 37
    options of MySQL 8.4, and Kind.getValueType() says what each takes, following
    sql_yacc.yy:

    • a string;
    • a string or NULL;
    • an unsigned number;
    • 0 or 1 (GTID_ONLY and SOURCE_CONNECTION_AUTO_FAILOVER, where MySQL reports any other
      value as a syntax error);
    • the possibly empty server ID list of IGNORE_SERVER_IDS = (1, 2);
    • an account or NULL;
    • STREAM | ON | OFF | GENERATE;
    • OFF | LOCAL | 'uuid'.

    A value of another type is a parse error, as it is in MySQL. Keyword values are kept as a
    Column, like ON in the PostgreSQL options. PRIVILEGE_CHECKS_USER keeps its account in
    getAccount() (the UserAccount of CREATE USER). getPrimaryKeyCheck() and
    getGtidAssignment() read the keyword options as enums.

  • CHANGE REPLICATION FILTER. ChangeReplicationFilterStatement holds ReplicationFilter
    rules. Each rule's list may be empty and holds what its kind takes:

    • database names (REPLICATE_DO_DB, REPLICATE_IGNORE_DB);
    • db.table names as Table (REPLICATE_DO_TABLE, REPLICATE_IGNORE_TABLE);
    • quoted patterns (REPLICATE_WILD_DO_TABLE, REPLICATE_WILD_IGNORE_TABLE);
    • (from, to) pairs (REPLICATE_REWRITE_DB = ((a, b), (c, d))).
  • START / STOP REPLICA. StartReplicaStatement and StopReplicaStatement keep their threads
    (IO_THREAD, SQL_THREAD). START REPLICA also keeps:

    • its UNTIL conditions as ReplicaUntilOptions. The first may be any condition, and later
      ones only log coordinates, as in the server grammar;
    • the USER, PASSWORD, DEFAULT_AUTH and PLUGIN_DIR strings, in the server's order.
  • RESET. ResetReplicationStatement holds the comma list of MySQL's RESET statement:
    BINARY LOGS AND GTIDS [TO n] and REPLICA [ALL] [FOR CHANNEL 'c'], as in
    RESET REPLICA, BINARY LOGS AND GTIDS. ResetStatement is not changed.

  • PURGE. PurgeStatement gains the additive PurgeObjectType.BINARY_LOGS, a LogBound
    (TO, BEFORE) and getLogExpression(), which is the log name literal or the BEFORE
    expression. getObject() returns the same expression. MySQL parses BEFORE with subqueries
    disallowed, so a subquery anywhere in it is a parse error:
    PURGE BINARY LOGS BEFORE NOW() + (SELECT 1) is rejected, while BEFORE (NOW()) and
    BEFORE @cutoff are accepted. The Oracle forms are unchanged.

  • FOR CHANNEL. One ReplicationChannel() production serves every statement. Following
    opt_channel, the channel is a string, kept as a StringValue in getChannel().

  • Dialects.

    • Dialect.MYSQL and the parser without a dialect read these forms.
    • Without a dialect, a bare RESET REPLICA stays the ResetStatement it is today. ALL,
      FOR CHANNEL or a further reset option after it makes it MySQL's.
    • Dialect.MARIADB reads PURGE BINARY LOGS and MariaDB's PURGE MASTER LOGS
      (PurgeObjectType.MASTER_LOGS). MariaDB writes the other replication statements
      differently (CHANGE MASTER, connection names, MASTER_ options), so they keep today's
      readings there.
    • MySQL 8.4 removed CHANGE MASTER TO, START SLAVE, RESET MASTER and the MASTER_
      options, so they stay unsupported.
    • PostgreSQL, Oracle and MariaDB keep their readings of RESET, PURGE and START.
    • STOP becomes a non-reserved keyword and stays usable as a name. REPLICA, REPLICATION,
      SOURCE, CHANNEL and LOGS remain identifiers.
    • Oracle's PURGE TABLESPACE name [USER name] read both names as plain identifiers. It now
      reads them as object names, so PURGE TABLESPACE stop keeps parsing; quoted names are
      accepted as well.
  • Wiring. The following all handle the new statements:

    • statement visitors and StatementDeParser, through each model's appendTo, with values
      passing through the expression deparser;
    • TablesNamesFinder, which reports the tables of REPLICATE_DO_TABLE and
      REPLICATE_IGNORE_TABLE;
    • feature analysis, which reports MODIFIES_SESSION, as for ALTER SYSTEM;
    • validation, through the new features changeReplicationSource, changeReplicationFilter,
      startReplica, stopReplica, resetReplication and purgeBinaryLogs. The MySQL
      capabilities enable all six, and MariaDB enables purgeBinaryLogs.

References:
CHANGE REPLICATION SOURCE TO,
CHANGE REPLICATION FILTER,
START REPLICA,
STOP REPLICA,
RESET,
RESET BINARY LOGS AND GTIDS,
RESET REPLICA,
PURGE BINARY LOGS,
MariaDB PURGE BINARY LOGS.

Not included, all spellings the server accepts that are rarely written:

  • the undocumented RELAY_THREAD synonym of IO_THREAD;
  • hexadecimal numbers (0x…) as option values;
  • double-quoted strings, which the server reads as strings outside ANSI_QUOTES mode;
  • the leading comma IGNORE_SERVER_IDS = (, 1) that the server's grammar lets through.

Validation

  • 129 new tests: 126 in MySqlReplicationStatementsTest and 3 in
    ReplicationStatementValidatorTest. They cover each form with and without the MySQL dialect,
    toString, StatementDeParser, reparsing, the AST fields, 60 rejected forms (among them
    subqueries in PURGE BINARY LOGS BEFORE), the readings
    kept in PostgreSQL, Oracle and MariaDB, statement boundaries and unsupported-statement
    scripts, STOP as a name, TablesNamesFinder, feature analysis, node ranges and building in
    code. ASTNodeCoverageTest checks seven new statements. The new tests fail on master, where
    every new form is a parse error.

  • spotlessApply cleanTest check jar: 13,067 tests, 25 skipped, 0 failures. Grammar ambiguity
    check, Checkstyle, PMD, SpotBugs and JaCoCo pass.

  • Compared with master (a30a67920) on the DDL/DCL survey corpora: 0 regressions.

  • Compared with master (a30a67920) on the non-DDL MySQL corpus:

    • with the MySQL dialect, 7 replication cases fixed, 0 regressed, 0 changed;
    • without a dialect, the same 7 fixed, 0 regressed, 0 changed.
  • Compared with master (a30a67920) on the PostgreSQL corpora: no changes.

  • MySQL 8.4.11 accepts all 55 statements the tests accept for the MySQL dialect, 53 of which master rejects. Some fail only when run, for example Target log not found in binlog, Multiple channels exist on the replica or Replica channel 'c1' does not exist. This includes the comma lists of RESET, every CHANGE REPLICATION SOURCE TO option with its value type, empty filter lists and the START REPLICA connection options. It reports a syntax error (1064) for all 60 statements the tests reject, including GTID_ONLY = 2, SOURCE_HOST = NULL, an unquoted channel name, a subquery in PURGE BINARY LOGS BEFORE, and START SLAVE / CHANGE MASTER TO, which MySQL 8.4 removed.

  • PostgreSQL 18.6 still reads RESET REPLICA as resetting a configuration parameter (it fails only with unrecognized configuration parameter "replica"), and reports a syntax error for the MySQL replication statements.

AI assistance: prepared with Claude Code.

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>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant