This is an automated email from the ASF dual-hosted git repository.

corgy-w pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 9d6b879051 [Improve][MySQL-CDC] Enhance diagnostics for missing binlog 
and GTID during restore (#10566)
9d6b879051 is described below

commit 9d6b87905173e38544f94433b76e475866c7e6ab
Author: Jast <[email protected]>
AuthorDate: Fri May 8 10:34:10 2026 +0800

    [Improve][MySQL-CDC] Enhance diagnostics for missing binlog and GTID during 
restore (#10566)
---
 .../reader/fetch/MySqlSourceFetchTaskContext.java  | 313 ++++++++++++++++++++-
 .../MysqlCDCWithMissingBinlogDiagnosticsIT.java    | 253 +++++++++++++++++
 2 files changed, 563 insertions(+), 3 deletions(-)

diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/reader/fetch/MySqlSourceFetchTaskContext.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/reader/fetch/MySqlSourceFetchTaskContext.java
index 0fa0a86065..9e6b03db58 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/reader/fetch/MySqlSourceFetchTaskContext.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/reader/fetch/MySqlSourceFetchTaskContext.java
@@ -75,9 +75,12 @@ import lombok.extern.slf4j.Slf4j;
 import java.io.IOException;
 import java.sql.SQLException;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import static 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.offset.BinlogOffset.BINLOG_FILENAME_OFFSET_KEY;
 import static 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.utils.MySqlConnectionUtils.createBinaryClient;
@@ -89,6 +92,8 @@ import static 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.utils.MySqlCon
 public class MySqlSourceFetchTaskContext extends JdbcSourceFetchTaskContext {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(MySqlSourceFetchTaskContext.class);
+    private static final int BINLOG_FILE_SAMPLE_SIZE = 10;
+    private static final Pattern BINLOG_NUMBER_PATTERN = 
Pattern.compile("^(.*?)(\\d+)$");
 
     private final MySqlConnection connection;
     private final BinaryLogClient binaryLogClient;
@@ -333,10 +338,11 @@ public class MySqlSourceFetchTaskContext extends 
JdbcSourceFetchTaskContext {
         // And compare with the one we're supposed to use ...
         boolean found = logNames.stream().anyMatch(binlogFilename::equals);
         if (!found) {
-            LOG.info(
-                    "Connector requires binlog file '{}', but MySQL only has 
{}",
+            LOG.warn(
+                    "Connector requires binlog file '{}', but it is not 
available on the connected MySQL server. {}",
                     binlogFilename,
-                    String.join(", ", logNames));
+                    summarizeBinlogFiles(logNames));
+            logBinlogNotAvailableDiagnostics(binlogFilename, logNames, offset);
         } else {
             LOG.info("MySQL has the binlog file '{}' required by the 
connector", binlogFilename);
         }
@@ -355,6 +361,7 @@ public class MySqlSourceFetchTaskContext extends 
JdbcSourceFetchTaskContext {
             // Last offsets had GTIDs but the server does not use them ...
             LOG.warn(
                     "Connector used GTIDs previously, but MySQL does not know 
of any GTIDs or they are not enabled");
+            logGtidNotAvailableDiagnostics(offset);
             return false;
         }
 
@@ -390,14 +397,314 @@ public class MySqlSourceFetchTaskContext extends 
JdbcSourceFetchTaskContext {
                     nonPurgedGtidSetToReplicate);
             if (!gtidSetToReplicate.equals(nonPurgedGtidSetToReplicate)) {
                 LOG.warn("Some of the GTIDs needed to replicate have been 
already purged");
+                logGtidNotAvailableDiagnostics(offset);
                 return false;
             }
             return true;
         }
         LOG.info("Connector last known GTIDs are {}, but MySQL has {}", 
gtidSet, availableGtidSet);
+        logGtidNotAvailableDiagnostics(offset);
         return false;
     }
 
+    private void logBinlogNotAvailableDiagnostics(
+            String requiredBinlogFilename,
+            List<String> availableBinlogFiles,
+            MySqlOffsetContext offset) {
+        LOG.warn(
+                "MySQL-CDC diagnostic: requested starting offset 
sourceInfo={}",
+                offset.getSourceInfo());
+        LOG.warn(
+                "MySQL-CDC diagnostic: debezium connection target host={}, 
port={}",
+                getDbzConnectorConfig().hostname(),
+                getDbzConnectorConfig().port());
+
+        logMySqlServerIdentity();
+        logMySqlBinlogRetentionVariables();
+        logMySqlMasterStatus();
+        logMySqlReplicationStatus();
+        logBinlogRangeAnalysis(requiredBinlogFilename, availableBinlogFiles);
+    }
+
+    private void logGtidNotAvailableDiagnostics(MySqlOffsetContext offset) {
+        LOG.warn(
+                "MySQL-CDC diagnostic: GTID set is not available on server for 
restored offset. restored_gtid_set={}, sourceInfo={}",
+                offset.gtidSet(),
+                offset.getSourceInfo());
+        LOG.warn(
+                "MySQL-CDC diagnostic: debezium connection target host={}, 
port={}",
+                getDbzConnectorConfig().hostname(),
+                getDbzConnectorConfig().port());
+        logMySqlServerIdentity();
+        logMySqlBinlogRetentionVariables();
+        logMySqlMasterStatus();
+        logMySqlReplicationStatus();
+    }
+
+    private void logMySqlServerIdentity() {
+        final String sql =
+                "SELECT @@hostname AS hostname, @@port AS port, @@server_id AS 
server_id, "
+                        + "@@server_uuid AS server_uuid, @@read_only AS 
read_only, "
+                        + "@@super_read_only AS super_read_only, @@version AS 
version, "
+                        + "@@gtid_mode AS gtid_mode";
+        try {
+            connection.query(
+                    sql,
+                    rs -> {
+                        if (!rs.next()) {
+                            return;
+                        }
+                        LOG.warn(
+                                "MySQL-CDC diagnostic: connected to MySQL 
hostname={}, port={}, server_id={}, server_uuid={}, read_only={}, 
super_read_only={}, version={}, gtid_mode={}",
+                                rs.getString("hostname"),
+                                rs.getString("port"),
+                                rs.getString("server_id"),
+                                rs.getString("server_uuid"),
+                                rs.getString("read_only"),
+                                rs.getString("super_read_only"),
+                                rs.getString("version"),
+                                rs.getString("gtid_mode"));
+                    });
+        } catch (SQLException e) {
+            LOG.warn(
+                    "MySQL-CDC diagnostic: failed to query MySQL server 
identity: {}",
+                    e.getMessage());
+        }
+    }
+
+    private void logMySqlBinlogRetentionVariables() {
+        logMySqlVariable("log_bin");
+        logMySqlVariable("binlog_format");
+        logMySqlVariable("binlog_expire_logs_seconds");
+        logMySqlVariable("expire_logs_days");
+    }
+
+    private void logMySqlVariable(String variableName) {
+        final String sql = String.format("SHOW VARIABLES LIKE '%s'", 
variableName);
+        try {
+            connection.query(
+                    sql,
+                    rs -> {
+                        if (!rs.next()) {
+                            LOG.warn("MySQL-CDC diagnostic: variable {} not 
found", variableName);
+                            return;
+                        }
+                        LOG.warn(
+                                "MySQL-CDC diagnostic: variable {}={}",
+                                rs.getString(1),
+                                rs.getString(2));
+                    });
+        } catch (SQLException e) {
+            LOG.warn(
+                    "MySQL-CDC diagnostic: failed to query variable {}: {}",
+                    variableName,
+                    e.getMessage());
+        }
+    }
+
+    private void logMySqlMasterStatus() {
+        try {
+            connection.query(
+                    "SHOW MASTER STATUS",
+                    rs -> {
+                        if (!rs.next()) {
+                            LOG.warn(
+                                    "MySQL-CDC diagnostic: SHOW MASTER STATUS 
returned empty result");
+                            return;
+                        }
+                        String file = safeGetString(rs, "File");
+                        String position = safeGetString(rs, "Position");
+                        String gtidSet = safeGetString(rs, 
"Executed_Gtid_Set");
+                        LOG.warn(
+                                "MySQL-CDC diagnostic: master status file={}, 
position={}, executed_gtid_set={}",
+                                file,
+                                position,
+                                gtidSet);
+                    });
+        } catch (SQLException e) {
+            LOG.warn(
+                    "MySQL-CDC diagnostic: failed to query SHOW MASTER STATUS: 
{}", e.getMessage());
+        }
+    }
+
+    private void logMySqlReplicationStatus() {
+        if (logReplicationStatus("SHOW REPLICA STATUS", "replica")) {
+            return;
+        }
+        logReplicationStatus("SHOW SLAVE STATUS", "slave");
+    }
+
+    private boolean logReplicationStatus(String sql, String label) {
+        try {
+            final List<String> row = new ArrayList<>(8);
+            connection.query(
+                    sql,
+                    rs -> {
+                        if (!rs.next()) {
+                            return;
+                        }
+                        String sourceHost =
+                                firstNonNull(
+                                        safeGetString(rs, "Source_Host"),
+                                        safeGetString(rs, "Master_Host"));
+                        String ioRunning =
+                                firstNonNull(
+                                        safeGetString(rs, 
"Replica_IO_Running"),
+                                        safeGetString(rs, "Slave_IO_Running"));
+                        String sqlRunning =
+                                firstNonNull(
+                                        safeGetString(rs, 
"Replica_SQL_Running"),
+                                        safeGetString(rs, 
"Slave_SQL_Running"));
+                        String relayMasterLogFile =
+                                firstNonNull(
+                                        safeGetString(rs, 
"Relay_Master_Log_File"),
+                                        safeGetString(rs, "Master_Log_File"));
+                        String execMasterLogPos =
+                                firstNonNull(
+                                        safeGetString(rs, 
"Exec_Master_Log_Pos"),
+                                        safeGetString(rs, 
"Read_Master_Log_Pos"));
+                        String retrievedGtidSet = safeGetString(rs, 
"Retrieved_Gtid_Set");
+                        String executedGtidSet = safeGetString(rs, 
"Executed_Gtid_Set");
+
+                        row.add("source_host=" + sourceHost);
+                        row.add("io_running=" + ioRunning);
+                        row.add("sql_running=" + sqlRunning);
+                        row.add("relay_master_log_file=" + relayMasterLogFile);
+                        row.add("exec_master_log_pos=" + execMasterLogPos);
+                        if (retrievedGtidSet != null) {
+                            row.add("retrieved_gtid_set=" + retrievedGtidSet);
+                        }
+                        if (executedGtidSet != null) {
+                            row.add("executed_gtid_set=" + executedGtidSet);
+                        }
+                    });
+            if (row.isEmpty()) {
+                LOG.warn("MySQL-CDC diagnostic: {} status empty/unsupported", 
label);
+                return true;
+            }
+            LOG.warn("MySQL-CDC diagnostic: {} status {}", label, 
String.join(", ", row));
+            return true;
+        } catch (SQLException e) {
+            LOG.warn("MySQL-CDC diagnostic: {} failed: {}", sql, 
e.getMessage());
+            return false;
+        }
+    }
+
+    private void logBinlogRangeAnalysis(
+            String requiredBinlogFilename, List<String> availableBinlogFiles) {
+        BinlogFileNumber required = 
parseBinlogFileNumber(requiredBinlogFilename);
+        if (required == null) {
+            return;
+        }
+
+        long min = Long.MAX_VALUE;
+        long max = Long.MIN_VALUE;
+        boolean any = false;
+        for (String file : availableBinlogFiles) {
+            BinlogFileNumber parsed = parseBinlogFileNumber(file);
+            if (parsed == null || !required.prefix.equals(parsed.prefix)) {
+                continue;
+            }
+            any = true;
+            min = Math.min(min, parsed.number);
+            max = Math.max(max, parsed.number);
+        }
+        if (!any) {
+            LOG.warn(
+                    "MySQL-CDC diagnostic: cannot compare binlog sequence 
because available binlog filenames don't match required prefix '{}'",
+                    required.prefix);
+            return;
+        }
+
+        if (required.number < min) {
+            LOG.warn(
+                    "MySQL-CDC diagnostic: required binlog sequence {}{} is 
older than the earliest available {}{}; likely purged/expired binlog on server",
+                    required.prefix,
+                    required.number,
+                    required.prefix,
+                    min);
+        } else if (required.number > max) {
+            LOG.warn(
+                    "MySQL-CDC diagnostic: required binlog sequence {}{} is 
newer than the latest available {}{}; possible connection to an out-of-date 
replica, proxy routing to different instance, or RESET MASTER",
+                    required.prefix,
+                    required.number,
+                    required.prefix,
+                    max);
+        } else {
+            LOG.warn(
+                    "MySQL-CDC diagnostic: required binlog sequence {}{} is 
within available range {}{}..{}{} but missing; possible manual binlog deletion, 
binlog.index corruption, or connecting to a different MySQL instance 
intermittently",
+                    required.prefix,
+                    required.number,
+                    required.prefix,
+                    min,
+                    required.prefix,
+                    max);
+        }
+    }
+
+    private static String summarizeBinlogFiles(List<String> files) {
+        if (files == null || files.isEmpty()) {
+            return "available binlog files: (empty)";
+        }
+        int total = files.size();
+        int headCount = Math.min(BINLOG_FILE_SAMPLE_SIZE, total);
+        int tailCount = Math.min(BINLOG_FILE_SAMPLE_SIZE, Math.max(0, total - 
headCount));
+
+        List<String> head = files.subList(0, headCount);
+        List<String> tail =
+                tailCount > 0
+                        ? files.subList(total - tailCount, total)
+                        : java.util.Collections.emptyList();
+
+        if (tail.isEmpty()) {
+            return String.format(
+                    "available binlog files: count=%d, sample=%s", total, 
String.join(", ", head));
+        }
+        return String.format(
+                "available binlog files: count=%d, head=[%s], tail=[%s]",
+                total, String.join(", ", head), String.join(", ", tail));
+    }
+
+    private static BinlogFileNumber parseBinlogFileNumber(String filename) {
+        if (filename == null) {
+            return null;
+        }
+        Matcher matcher = BINLOG_NUMBER_PATTERN.matcher(filename);
+        if (!matcher.matches()) {
+            return null;
+        }
+        String prefix = matcher.group(1);
+        String numberStr = matcher.group(2);
+        try {
+            long number = Long.parseLong(numberStr);
+            return new BinlogFileNumber(prefix, number);
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    private static String safeGetString(java.sql.ResultSet rs, String 
columnLabel) {
+        try {
+            return rs.getString(columnLabel);
+        } catch (SQLException e) {
+            return null;
+        }
+    }
+
+    private static String firstNonNull(String a, String b) {
+        return a != null ? a : b;
+    }
+
+    private static class BinlogFileNumber {
+        private final String prefix;
+        private final long number;
+
+        private BinlogFileNumber(String prefix, long number) {
+            this.prefix = prefix;
+            this.number = number;
+        }
+    }
+
     private void validateAndLoadDatabaseHistory(
             MySqlOffsetContext offset, MySqlDatabaseSchema schema) {
         schema.initializeStorage();
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/MysqlCDCWithMissingBinlogDiagnosticsIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/MysqlCDCWithMissingBinlogDiagnosticsIT.java
new file mode 100644
index 0000000000..7f0929b133
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-mysql-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/MysqlCDCWithMissingBinlogDiagnosticsIT.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.connectors.seatunnel.cdc.mysql;
+
+import 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.testutils.MySqlContainer;
+import 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.testutils.MySqlVersion;
+import 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.testutils.UniqueDatabase;
+import org.apache.seatunnel.e2e.common.TestResource;
+import org.apache.seatunnel.e2e.common.TestSuiteBase;
+import org.apache.seatunnel.e2e.common.container.ContainerExtendedFactory;
+import org.apache.seatunnel.e2e.common.container.EngineType;
+import org.apache.seatunnel.e2e.common.container.TestContainer;
+import org.apache.seatunnel.e2e.common.junit.DisabledOnContainer;
+import org.apache.seatunnel.e2e.common.junit.TestContainerExtension;
+import org.apache.seatunnel.e2e.common.util.JobIdGenerator;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.TestTemplate;
+import org.testcontainers.containers.Container;
+import org.testcontainers.containers.output.Slf4jLogConsumer;
+import org.testcontainers.lifecycle.Startables;
+import org.testcontainers.utility.DockerLoggerFactory;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import static org.awaitility.Awaitility.await;
+
+@Slf4j
+@DisabledOnContainer(
+        value = {},
+        type = {EngineType.SPARK, EngineType.FLINK},
+        disabledReason = "Currently SPARK and FLINK do not support restore")
+public class MysqlCDCWithMissingBinlogDiagnosticsIT extends TestSuiteBase 
implements TestResource {
+    private static final String MYSQL_HOST = "mysql_cdc_e2e";
+    private static final String MYSQL_USER_NAME = "mysqluser";
+    private static final String MYSQL_USER_PASSWORD = "mysqlpw";
+    private static final String MYSQL_DATABASE = "mysql_cdc";
+    private static final String SOURCE_TABLE = "mysql_cdc_e2e_source_table";
+    private static final String SINK_TABLE = "mysql_cdc_e2e_sink_table";
+
+    private static final MySqlContainer MYSQL_CONTAINER = 
createMySqlContainer(MySqlVersion.V8_0);
+
+    private final UniqueDatabase inventoryDatabase =
+            new UniqueDatabase(
+                    MYSQL_CONTAINER, MYSQL_DATABASE, "mysqluser", "mysqlpw", 
MYSQL_DATABASE);
+
+    private static MySqlContainer createMySqlContainer(MySqlVersion version) {
+        return new MySqlContainer(version)
+                .withConfigurationOverride("docker/server-gtids/my.cnf")
+                .withSetupSQL("docker/setup.sql")
+                .withNetwork(NETWORK)
+                .withNetworkAliases(MYSQL_HOST)
+                .withDatabaseName(MYSQL_DATABASE)
+                .withUsername(MYSQL_USER_NAME)
+                .withPassword(MYSQL_USER_PASSWORD)
+                .withLogConsumer(
+                        new 
Slf4jLogConsumer(DockerLoggerFactory.getLogger("mysql-docker-image")));
+    }
+
+    private String driverUrl() {
+        return 
"https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.32/mysql-connector-j-8.0.32.jar";;
+    }
+
+    @TestContainerExtension
+    protected final ContainerExtendedFactory extendedFactory =
+            container -> {
+                Container.ExecResult extraCommands =
+                        container.execInContainer(
+                                "bash",
+                                "-c",
+                                "mkdir -p /tmp/seatunnel/plugins/MySQL-CDC/lib 
&& cd /tmp/seatunnel/plugins/MySQL-CDC/lib && wget "
+                                        + driverUrl());
+                Assertions.assertEquals(0, extraCommands.getExitCode(), 
extraCommands.getStderr());
+            };
+
+    @BeforeAll
+    @Override
+    public void startUp() {
+        log.info("Starting Mysql containers...");
+        Startables.deepStart(Stream.of(MYSQL_CONTAINER)).join();
+        log.info("Mysql Containers are started");
+        inventoryDatabase.createAndInitialize();
+        log.info("Mysql ddl execution is complete");
+    }
+
+    @Override
+    @AfterAll
+    public void tearDown() {
+        if (MYSQL_CONTAINER != null) {
+            MYSQL_CONTAINER.close();
+        }
+    }
+
+    @TestTemplate
+    public void 
testRestoreFailsAndLogsDiagnosticsWhenBinlogMissing(TestContainer container)
+            throws InterruptedException, IOException {
+        clearTable(MYSQL_DATABASE, SINK_TABLE);
+
+        Long jobId = JobIdGenerator.newJobId();
+        CompletableFuture.runAsync(
+                () -> {
+                    try {
+                        container.executeJob(
+                                "/mysqlcdc_to_mysql_with_binlog_delete.conf",
+                                String.valueOf(jobId));
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+
+        await().atMost(60, TimeUnit.SECONDS)
+                .untilAsserted(
+                        () -> {
+                            List<List<Object>> source =
+                                    query(
+                                            "select count(1) from "
+                                                    + MYSQL_DATABASE
+                                                    + "."
+                                                    + SOURCE_TABLE);
+                            List<List<Object>> sink =
+                                    query(
+                                            "select count(1) from "
+                                                    + MYSQL_DATABASE
+                                                    + "."
+                                                    + SINK_TABLE);
+                            Assertions.assertEquals(source, sink);
+                        });
+
+        // Ensure checkpoint offset is in a newer binlog file
+        executeSql("flush binary logs");
+        executeSql(
+                "INSERT INTO "
+                        + MYSQL_DATABASE
+                        + "."
+                        + SOURCE_TABLE
+                        + " (id, f_int, f_varchar) VALUES (200, 200, 
'binlog_rotate')");
+
+        await().atMost(60, TimeUnit.SECONDS)
+                .untilAsserted(
+                        () ->
+                                Assertions.assertEquals(
+                                        query(
+                                                "select count(1) from "
+                                                        + MYSQL_DATABASE
+                                                        + "."
+                                                        + SOURCE_TABLE),
+                                        query(
+                                                "select count(1) from "
+                                                        + MYSQL_DATABASE
+                                                        + "."
+                                                        + SINK_TABLE)));
+
+        Assertions.assertEquals(0, 
container.savepointJob(String.valueOf(jobId)).getExitCode());
+
+        // Rotate again, then purge all previous binlogs (including the one 
referenced by savepoint)
+        executeSql("flush binary logs");
+        String purgeToBinlog = query("show master 
status").get(0).get(0).toString();
+        executeSql("purge binary logs to '" + purgeToBinlog + "'");
+
+        // Restore should fail due to missing binlog, but must output 
diagnostics for
+        // troubleshooting
+        Container.ExecResult restoreResult =
+                container.restoreJob(
+                        "/mysqlcdc_to_mysql_with_binlog_delete.conf", 
String.valueOf(jobId));
+        Assertions.assertNotEquals(
+                0,
+                restoreResult.getExitCode(),
+                "Expected restore to fail due to missing binlog, but exit code 
is 0");
+
+        String logs =
+                restoreResult.getStdout()
+                        + "\n"
+                        + restoreResult.getStderr()
+                        + "\n"
+                        + container.getServerLogs();
+        Assertions.assertTrue(
+                logs.contains("MySQL-CDC diagnostic: connected to MySQL"),
+                "Expected diagnostic logs to include MySQL server identity, 
but not found in server logs");
+        Assertions.assertTrue(
+                logs.contains("Connector requires binlog file")
+                        || logs.contains("MySQL-CDC diagnostic: GTID set is 
not available")
+                        || logs.contains(
+                                "Some of the GTIDs needed to replicate have 
been already purged"),
+                "Expected diagnostic logs to include missing binlog or GTID 
diagnostics, but not found in logs");
+    }
+
+    private void clearTable(String database, String tableName) {
+        executeSql("truncate table " + database + "." + tableName);
+    }
+
+    private void executeSql(String sql) {
+        try (Connection connection = getJdbcConnection()) {
+            connection.createStatement().execute(sql);
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Connection getJdbcConnection() throws SQLException {
+        return DriverManager.getConnection(
+                MYSQL_CONTAINER.getJdbcUrl(),
+                MYSQL_CONTAINER.getUsername(),
+                MYSQL_CONTAINER.getPassword());
+    }
+
+    private List<List<Object>> query(String sql) {
+        try (Connection connection = getJdbcConnection();
+                Statement statement = connection.createStatement();
+                ResultSet resultSet = statement.executeQuery(sql)) {
+            List<List<Object>> result = new ArrayList<>();
+            int columnCount = resultSet.getMetaData().getColumnCount();
+            while (resultSet.next()) {
+                ArrayList<Object> objects = new ArrayList<>();
+                for (int i = 1; i <= columnCount; i++) {
+                    objects.add(resultSet.getObject(i));
+                }
+                result.add(objects);
+            }
+            return result;
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

Reply via email to