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

davidzollo 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 523d69dbb1 [Feature][Connector-V2] Add OceanBase CDC source connector 
(#11060)
523d69dbb1 is described below

commit 523d69dbb1e324ab58ead6178b1b2a3a7c327aa4
Author: David Zollo <[email protected]>
AuthorDate: Tue Sep 22 09:40:56 2026 -0400

    [Feature][Connector-V2] Add OceanBase CDC source connector (#11060)
    
    Co-authored-by: Daniel <[email protected]>
    Co-authored-by: Daniel <[email protected]>
---
 config/plugin_config                               |   1 +
 .../changelog/connector-cdc-oceanbase.md           |   6 +
 docs/en/connectors/source/OceanBase-CDC.md         | 119 +++++++++
 .../changelog/connector-cdc-oceanbase.md           |   6 +
 docs/zh/connectors/source/OceanBase-CDC.md         | 117 +++++++++
 plugin-mapping.properties                          |   1 +
 .../source/MySqlIncrementalSourceFactory.java      | 108 ++++----
 .../connector-cdc/connector-cdc-oceanbase/pom.xml  |  46 ++++
 .../source/OceanBaseIncrementalSource.java         |  59 +++++
 .../source/OceanBaseIncrementalSourceFactory.java  | 123 +++++++++
 .../OceanBaseIncrementalSourceFactoryTest.java     | 109 ++++++++
 seatunnel-connectors-v2/connector-cdc/pom.xml      |   1 +
 seatunnel-dist/pom.xml                             |   6 +
 .../connector-cdc-oceanbase-e2e/pom.xml            |  69 +++++
 .../cdc/oceanbase/MySqlCompatibleContainer.java    | 287 ++++++++++++++++++++
 .../cdc/oceanbase/OceanBaseCDCCompatibilityIT.java | 288 +++++++++++++++++++++
 .../src/test/resources/docker/server-gtids/my.cnf  |  31 +++
 .../src/test/resources/docker/setup.sql            |  24 ++
 .../oceanbasecdc_to_mysql_compatible.conf          |  49 ++++
 seatunnel-e2e/seatunnel-connector-v2-e2e/pom.xml   |   1 +
 20 files changed, 1406 insertions(+), 45 deletions(-)

diff --git a/config/plugin_config b/config/plugin_config
index 651cb4033b..2636f8f827 100644
--- a/config/plugin_config
+++ b/config/plugin_config
@@ -29,6 +29,7 @@ connector-cassandra
 connector-salesforce
 connector-cdc-db2
 connector-cdc-mysql
+connector-cdc-oceanbase
 connector-cdc-mongodb
 connector-cdc-sqlserver
 connector-cdc-postgres
diff --git a/docs/en/connectors/changelog/connector-cdc-oceanbase.md 
b/docs/en/connectors/changelog/connector-cdc-oceanbase.md
new file mode 100644
index 0000000000..1cf41e674b
--- /dev/null
+++ b/docs/en/connectors/changelog/connector-cdc-oceanbase.md
@@ -0,0 +1,6 @@
+<details><summary> Change Log </summary>
+
+| Change | Commit | Version |
+| --- | --- | --- |
+
+</details>
diff --git a/docs/en/connectors/source/OceanBase-CDC.md 
b/docs/en/connectors/source/OceanBase-CDC.md
new file mode 100644
index 0000000000..da02447149
--- /dev/null
+++ b/docs/en/connectors/source/OceanBase-CDC.md
@@ -0,0 +1,119 @@
+import ChangeLog from '../changelog/connector-cdc-oceanbase.md';
+
+# OceanBase CDC
+
+> OceanBase CDC source connector
+
+## Support Those Engines
+
+> SeaTunnel Zeta<br/>
+> Flink <br/>
+
+## Key features
+
+- [ ] [batch](../../introduction/concepts/connector-v2-features.md)
+- [x] [stream](../../introduction/concepts/connector-v2-features.md)
+- [x] [exactly-once](../../introduction/concepts/connector-v2-features.md)
+- [ ] [column projection](../../introduction/concepts/connector-v2-features.md)
+- [x] [parallelism](../../introduction/concepts/connector-v2-features.md)
+- [x] [support user-defined 
split](../../introduction/concepts/connector-v2-features.md)
+
+## Description
+
+The first delivery of the OceanBase CDC connector targets the stable OceanBase 
MySQL compatible
+path backed by OceanBase Binlog Service.
+
+To keep the implementation narrow and restart-safe, `OceanBase-CDC` reuses 
SeaTunnel's
+`MySQL-CDC` incremental runtime for:
+
+- snapshot + incremental capture
+- checkpoint / restore handling
+- multi-table CDC row semantics
+- schema change propagation supported by `MySQL-CDC`
+
+OceanBase Oracle compatible mode is not supported in this first delivery.
+
+## Supported DataSource Info
+
+| Datasource | Supported versions | Driver | Url | Notes |
+| --- | --- | --- | --- | --- |
+| OceanBase CE / OceanBase EE (MySQL compatible mode) | OceanBase deployments 
that expose a MySQL-compatible snapshot endpoint and OceanBase Binlog Service | 
`com.mysql.cj.jdbc.Driver` | `jdbc:mysql://localhost:2881/test` | Requires 
OceanBase Binlog Service |
+
+## Using Dependency
+
+### Install Jdbc Driver
+
+#### For Flink Engine
+
+> 1. You need to ensure that the [MySQL JDBC 
driver](https://mvnrepository.com/artifact/mysql/mysql-connector-java) has been 
placed in `${SEATUNNEL_HOME}/plugins/`.
+
+#### For SeaTunnel Zeta Engine
+
+> 1. You need to ensure that the [MySQL JDBC 
driver](https://mvnrepository.com/artifact/mysql/mysql-connector-java) has been 
placed in `${SEATUNNEL_HOME}/lib/`.
+
+## OceanBase Preparation
+
+Before using `OceanBase-CDC`, make sure the monitored tenant satisfies these 
requirements:
+
+1. OceanBase runs in MySQL compatible mode for the captured tables.
+2. OceanBase Binlog Service is deployed and enabled for incremental 
subscription.
+3. The JDBC `url` points to a MySQL-compatible endpoint that SeaTunnel can use 
for snapshot reads.
+4. The configured account can read the captured tables and subscribe to 
incremental changes.
+
+## Source Options
+
+`OceanBase-CDC` reuses the `MySQL-CDC` option contract for its CDC behavior.
+
+Please refer to [MySQL CDC Source Options](./MySQL-CDC.md#source-options) for 
the complete option
+list.
+
+### OceanBase-specific constraints
+
+- Use a MySQL-compatible JDBC URL such as `jdbc:mysql://host:2881/database`.
+- Use the MySQL JDBC driver `com.mysql.cj.jdbc.Driver`.
+- `compatible_mode` is optional and is fixed to `mysql` when omitted. If 
configured, it must be
+  `mysql`; Oracle-compatible mode is not supported by this connector.
+- The first delivery supports explicitly configured tables only through 
`table-names`,
+  `table-pattern`, and `table-names-config`.
+- Startup modes, checkpoint / restore semantics, and schema evolution behavior 
are the same as
+  `MySQL-CDC`.
+
+## Task Example
+
+### Simple
+
+> Support multi-table reading from OceanBase MySQL compatible mode
+
+```
+env {
+  execution.parallelism = 1
+  job.mode = "STREAMING"
+  checkpoint.interval = 5000
+}
+
+source {
+  OceanBase-CDC {
+    plugin_output = "oceanbase_cdc"
+    username = "root"
+    password = "123456"
+    url = "jdbc:mysql://127.0.0.1:2881/inventory"
+    database-names = ["inventory"]
+    table-names = ["inventory.orders", "inventory.customers"]
+    server-time-zone = "Asia/Shanghai"
+    startup.mode = "initial"
+    exactly_once = true
+  }
+}
+
+transform {
+
+}
+
+sink {
+  Console {}
+}
+```
+
+## Changelog
+
+<ChangeLog />
diff --git a/docs/zh/connectors/changelog/connector-cdc-oceanbase.md 
b/docs/zh/connectors/changelog/connector-cdc-oceanbase.md
new file mode 100644
index 0000000000..527d6aa9c8
--- /dev/null
+++ b/docs/zh/connectors/changelog/connector-cdc-oceanbase.md
@@ -0,0 +1,6 @@
+<details><summary> Change Log </summary>
+
+| 变更 | Commit | 版本 |
+| --- | --- | --- |
+
+</details>
diff --git a/docs/zh/connectors/source/OceanBase-CDC.md 
b/docs/zh/connectors/source/OceanBase-CDC.md
new file mode 100644
index 0000000000..70d7fe9a52
--- /dev/null
+++ b/docs/zh/connectors/source/OceanBase-CDC.md
@@ -0,0 +1,117 @@
+import ChangeLog from '../changelog/connector-cdc-oceanbase.md';
+
+# OceanBase CDC
+
+> OceanBase CDC 源连接器
+
+## 支持这些引擎
+
+> SeaTunnel Zeta<br/>
+> Flink <br/>
+
+## 主要功能
+
+- [ ] [批处理](../../introduction/concepts/connector-v2-features.md)
+- [x] [流处理](../../introduction/concepts/connector-v2-features.md)
+- [x] [精确一次](../../introduction/concepts/connector-v2-features.md)
+- [ ] [列投影](../../introduction/concepts/connector-v2-features.md)
+- [x] [并行度](../../introduction/concepts/connector-v2-features.md)
+- [x] [支持用户定义的拆分](../../introduction/concepts/connector-v2-features.md)
+
+## 描述
+
+OceanBase CDC 首个交付版本只覆盖稳定、可验证的 OceanBase MySQL 兼容模式路径,并依赖
+OceanBase Binlog Service 提供增量订阅能力。
+
+为了把实现范围控制在最小且确保重启恢复语义可靠,`OceanBase-CDC` 直接复用 SeaTunnel
+现有 `MySQL-CDC` 的增量运行时,包括:
+
+- 全量快照 + 增量读取
+- checkpoint / restore 处理
+- 多表 CDC 行语义
+- `MySQL-CDC` 已支持的模式演进能力
+
+本次首批交付暂不支持 OceanBase Oracle 兼容模式的增量 CDC。
+
+## 支持的数据源信息
+
+| 数据源 | 支持版本 | 驱动 | URL | 说明 |
+| --- | --- | --- | --- | --- |
+| OceanBase CE / OceanBase EE(MySQL 兼容模式) | 暴露 MySQL 兼容快照读取端点,并开启 OceanBase 
Binlog Service 的部署 | `com.mysql.cj.jdbc.Driver` | 
`jdbc:mysql://localhost:2881/test` | 依赖 OceanBase Binlog Service |
+
+## 依赖使用
+
+### 安装 JDBC 驱动
+
+#### 对于 Flink 引擎
+
+> 1. 你需要确保 [MySQL JDBC 
驱动](https://mvnrepository.com/artifact/mysql/mysql-connector-java) 已经放在 
`${SEATUNNEL_HOME}/plugins/` 目录下。
+
+#### 对于 SeaTunnel Zeta 引擎
+
+> 1. 你需要确保 [MySQL JDBC 
驱动](https://mvnrepository.com/artifact/mysql/mysql-connector-java) 已经放在 
`${SEATUNNEL_HOME}/lib/` 目录下。
+
+## OceanBase 前置准备
+
+在使用 `OceanBase-CDC` 之前,请确保被采集租户满足以下条件:
+
+1. OceanBase 运行在 MySQL 兼容模式。
+2. OceanBase Binlog Service 已部署并开启增量订阅能力。
+3. JDBC `url` 指向 SeaTunnel 可用于快照读取的 MySQL 兼容端点。
+4. 配置账号同时具备快照读取和增量订阅所需权限。
+
+## 源端可选项
+
+`OceanBase-CDC` 在 CDC 行为上复用 `MySQL-CDC` 的配置契约。
+
+完整参数请直接参考 [MySQL CDC 源端参数](./MySQL-CDC.md#配置参数选项)。
+
+### OceanBase 特有约束
+
+- JDBC URL 需要使用 MySQL 兼容写法,例如 `jdbc:mysql://host:2881/database`。
+- JDBC 驱动固定使用 MySQL 驱动 `com.mysql.cj.jdbc.Driver`。
+- `compatible_mode` 为可选项,未配置时固定为 `mysql`。如显式配置,其值必须为
+  `mysql`;该连接器不支持 Oracle 兼容模式。
+- 首批版本只支持显式配置的表,即通过 `table-names`、`table-pattern`、
+  `table-names-config` 指定采集范围。
+- 启动模式、checkpoint / restore 语义、模式演进行为与 `MySQL-CDC` 保持一致。
+
+## 任务示例
+
+### 简单示例
+
+> 读取 OceanBase MySQL 兼容模式下的多张表
+
+```
+env {
+  execution.parallelism = 1
+  job.mode = "STREAMING"
+  checkpoint.interval = 5000
+}
+
+source {
+  OceanBase-CDC {
+    plugin_output = "oceanbase_cdc"
+    username = "root"
+    password = "123456"
+    url = "jdbc:mysql://127.0.0.1:2881/inventory"
+    database-names = ["inventory"]
+    table-names = ["inventory.orders", "inventory.customers"]
+    server-time-zone = "Asia/Shanghai"
+    startup.mode = "initial"
+    exactly_once = true
+  }
+}
+
+transform {
+
+}
+
+sink {
+  Console {}
+}
+```
+
+## 变更日志
+
+<ChangeLog />
diff --git a/plugin-mapping.properties b/plugin-mapping.properties
index f77a63baf4..4b41bb59a4 100644
--- a/plugin-mapping.properties
+++ b/plugin-mapping.properties
@@ -139,6 +139,7 @@ seatunnel.sink.Doris = connector-doris
 seatunnel.source.Maxcompute = connector-maxcompute
 seatunnel.sink.Maxcompute = connector-maxcompute
 seatunnel.source.MySQL-CDC = connector-cdc-mysql
+seatunnel.source.OceanBase-CDC = connector-cdc-oceanbase
 seatunnel.source.MongoDB-CDC = connector-cdc-mongodb
 seatunnel.source.TiDB-CDC = connector-cdc-tidb
 seatunnel.source.Vitess-CDC = connector-cdc-vitess
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java
index 9f4a45fc04..b16b0ddfc1 100644
--- 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java
@@ -54,6 +54,16 @@ public class MySqlIncrementalSourceFactory extends 
BaseChangeStreamTableSourceFa
 
     @Override
     public OptionRule optionRule() {
+        return getOptionRuleBuilder().build();
+    }
+
+    /**
+     * Create the common MySQL CDC source option rule builder.
+     *
+     * <p>Subclasses that reuse the MySQL CDC runtime can add 
connector-specific options before
+     * building the rule.
+     */
+    protected OptionRule.Builder getOptionRuleBuilder() {
         return JdbcSourceOptions.getBaseRule()
                 .required(
                         MySqlIncrementalSourceOptions.USERNAME,
@@ -101,8 +111,7 @@ public class MySqlIncrementalSourceFactory extends 
BaseChangeStreamTableSourceFa
                 .conditional(
                         MySqlIncrementalSourceOptions.STARTUP_MODE,
                         StartupMode.TIMESTAMP,
-                        SourceOptions.STARTUP_TIMESTAMP)
-                .build();
+                        SourceOptions.STARTUP_TIMESTAMP);
     }
 
     @Override
@@ -115,51 +124,60 @@ public class MySqlIncrementalSourceFactory extends 
BaseChangeStreamTableSourceFa
             TableSource<T, SplitT, StateT> restoreSource(
                     TableSourceFactoryContext context, List<CatalogTable> 
restoreTables) {
         return () -> {
-            // Load the JDBC driver in to DriverManager
-            try {
-                Class.forName("com.mysql.cj.jdbc.Driver");
-            } catch (Exception e) {
-                log.warn("Failed to load JDBC driver com.mysql.cj.jdbc.Driver 
", e);
-            }
             ReadonlyConfig config = context.getOptions();
-            List<CatalogTable> catalogTables =
-                    CatalogTableUtil.getCatalogTables(config, 
context.getClassLoader());
-            boolean enableSchemaChange =
-                    context.getOptions()
-                            .getOptional(SourceOptions.SCHEMA_CHANGES_ENABLED)
-                            .orElse(
-                                    // TODO remove this after all users used 
the new schema change
-                                    // option
-                                    context.getOptions()
-                                            
.getOptional(SourceOptions.DEBEZIUM_PROPERTIES)
-                                            .map(
-                                                    e ->
-                                                            e.getOrDefault(
-                                                                    
MySqlSourceConfigFactory
-                                                                            
.SCHEMA_CHANGE_KEY,
-                                                                    
SourceOptions
-                                                                            
.SCHEMA_CHANGES_ENABLED
-                                                                            
.defaultValue()
-                                                                            
.toString()))
-                                            .map(Boolean::parseBoolean)
-                                            .orElse(
-                                                    
SourceOptions.SCHEMA_CHANGES_ENABLED
-                                                            .defaultValue()));
-            if (!restoreTables.isEmpty() && enableSchemaChange) {
-                catalogTables = mergeTableStruct(catalogTables, restoreTables);
-            }
-
-            Optional<List<JdbcSourceTableConfig>> tableConfigs =
-                    
context.getOptions().getOptional(JdbcSourceOptions.TABLE_NAMES_CONFIG);
-            if (tableConfigs.isPresent()) {
-                catalogTables =
-                        CatalogTableUtils.mergeCatalogTableConfig(
-                                catalogTables,
-                                tableConfigs.get(),
-                                text -> TablePath.of(text, false));
-            }
             return (SeaTunnelSource<T, SplitT, StateT>)
-                    new MySqlIncrementalSource<>(config, catalogTables);
+                    new MySqlIncrementalSource<>(
+                            config, buildCatalogTables(context, config, 
restoreTables));
         };
     }
+
+    /**
+     * Build source tables from the current configuration and checkpoint state.
+     *
+     * <p>Subclasses that reuse the MySQL CDC runtime can provide a normalized 
connector
+     * configuration while keeping catalog discovery and checkpoint merge 
behavior aligned.
+     */
+    protected List<CatalogTable> buildCatalogTables(
+            TableSourceFactoryContext context,
+            ReadonlyConfig config,
+            List<CatalogTable> restoreTables) {
+        // Load the JDBC driver in to DriverManager
+        try {
+            Class.forName("com.mysql.cj.jdbc.Driver");
+        } catch (Exception e) {
+            log.warn("Failed to load JDBC driver com.mysql.cj.jdbc.Driver ", 
e);
+        }
+        List<CatalogTable> catalogTables =
+                CatalogTableUtil.getCatalogTables(config, 
context.getClassLoader());
+        boolean enableSchemaChange =
+                config.getOptional(SourceOptions.SCHEMA_CHANGES_ENABLED)
+                        .orElse(
+                                // TODO remove this after all users used the 
new schema change
+                                // option
+                                
config.getOptional(SourceOptions.DEBEZIUM_PROPERTIES)
+                                        .map(
+                                                e ->
+                                                        e.getOrDefault(
+                                                                
MySqlSourceConfigFactory
+                                                                        
.SCHEMA_CHANGE_KEY,
+                                                                
SourceOptions.SCHEMA_CHANGES_ENABLED
+                                                                        
.defaultValue()
+                                                                        
.toString()))
+                                        .map(Boolean::parseBoolean)
+                                        .orElse(
+                                                
SourceOptions.SCHEMA_CHANGES_ENABLED
+                                                        .defaultValue()));
+        if (!restoreTables.isEmpty() && enableSchemaChange) {
+            catalogTables = mergeTableStruct(catalogTables, restoreTables);
+        }
+
+        Optional<List<JdbcSourceTableConfig>> tableConfigs =
+                config.getOptional(JdbcSourceOptions.TABLE_NAMES_CONFIG);
+        if (tableConfigs.isPresent()) {
+            catalogTables =
+                    CatalogTableUtils.mergeCatalogTableConfig(
+                            catalogTables, tableConfigs.get(), text -> 
TablePath.of(text, false));
+        }
+        return catalogTables;
+    }
 }
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/pom.xml 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/pom.xml
new file mode 100644
index 0000000000..460f5c07b3
--- /dev/null
+++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.seatunnel</groupId>
+        <artifactId>connector-cdc</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>connector-cdc-oceanbase</artifactId>
+    <name>SeaTunnel : Connectors V2 : CDC : OceanBase</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.seatunnel</groupId>
+            <artifactId>connector-cdc-base</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.seatunnel</groupId>
+            <artifactId>connector-cdc-mysql</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSource.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSource.java
new file mode 100644
index 0000000000..36a020556d
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSource.java
@@ -0,0 +1,59 @@
+/*
+ * 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.oceanbase.source;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
+import 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.MySqlIncrementalSource;
+
+import java.util.List;
+
+/**
+ * OceanBase CDC source for the first delivery path that targets OceanBase 
MySQL compatible mode.
+ *
+ * <p>The connector intentionally reuses the MySQL CDC runtime because 
OceanBase Binlog Service
+ * exposes a MySQL-compatible change-log interface, which keeps snapshot, 
incremental, checkpoint,
+ * and restore semantics aligned with the existing MySQL connector.
+ *
+ * @param <T> emitted record type
+ */
+public class OceanBaseIncrementalSource<T> extends MySqlIncrementalSource<T> {
+
+    /** Stable plugin identifier used by SeaTunnel config and plugin 
discovery. */
+    static final String IDENTIFIER = "OceanBase-CDC";
+
+    /**
+     * Create an OceanBase CDC source backed by the MySQL CDC runtime.
+     *
+     * @param options connector options
+     * @param catalogTables tables discovered or restored for the source
+     */
+    public OceanBaseIncrementalSource(ReadonlyConfig options, 
List<CatalogTable> catalogTables) {
+        super(options, catalogTables);
+    }
+
+    /**
+     * Expose the OceanBase-specific plugin name while reusing 
MySQL-compatible internals.
+     *
+     * @return OceanBase CDC plugin name
+     */
+    @Override
+    public String getPluginName() {
+        return IDENTIFIER;
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSourceFactory.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSourceFactory.java
new file mode 100644
index 0000000000..3c5599d90e
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSourceFactory.java
@@ -0,0 +1,123 @@
+/*
+ * 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.oceanbase.source;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.configuration.util.Conditions;
+import org.apache.seatunnel.api.configuration.util.OptionRule;
+import org.apache.seatunnel.api.source.SeaTunnelSource;
+import org.apache.seatunnel.api.source.SourceSplit;
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
+import org.apache.seatunnel.api.table.connector.TableSource;
+import org.apache.seatunnel.api.table.factory.Factory;
+import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext;
+import 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.MySqlIncrementalSourceFactory;
+import org.apache.seatunnel.connectors.seatunnel.jdbc.config.JdbcCommonOptions;
+
+import com.google.auto.service.AutoService;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Factory for the OceanBase CDC source.
+ *
+ * <p>The first implementation deliberately wraps the MySQL CDC connector 
because that is the stable
+ * and testable path already adopted by Flink CDC for OceanBase Binlog Service.
+ */
+@AutoService(Factory.class)
+public class OceanBaseIncrementalSourceFactory extends 
MySqlIncrementalSourceFactory {
+
+    private static final String MYSQL_COMPATIBLE_MODE = "mysql";
+
+    /**
+     * Return the identifier used in SeaTunnel source config.
+     *
+     * @return OceanBase CDC identifier
+     */
+    @Override
+    public String factoryIdentifier() {
+        return OceanBaseIncrementalSource.IDENTIFIER;
+    }
+
+    /**
+     * Return the concrete source class used for OceanBase CDC jobs.
+     *
+     * @return OceanBase CDC source class
+     */
+    @Override
+    public Class<? extends SeaTunnelSource> getSourceClass() {
+        return OceanBaseIncrementalSource.class;
+    }
+
+    /**
+     * Allow the OceanBase catalog selector while restricting this 
MySQL-binlog wrapper to MySQL
+     * compatible mode.
+     */
+    @Override
+    public OptionRule optionRule() {
+        return getOptionRuleBuilder()
+                .optional(
+                        JdbcCommonOptions.COMPATIBLE_MODE,
+                        Conditions.matches(JdbcCommonOptions.COMPATIBLE_MODE, 
"(?i)mysql"))
+                .build();
+    }
+
+    /**
+     * Restore the source by reusing MySQL-compatible table discovery and 
checkpoint merge logic.
+     *
+     * @param context source factory context
+     * @param restoreTables restored table structures from checkpoint state
+     * @param <T> emitted record type
+     * @param <SplitT> split type
+     * @param <StateT> state type
+     * @return restorable OceanBase CDC table source
+     */
+    @Override
+    public <T, SplitT extends SourceSplit, StateT extends Serializable>
+            TableSource<T, SplitT, StateT> restoreSource(
+                    TableSourceFactoryContext context, List<CatalogTable> 
restoreTables) {
+        return () -> {
+            ReadonlyConfig config = 
mysqlCompatibleConfig(context.getOptions());
+            return (SeaTunnelSource<T, SplitT, StateT>)
+                    new OceanBaseIncrementalSource<>(
+                            config, buildCatalogTables(context, config, 
restoreTables));
+        };
+    }
+
+    /**
+     * Add the catalog selector required by OceanBase catalog discovery when 
users omit it.
+     *
+     * <p>Validation permits only MySQL mode because the incremental runtime 
uses MySQL binlog
+     * semantics.
+     */
+    ReadonlyConfig mysqlCompatibleConfig(ReadonlyConfig config) {
+        String compatibleMode =
+                
config.getOptional(JdbcCommonOptions.COMPATIBLE_MODE).orElse(MYSQL_COMPATIBLE_MODE);
+        if (!MYSQL_COMPATIBLE_MODE.equalsIgnoreCase(compatibleMode)) {
+            throw new IllegalArgumentException(
+                    "OceanBase-CDC supports only MySQL compatible mode, but 
compatible_mode is "
+                            + compatibleMode);
+        }
+        Map<String, Object> options = new HashMap<>(config.getSourceMap());
+        options.put(JdbcCommonOptions.COMPATIBLE_MODE.key(), 
MYSQL_COMPATIBLE_MODE);
+        return ReadonlyConfig.fromMap(options);
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSourceFactoryTest.java
 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSourceFactoryTest.java
new file mode 100644
index 0000000000..9878a05024
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oceanbase/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/source/OceanBaseIncrementalSourceFactoryTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.oceanbase.source;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.configuration.util.ConfigValidator;
+import org.apache.seatunnel.api.configuration.util.OptionValidationException;
+import 
org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.MySqlIncrementalSourceFactory;
+import org.apache.seatunnel.connectors.seatunnel.jdbc.config.JdbcCommonOptions;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests the OceanBase CDC wrapper contract that differentiates it from the 
reused MySQL CDC
+ * implementation.
+ */
+public class OceanBaseIncrementalSourceFactoryTest {
+
+    /** Verify the OceanBase wrapper uses a dedicated factory identifier for 
plugin discovery. */
+    @Test
+    public void testFactoryIdentifier() {
+        Assertions.assertEquals(
+                "OceanBase-CDC", new 
OceanBaseIncrementalSourceFactory().factoryIdentifier());
+    }
+
+    /** Verify the wrapper keeps the MySQL factory inheritance so restore 
logic stays aligned. */
+    @Test
+    public void testFactoryInheritance() {
+        Assertions.assertEquals(
+                MySqlIncrementalSourceFactory.class,
+                OceanBaseIncrementalSourceFactory.class.getSuperclass());
+    }
+
+    /** Verify the factory returns the OceanBase wrapper source instead of the 
raw MySQL source. */
+    @Test
+    public void testSourceClass() {
+        Assertions.assertEquals(
+                OceanBaseIncrementalSource.class,
+                new OceanBaseIncrementalSourceFactory().getSourceClass());
+    }
+
+    /** Verify restore configuration defaults the OceanBase catalog selector 
to MySQL mode. */
+    @Test
+    public void testRestoreConfigurationDefaultsCatalogCompatibleModeToMysql() 
{
+        OceanBaseIncrementalSourceFactory factory = new 
OceanBaseIncrementalSourceFactory();
+        ReadonlyConfig config = ReadonlyConfig.fromMap(requiredOptions());
+
+        ConfigValidator.of(config).validate(factory.optionRule());
+        ConfigValidator.validateUnknownKeys(config, factory.optionRule(), 
"OceanBase-CDC");
+
+        Assertions.assertEquals(
+                "mysql",
+                
factory.mysqlCompatibleConfig(config).get(JdbcCommonOptions.COMPATIBLE_MODE));
+    }
+
+    /** Verify static validation accepts the explicit MySQL selector used by 
E2E configurations. */
+    @Test
+    public void testOptionRuleAcceptsMysqlCompatibleMode() {
+        OceanBaseIncrementalSourceFactory factory = new 
OceanBaseIncrementalSourceFactory();
+        Map<String, Object> options = requiredOptions();
+        options.put(JdbcCommonOptions.COMPATIBLE_MODE.key(), "mysql");
+        ReadonlyConfig config = ReadonlyConfig.fromMap(options);
+
+        ConfigValidator.of(config).validate(factory.optionRule());
+        ConfigValidator.validateUnknownKeys(config, factory.optionRule(), 
"OceanBase-CDC");
+    }
+
+    /** Verify this MySQL-binlog connector rejects the unsupported OceanBase 
Oracle mode. */
+    @Test
+    public void testOptionRuleRejectsOracleCompatibleMode() {
+        Map<String, Object> options = requiredOptions();
+        options.put(JdbcCommonOptions.COMPATIBLE_MODE.key(), "oracle");
+
+        Assertions.assertThrows(
+                OptionValidationException.class,
+                () ->
+                        ConfigValidator.of(ReadonlyConfig.fromMap(options))
+                                .validate(new 
OceanBaseIncrementalSourceFactory().optionRule()));
+    }
+
+    private static Map<String, Object> requiredOptions() {
+        Map<String, Object> options = new HashMap<>();
+        options.put("username", "root");
+        options.put("password", "password");
+        options.put("url", "jdbc:mysql://localhost:2881/inventory");
+        options.put("table-names", 
Collections.singletonList("inventory.orders"));
+        return options;
+    }
+}
diff --git a/seatunnel-connectors-v2/connector-cdc/pom.xml 
b/seatunnel-connectors-v2/connector-cdc/pom.xml
index 1e90099893..f459bc109c 100644
--- a/seatunnel-connectors-v2/connector-cdc/pom.xml
+++ b/seatunnel-connectors-v2/connector-cdc/pom.xml
@@ -33,6 +33,7 @@
         <module>connector-cdc-base</module>
         <module>connector-cdc-db2</module>
         <module>connector-cdc-mysql</module>
+        <module>connector-cdc-oceanbase</module>
         <module>connector-cdc-sqlserver</module>
         <module>connector-cdc-mongodb</module>
         <module>connector-cdc-postgres</module>
diff --git a/seatunnel-dist/pom.xml b/seatunnel-dist/pom.xml
index cdbaf588cb..3b0c38b4ff 100644
--- a/seatunnel-dist/pom.xml
+++ b/seatunnel-dist/pom.xml
@@ -770,6 +770,12 @@
                     <version>${project.version}</version>
                     <scope>provided</scope>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.seatunnel</groupId>
+                    <artifactId>connector-cdc-oceanbase</artifactId>
+                    <version>${project.version}</version>
+                    <scope>provided</scope>
+                </dependency>
                 <dependency>
                     <groupId>org.apache.seatunnel</groupId>
                     <artifactId>connector-cdc-oracle</artifactId>
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/pom.xml 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/pom.xml
new file mode 100644
index 0000000000..736fe4bb36
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.seatunnel</groupId>
+        <artifactId>seatunnel-connector-v2-e2e</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>connector-cdc-oceanbase-e2e</artifactId>
+    <name>SeaTunnel : E2E : Connector V2 : CDC OceanBase</name>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.seatunnel</groupId>
+                <artifactId>connector-jdbc</artifactId>
+                <version>${project.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <!-- SeaTunnel connectors -->
+        <dependency>
+            <groupId>org.apache.seatunnel</groupId>
+            <artifactId>connector-cdc-base</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.seatunnel</groupId>
+            <artifactId>connector-cdc-oceanbase</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>mysql</artifactId>
+            <version>${testcontainer.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/MySqlCompatibleContainer.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/MySqlCompatibleContainer.java
new file mode 100644
index 0000000000..c3321147eb
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/MySqlCompatibleContainer.java
@@ -0,0 +1,287 @@
+/*
+ * 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.oceanbase;
+
+import org.testcontainers.containers.ContainerLaunchException;
+import org.testcontainers.containers.JdbcDatabaseContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * MySQL-compatible source container used by the OceanBase CDC E2E module.
+ *
+ * <p>The utility stays local to this module so OceanBase E2E does not depend 
on MySQL CDC test
+ * artifacts while still preserving docker-entrypoint initialization for GTID 
and binlog settings.
+ */
+class MySqlCompatibleContainer extends 
JdbcDatabaseContainer<MySqlCompatibleContainer> {
+
+    /** Official MySQL image repository used as the OceanBase-compatible CDC 
runtime. */
+    private static final String IMAGE = "mysql";
+
+    /** MySQL service port exposed inside the Docker network. */
+    private static final Integer MYSQL_PORT = 3306;
+
+    /** Classpath resource parameter that maps a custom my.cnf into the 
container. */
+    private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = "MY_CNF";
+
+    /** Classpath resource parameter that maps setup SQL into 
docker-entrypoint-initdb.d. */
+    private static final String SETUP_SQL_PARAM_NAME = "SETUP_SQL";
+
+    /** MySQL root user name, used to decide whether an empty password is 
legal. */
+    private static final String MYSQL_ROOT_USER = "root";
+
+    /** Database created by the container entrypoint before setup SQL runs. */
+    private String databaseName = "test";
+
+    /** User created by the container entrypoint for JDBC setup and 
assertions. */
+    private String username = "test";
+
+    /** Password used by both the test user and the root account. */
+    private String password = "test";
+
+    /**
+     * Create a MySQL-compatible container with a fixed image tag.
+     *
+     * @param version MySQL image tag used by the E2E test
+     */
+    MySqlCompatibleContainer(String version) {
+        super(DockerImageName.parse(IMAGE + ":" + version));
+        addExposedPort(MYSQL_PORT);
+    }
+
+    /**
+     * Return the mapped MySQL port used by Testcontainers' JDBC wait strategy.
+     *
+     * @return mapped liveness check ports
+     */
+    @Override
+    protected Set<Integer> getLivenessCheckPorts() {
+        Set<Integer> ports = new HashSet<>();
+        ports.add(getMappedPort(MYSQL_PORT));
+        return ports;
+    }
+
+    /** Configure MySQL entrypoint environment and optional classpath-mounted 
resources. */
+    @Override
+    protected void configure() {
+        optionallyMapResourceParameterAsVolume(
+                MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, "/etc/mysql/", 
"mysql-default-conf");
+
+        if (parameters.containsKey(SETUP_SQL_PARAM_NAME)) {
+            optionallyMapResourceParameterAsVolume(
+                    SETUP_SQL_PARAM_NAME, "/docker-entrypoint-initdb.d/", 
"N/A");
+        }
+
+        addEnv("MYSQL_DATABASE", databaseName);
+        addEnv("MYSQL_USER", username);
+        if (password != null && !password.isEmpty()) {
+            addEnv("MYSQL_PASSWORD", password);
+            addEnv("MYSQL_ROOT_PASSWORD", password);
+        } else if (MYSQL_ROOT_USER.equalsIgnoreCase(username)) {
+            addEnv("MYSQL_ALLOW_EMPTY_PASSWORD", "yes");
+        } else {
+            throw new ContainerLaunchException(
+                    "Empty password can be used only with the root user");
+        }
+        setStartupAttempts(3);
+    }
+
+    /**
+     * Resolve the MySQL driver class name used by the local JDBC assertions.
+     *
+     * @return available MySQL driver class
+     */
+    @Override
+    public String getDriverClassName() {
+        try {
+            Class.forName("com.mysql.cj.jdbc.Driver");
+            return "com.mysql.cj.jdbc.Driver";
+        } catch (ClassNotFoundException e) {
+            return "com.mysql.jdbc.Driver";
+        }
+    }
+
+    /**
+     * Build a JDBC URL for the requested database.
+     *
+     * @param databaseName target database name
+     * @return JDBC URL for the mapped container port
+     */
+    String getJdbcUrl(String databaseName) {
+        String additionalUrlParams = constructUrlParameters("?", "&");
+        return "jdbc:mysql://"
+                + getHost()
+                + ":"
+                + getDatabasePort()
+                + "/"
+                + databaseName
+                + additionalUrlParams;
+    }
+
+    /**
+     * Build the default JDBC URL for this container.
+     *
+     * @return JDBC URL for the configured database
+     */
+    @Override
+    public String getJdbcUrl() {
+        return getJdbcUrl(databaseName);
+    }
+
+    /**
+     * Return the host-mapped MySQL port.
+     *
+     * @return mapped MySQL port
+     */
+    int getDatabasePort() {
+        return getMappedPort(MYSQL_PORT);
+    }
+
+    /**
+     * Add connection parameters needed by MySQL 8 test containers.
+     *
+     * @param queryString query string from Testcontainers
+     * @return JDBC URL with required compatibility parameters
+     */
+    @Override
+    protected String constructUrlForConnection(String queryString) {
+        String url = super.constructUrlForConnection(queryString);
+
+        if (!url.contains("useSSL=")) {
+            url = appendUrlParam(url, "useSSL=false");
+        }
+
+        if (!url.contains("allowPublicKeyRetrieval=")) {
+            url = appendUrlParam(url, "allowPublicKeyRetrieval=true");
+        }
+
+        return url;
+    }
+
+    /**
+     * Return the configured database name.
+     *
+     * @return database name
+     */
+    @Override
+    public String getDatabaseName() {
+        return databaseName;
+    }
+
+    /**
+     * Return the configured JDBC user.
+     *
+     * @return JDBC user name
+     */
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Return the configured JDBC password.
+     *
+     * @return JDBC password
+     */
+    @Override
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Return a cheap query for container readiness checks.
+     *
+     * @return readiness query
+     */
+    @Override
+    protected String getTestQueryString() {
+        return "SELECT 1";
+    }
+
+    /**
+     * Set the classpath my.cnf resource that should be mounted into the 
container.
+     *
+     * @param configPath classpath path to the MySQL config file
+     * @return this container
+     */
+    MySqlCompatibleContainer withConfigurationOverride(String configPath) {
+        parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, configPath);
+        return this;
+    }
+
+    /**
+     * Set the classpath setup SQL resource that should run during MySQL 
entrypoint initialization.
+     *
+     * @param sqlPath classpath path to setup SQL
+     * @return this container
+     */
+    MySqlCompatibleContainer withSetupSQL(String sqlPath) {
+        parameters.put(SETUP_SQL_PARAM_NAME, sqlPath);
+        return this;
+    }
+
+    /**
+     * Set the database created by the MySQL entrypoint.
+     *
+     * @param databaseName database name
+     * @return this container
+     */
+    @Override
+    public MySqlCompatibleContainer withDatabaseName(final String 
databaseName) {
+        this.databaseName = databaseName;
+        return this;
+    }
+
+    /**
+     * Set the JDBC user created by the MySQL entrypoint.
+     *
+     * @param username JDBC user name
+     * @return this container
+     */
+    @Override
+    public MySqlCompatibleContainer withUsername(final String username) {
+        this.username = username;
+        return this;
+    }
+
+    /**
+     * Set the password used by the JDBC and root users.
+     *
+     * @param password JDBC password
+     * @return this container
+     */
+    @Override
+    public MySqlCompatibleContainer withPassword(final String password) {
+        this.password = password;
+        return this;
+    }
+
+    /**
+     * Append a JDBC URL parameter using the correct separator for the current 
URL.
+     *
+     * @param url current JDBC URL
+     * @param parameter URL parameter to append
+     * @return JDBC URL with the parameter appended
+     */
+    private String appendUrlParam(String url, String parameter) {
+        String separator = url.contains("?") ? "&" : "?";
+        return url + separator + parameter;
+    }
+}
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/OceanBaseCDCCompatibilityIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/OceanBaseCDCCompatibilityIT.java
new file mode 100644
index 0000000000..6a1b2ed3b7
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oceanbase/OceanBaseCDCCompatibilityIT.java
@@ -0,0 +1,288 @@
+/*
+ * 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.oceanbase;
+
+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.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.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.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import static org.awaitility.Awaitility.await;
+
+/**
+ * Verifies that the OceanBase CDC connector can be discovered and run through 
the MySQL-compatible
+ * CDC path used by the current OceanBase wrapper.
+ *
+ * <p>This is intentionally a compatibility smoke E2E. It does not claim to 
validate a real
+ * OceanBase Binlog Service deployment because the project has no reusable 
OceanBase CDC
+ * Testcontainers environment yet.
+ */
+@Slf4j
+@DisabledOnContainer(
+        value = {},
+        type = {EngineType.SPARK},
+        disabledReason = "Currently SPARK do not support cdc")
+public class OceanBaseCDCCompatibilityIT extends TestSuiteBase implements 
TestResource {
+
+    /**
+     * Network alias used by SeaTunnel engine containers to reach the 
MySQL-compatible CDC source.
+     */
+    private static final String MYSQL_HOST = "mysql_cdc_e2e";
+
+    /** Administrative user used by the test to prepare source and sink 
tables. */
+    private static final String MYSQL_USER_NAME = "mysqluser";
+
+    /** Password shared by the setup SQL users in the MySQL-compatible E2E 
environment. */
+    private static final String MYSQL_USER_PASSWORD = "mysqlpw";
+
+    /** Database name shared with the SeaTunnel job config under test. */
+    private static final String MYSQL_DATABASE = "mysql_cdc";
+
+    /** Small source table used to keep the OceanBase wrapper smoke test 
focused and fast. */
+    private static final String SOURCE_TABLE = 
"oceanbase_cdc_e2e_source_table";
+
+    /** Dedicated sink table generated by the JDBC sink for this compatibility 
test. */
+    private static final String SINK_TABLE = "oceanbase_cdc_e2e_sink_table";
+
+    /** MySQL driver jar required by the OceanBase wrapper because it 
delegates to MySQL CDC. */
+    private static final String MYSQL_DRIVER_URL =
+            
"https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.32/mysql-connector-j-8.0.32.jar";;
+
+    /** MySQL-compatible source container used as the reproducible CDC runtime 
for the wrapper. */
+    private static final MySqlCompatibleContainer MYSQL_CONTAINER = 
createMySqlContainer();
+
+    /**
+     * Build the MySQL-compatible source with GTID/binlog settings required by 
CDC readers.
+     *
+     * @return configured MySQL-compatible container
+     */
+    private static MySqlCompatibleContainer createMySqlContainer() {
+        return new MySqlCompatibleContainer("8.0.43")
+                .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")));
+    }
+
+    /**
+     * Install the JDBC driver into the OceanBase CDC plugin directory inside 
each engine container.
+     */
+    @TestContainerExtension
+    protected final ContainerExtendedFactory extendedFactory =
+            container -> {
+                Container.ExecResult extraCommands =
+                        container.execInContainer(
+                                "bash",
+                                "-c",
+                                "mkdir -p 
/tmp/seatunnel/plugins/OceanBase-CDC/lib && cd 
/tmp/seatunnel/plugins/OceanBase-CDC/lib && wget "
+                                        + MYSQL_DRIVER_URL);
+                Assertions.assertEquals(0, extraCommands.getExitCode(), 
extraCommands.getStderr());
+            };
+
+    /** Start the MySQL-compatible CDC source before the SeaTunnel job 
containers run. */
+    @BeforeAll
+    @Override
+    public void startUp() {
+        log.info("Starting MySQL-compatible container for OceanBase CDC 
compatibility E2E.");
+        Startables.deepStart(Stream.of(MYSQL_CONTAINER)).join();
+        log.info("MySQL-compatible container for OceanBase CDC compatibility 
E2E is started.");
+    }
+
+    /** Close the MySQL container allocated by this test class. */
+    @Override
+    @AfterAll
+    public void tearDown() {
+        if (MYSQL_CONTAINER != null) {
+            MYSQL_CONTAINER.close();
+        }
+    }
+
+    /**
+     * Run OceanBase-CDC against a MySQL-compatible CDC source and verify 
snapshot plus incremental
+     * events reach the JDBC sink.
+     *
+     * @param container SeaTunnel engine test container
+     */
+    @TestTemplate
+    public void testOceanBaseCdcWrapperRuntimeE2e(TestContainer container) {
+        recreateTestTables();
+
+        CompletableFuture<Void> jobFuture =
+                CompletableFuture.runAsync(
+                        () -> {
+                            try {
+                                Container.ExecResult execResult =
+                                        container.executeJob(
+                                                
"/oceanbasecdc_to_mysql_compatible.conf");
+                                Assertions.assertEquals(
+                                        0, execResult.getExitCode(), 
execResult.getStderr());
+                            } catch (Exception e) {
+                                throw new CompletionException(e);
+                            }
+                        });
+
+        await().atMost(60, TimeUnit.SECONDS)
+                .untilAsserted(
+                        () -> {
+                            assertStreamingJobStillRunning(jobFuture);
+                            Assertions.assertIterableEquals(
+                                    queryTable(SOURCE_TABLE), 
queryTable(SINK_TABLE));
+                        });
+
+        applyIncrementalChanges();
+
+        await().atMost(60, TimeUnit.SECONDS)
+                .untilAsserted(
+                        () -> {
+                            assertStreamingJobStillRunning(jobFuture);
+                            Assertions.assertIterableEquals(
+                                    queryTable(SOURCE_TABLE), 
queryTable(SINK_TABLE));
+                        });
+    }
+
+    /** Recreate isolated source and sink tables for deterministic snapshot 
assertions. */
+    private void recreateTestTables() {
+        executeSql("DROP TABLE IF EXISTS " + MYSQL_DATABASE + "." + 
SINK_TABLE);
+        executeSql("DROP TABLE IF EXISTS " + MYSQL_DATABASE + "." + 
SOURCE_TABLE);
+        executeSql(
+                "CREATE TABLE "
+                        + MYSQL_DATABASE
+                        + "."
+                        + SOURCE_TABLE
+                        + " (id INT PRIMARY KEY, name VARCHAR(64), score 
INT)");
+        executeSql(
+                "CREATE TABLE "
+                        + MYSQL_DATABASE
+                        + "."
+                        + SINK_TABLE
+                        + " (id INT PRIMARY KEY, name VARCHAR(64), score 
INT)");
+        executeSql(
+                "INSERT INTO "
+                        + MYSQL_DATABASE
+                        + "."
+                        + SOURCE_TABLE
+                        + " VALUES (1, 'alpha', 10), (2, 'beta', 20)");
+    }
+
+    /** Apply insert, update and delete operations after the initial snapshot 
is synchronized. */
+    private void applyIncrementalChanges() {
+        executeSql(
+                "INSERT INTO " + MYSQL_DATABASE + "." + SOURCE_TABLE + " 
VALUES (3, 'gamma', 30)");
+        executeSql(
+                "UPDATE " + MYSQL_DATABASE + "." + SOURCE_TABLE + " SET score 
= 25 WHERE id = 2");
+        executeSql("DELETE FROM " + MYSQL_DATABASE + "." + SOURCE_TABLE + " 
WHERE id = 1");
+    }
+
+    /**
+     * Fail fast if the asynchronous streaming job has already exited or 
failed.
+     *
+     * @param jobFuture asynchronous SeaTunnel job execution future
+     */
+    private void assertStreamingJobStillRunning(CompletableFuture<Void> 
jobFuture) {
+        if (jobFuture.isCompletedExceptionally()) {
+            jobFuture.join();
+        }
+        Assertions.assertFalse(jobFuture.isDone(), "OceanBase CDC streaming 
job exited early.");
+    }
+
+    /**
+     * Query the deterministic table projection used by this test.
+     *
+     * @param tableName source or sink table name
+     * @return rows ordered by primary key
+     */
+    private List<List<Object>> queryTable(String tableName) {
+        String sql =
+                "SELECT id, name, score FROM " + MYSQL_DATABASE + "." + 
tableName + " ORDER BY id";
+        try (Connection connection = getJdbcConnection();
+                Statement statement = connection.createStatement();
+                ResultSet resultSet = statement.executeQuery(sql)) {
+            List<List<Object>> result = new ArrayList<>();
+            while (resultSet.next()) {
+                result.add(
+                        Arrays.asList(
+                                resultSet.getInt("id"),
+                                resultSet.getString("name"),
+                                resultSet.getInt("score")));
+            }
+            log.debug("OceanBase CDC compatibility query, sql: {}, data: {}", 
sql, result);
+            return result;
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Execute table setup and mutation SQL against the MySQL-compatible CDC 
source.
+     *
+     * @param sql SQL statement to execute
+     */
+    private void executeSql(String sql) {
+        try (Connection connection = getJdbcConnection();
+                Statement statement = connection.createStatement()) {
+            statement.execute(sql);
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Open an administrative JDBC connection to the MySQL-compatible source 
container.
+     *
+     * @return JDBC connection for setup and assertions
+     * @throws SQLException when the container rejects the connection
+     */
+    private Connection getJdbcConnection() throws SQLException {
+        return DriverManager.getConnection(
+                MYSQL_CONTAINER.getJdbcUrl(),
+                MYSQL_CONTAINER.getUsername(),
+                MYSQL_CONTAINER.getPassword());
+    }
+}
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/docker/server-gtids/my.cnf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/docker/server-gtids/my.cnf
new file mode 100644
index 0000000000..bfe5641b9c
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/docker/server-gtids/my.cnf
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+[mysqld]
+skip-host-cache
+skip-name-resolve
+secure-file-priv=/var/lib/mysql
+user=mysql
+symbolic-links=0
+
+server-id         = 223344
+log_bin           = mysql-bin
+expire_logs_days  = 1
+binlog_format     = row
+
+gtid_mode = on
+enforce_gtid_consistency = on
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/docker/setup.sql
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/docker/setup.sql
new file mode 100644
index 0000000000..26eae4ac5c
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/docker/setup.sql
@@ -0,0 +1,24 @@
+--
+-- 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.
+--
+
+GRANT ALL PRIVILEGES ON *.* TO 'mysqluser'@'%';
+
+CREATE USER 'st_user_source' IDENTIFIED BY 'mysqlpw';
+GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT, 
DROP, LOCK TABLES ON *.* TO 'st_user_source'@'%';
+
+CREATE USER 'st_user_sink' IDENTIFIED BY 'mysqlpw';
+GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON *.* TO 
'st_user_sink'@'%';
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/oceanbasecdc_to_mysql_compatible.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/oceanbasecdc_to_mysql_compatible.conf
new file mode 100644
index 0000000000..0315ee7e81
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-cdc-oceanbase-e2e/src/test/resources/oceanbasecdc_to_mysql_compatible.conf
@@ -0,0 +1,49 @@
+#
+# 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.
+#
+
+env {
+  parallelism = 1
+  job.mode = "STREAMING"
+  checkpoint.interval = 5000
+}
+
+source {
+  OceanBase-CDC {
+    plugin_output = "customers_oceanbase_cdc"
+    server-id = 5752
+    username = "st_user_source"
+    password = "mysqlpw"
+    compatible_mode = "mysql"
+    table-names = ["mysql_cdc.oceanbase_cdc_e2e_source_table"]
+    url = "jdbc:mysql://mysql_cdc_e2e:3306/mysql_cdc"
+  }
+}
+
+sink {
+  jdbc {
+    plugin_input = "customers_oceanbase_cdc"
+    url = "jdbc:mysql://mysql_cdc_e2e:3306/mysql_cdc"
+    driver = "com.mysql.cj.jdbc.Driver"
+    user = "st_user_sink"
+    password = "mysqlpw"
+
+    generate_sink_sql = true
+    database = mysql_cdc
+    table = oceanbase_cdc_e2e_sink_table
+    primary_keys = ["id"]
+  }
+}
diff --git a/seatunnel-e2e/seatunnel-connector-v2-e2e/pom.xml 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/pom.xml
index 4dc825191b..9a488d3b5b 100644
--- a/seatunnel-e2e/seatunnel-connector-v2-e2e/pom.xml
+++ b/seatunnel-e2e/seatunnel-connector-v2-e2e/pom.xml
@@ -61,6 +61,7 @@
         <module>connector-iotdb-e2e</module>
         <module>connector-iotdb-v2-e2e</module>
         <module>connector-cdc-mysql-e2e</module>
+        <module>connector-cdc-oceanbase-e2e</module>
         <module>connector-cdc-mongodb-e2e</module>
         <module>connector-iceberg-e2e</module>
         <module>connector-iceberg-hadoop3-e2e</module>

Reply via email to