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 3ff3fcf53b [Feature][Connector-V2] Extend JDBC sink table_options to 
TiDB and OceanBase MySQL (#11388)
3ff3fcf53b is described below

commit 3ff3fcf53bce2db733a33e07f677788a700b1469
Author: luxiaolong <[email protected]>
AuthorDate: Sun Jul 12 19:46:13 2026 +0800

    [Feature][Connector-V2] Extend JDBC sink table_options to TiDB and 
OceanBase MySQL (#11388)
    
    Co-authored-by: det101 <[email protected]>
    Co-authored-by: Cursor <[email protected]>
---
 docs/en/connectors/sink/Jdbc.md                    | 10 +++
 docs/zh/connectors/sink/Jdbc.md                    | 10 +++
 .../OceanBaseMysqlCreateTableSqlBuilder.java       |  7 ++-
 .../jdbc/internal/dialect/mysql/MysqlDialect.java  | 25 +-------
 .../dialect/mysql/MysqlFamilyTableOptions.java     | 55 ++++++++++++++++
 .../dialect/oceanbase/OceanBaseMysqlDialect.java   |  6 ++
 .../OceanBaseMysqlCreateTableSqlBuilderTest.java   | 71 +++++++++++++++++++++
 .../jdbc/catalog/tidb/TiDBTableOptionsTest.java    | 73 ++++++++++++++++++++++
 .../oceanbase/OceanBaseMysqlDialectTest.java       | 55 ++++++++++++++++
 .../JdbcTableOptionsConditionExtensionTest.java    | 63 +++++++++++++++++++
 10 files changed, 348 insertions(+), 27 deletions(-)

diff --git a/docs/en/connectors/sink/Jdbc.md b/docs/en/connectors/sink/Jdbc.md
index 5284498d1e..08637e4466 100644
--- a/docs/en/connectors/sink/Jdbc.md
+++ b/docs/en/connectors/sink/Jdbc.md
@@ -267,10 +267,20 @@ Current support:
 | Dialect | Supported | Allowed keys |
 |---------|-----------|--------------|
 | MySQL | Yes | `engine`, `charset`, `collate` |
+| TiDB | Yes | `engine`, `charset`, `collate` (via MySQL JDBC protocol and 
`jdbc:mysql://`) |
+| OceanBase (MySQL mode) | Yes | `engine`, `charset`, `collate` |
 | Other JDBC dialects | No | Non-empty `table_options` fails validation at job 
submission |
 
 Invalid or unsupported keys are validated early via `JdbcSinkFactory` option 
rules (`--check` and job submission), not only at runtime DDL.
 
+**Dialect notes:**
+
+- **MySQL**: `engine`, `charset`, and `collate` are appended to `CREATE TABLE` 
and take effect.
+- **TiDB**: When connected via `jdbc:mysql://` with a MySQL JDBC driver, TiDB 
shares the same key whitelist and DDL merge path as MySQL. `charset` and 
`collate` take effect; `engine` is accepted for MySQL syntax compatibility but 
is **ignored** by TiDB (storage engine is not configurable).
+- **OceanBase (MySQL mode)**: Supported for `jdbc:oceanbase://` when not using 
Oracle-compatible mode. `charset` and `collate` must be values supported by 
your OceanBase version (typically a MySQL-compatible subset; use `SHOW CHARSET` 
/ `SHOW COLLATION` on the target). Unsupported values fail when `CREATE TABLE` 
runs, not at job submission. OceanBase **Oracle-compatible mode** does not 
support `table_options`; a non-empty map fails at job submission.
+
+SeaTunnel validates the **key whitelist** at submission time only; it does not 
verify whether each value is supported by the target database (same as the 
initial MySQL delivery).
+
 Example (MySQL auto-create with engine and charset):
 
 ```hocon
diff --git a/docs/zh/connectors/sink/Jdbc.md b/docs/zh/connectors/sink/Jdbc.md
index e13b6e181c..a33503c583 100644
--- a/docs/zh/connectors/sink/Jdbc.md
+++ b/docs/zh/connectors/sink/Jdbc.md
@@ -256,10 +256,20 @@ Sink 在自动建表(SaveMode DDL)时附加的表级选项。仅在 `schema_
 | 方言 | 是否支持 | 可用 key |
 |------|----------|----------|
 | MySQL | 是 | `engine`、`charset`、`collate` |
+| TiDB | 是 | `engine`、`charset`、`collate`(通过 MySQL JDBC 协议与 `jdbc:mysql://` 
连接) |
+| OceanBase(MySQL 模式) | 是 | `engine`、`charset`、`collate` |
 | 其他 JDBC 方言 | 否 | 配置非空 `table_options` 时任务启动即校验失败 |
 
 非法或不支持的 key 会在 `JdbcSinkFactory` 的 option 规则阶段提前校验(`--check` 与作业提交),而非仅在运行时 
DDL 阶段失败。
 
+**方言说明:**
+
+- **MySQL**:`engine`、`charset`、`collate` 均会写入 `CREATE TABLE` 并生效。
+- **TiDB**:通过 `jdbc:mysql://` 与 MySQL JDBC 驱动连接时,与 MySQL 使用相同的 key 白名单与 DDL 
拼接方式。`charset`、`collate` 会生效;`engine` 仅为 MySQL 兼容语法,TiDB 会解析但**忽略**存储引擎设置。
+- **OceanBase(MySQL 模式)**:`jdbc:oceanbase://` 且非 Oracle 兼容模式时支持上述三个 
key。`charset`、`collate` 须为 OceanBase 当前版本支持的字符集与排序规则(通常为 MySQL 兼容子集,请以目标库 `SHOW 
CHARSET` / `SHOW COLLATION` 为准);不支持的取值会在执行 `CREATE TABLE` 
时报错,而非在作业提交阶段校验。OceanBase **Oracle 兼容模式**不支持 `table_options`,配置非空时任务启动即失败。
+
+SeaTunnel 在提交时仅校验 **key 白名单**,不校验具体取值是否被目标库支持(与 MySQL 首批实现一致)。
+
 示例(MySQL 自动建表时指定存储引擎与字符集):
 
 ```hocon
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMysqlCreateTableSqlBuilder.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMysqlCreateTableSqlBuilder.java
index 5053d8cc14..db80ccb09c 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMysqlCreateTableSqlBuilder.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMysqlCreateTableSqlBuilder.java
@@ -27,6 +27,7 @@ import org.apache.seatunnel.api.table.catalog.TablePath;
 import org.apache.seatunnel.api.table.catalog.TableSchema;
 import org.apache.seatunnel.api.table.converter.BasicTypeDefine;
 import org.apache.seatunnel.api.table.type.SqlType;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.mysql.MySqlCatalog;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.utils.CatalogUtils;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oceanbase.OceanBaseMySqlTypeConverter;
@@ -85,9 +86,9 @@ public class OceanBaseMysqlCreateTableSqlBuilder {
         return new OceanBaseMysqlCreateTableSqlBuilder(
                         tablePath.getTableName(), typeConverter, createIndex)
                 .comment(catalogTable.getComment())
-                // todo: set charset and collate
-                .engine(null)
-                .charset(null)
+                
.engine(catalogTable.getOptions().get(MySqlCatalog.TABLE_OPTION_ENGINE))
+                
.charset(catalogTable.getOptions().get(MySqlCatalog.TABLE_OPTION_CHARSET))
+                
.collate(catalogTable.getOptions().get(MySqlCatalog.TABLE_OPTION_COLLATE))
                 .primaryKey(tableSchema.getPrimaryKey())
                 .constraintKeys(tableSchema.getConstraintKeys())
                 .addColumn(tableSchema.getColumns())
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java
index 09fefdd564..685b19efad 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java
@@ -19,11 +19,9 @@ package 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql;
 
 import org.apache.seatunnel.shade.org.apache.commons.lang3.StringUtils;
 
-import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode;
 import org.apache.seatunnel.api.table.catalog.TablePath;
 import org.apache.seatunnel.api.table.converter.BasicTypeDefine;
 import org.apache.seatunnel.api.table.converter.TypeConverter;
-import 
org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorException;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.JdbcRowConverter;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDialect;
@@ -43,14 +41,11 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -58,10 +53,6 @@ public class MysqlDialect implements JdbcDialect {
 
     private static final List NOT_SUPPORTED_DEFAULT_VALUES =
             Arrays.asList(MysqlType.BLOB, MysqlType.TEXT, MysqlType.JSON, 
MysqlType.GEOMETRY);
-    private static final Set<String> SUPPORTED_TABLE_OPTIONS =
-            Collections.unmodifiableSet(
-                    new LinkedHashSet<>(Arrays.asList("engine", "charset", 
"collate")));
-
     public String fieldIde = FieldIdeEnum.ORIGINAL.getValue();
 
     public MysqlDialect() {}
@@ -400,20 +391,6 @@ public class MysqlDialect implements JdbcDialect {
 
     @Override
     public void validateTableOptions(Map<String, String> tableOptions) {
-        if (tableOptions == null || tableOptions.isEmpty()) {
-            return;
-        }
-
-        Set<String> unsupportedOptions = new 
LinkedHashSet<>(tableOptions.keySet());
-        unsupportedOptions.removeAll(SUPPORTED_TABLE_OPTIONS);
-        if (!unsupportedOptions.isEmpty()) {
-            throw new JdbcConnectorException(
-                    SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED,
-                    String.format(
-                            "Unsupported JDBC table_options for dialect '%s': 
%s. Supported keys: %s",
-                            dialectName(),
-                            String.join(", ", unsupportedOptions),
-                            String.join(", ", SUPPORTED_TABLE_OPTIONS)));
-        }
+        MysqlFamilyTableOptions.validate(dialectName(), tableOptions);
     }
 }
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlFamilyTableOptions.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlFamilyTableOptions.java
new file mode 100644
index 0000000000..3a19b0328d
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlFamilyTableOptions.java
@@ -0,0 +1,55 @@
+/*
+ * 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.jdbc.internal.dialect.mysql;
+
+import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorException;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Shared {@code table_options} validation for MySQL-compatible JDBC 
dialects. */
+public final class MysqlFamilyTableOptions {
+
+    public static final Set<String> SUPPORTED_KEYS =
+            Collections.unmodifiableSet(
+                    new LinkedHashSet<>(Arrays.asList("engine", "charset", 
"collate")));
+
+    private MysqlFamilyTableOptions() {}
+
+    public static void validate(String dialectName, Map<String, String> 
tableOptions) {
+        if (tableOptions == null || tableOptions.isEmpty()) {
+            return;
+        }
+
+        Set<String> unsupportedOptions = new 
LinkedHashSet<>(tableOptions.keySet());
+        unsupportedOptions.removeAll(SUPPORTED_KEYS);
+        if (!unsupportedOptions.isEmpty()) {
+            throw new JdbcConnectorException(
+                    SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED,
+                    String.format(
+                            "Unsupported JDBC table_options for dialect '%s': 
%s. Supported keys: %s",
+                            dialectName,
+                            String.join(", ", unsupportedOptions),
+                            String.join(", ", SUPPORTED_KEYS)));
+        }
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java
index 8f36f0a6eb..f505559bad 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java
@@ -28,6 +28,7 @@ import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDiale
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDialectTypeMapper;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.SQLUtils;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.dialectenum.FieldIdeEnum;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql.MysqlFamilyTableOptions;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.source.JdbcSourceTable;
 
 import lombok.extern.slf4j.Slf4j;
@@ -269,4 +270,9 @@ public class OceanBaseMysqlDialect implements JdbcDialect {
                 return false;
         }
     }
+
+    @Override
+    public void validateTableOptions(Map<String, String> tableOptions) {
+        MysqlFamilyTableOptions.validate(dialectName(), tableOptions);
+    }
 }
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMysqlCreateTableSqlBuilderTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMysqlCreateTableSqlBuilderTest.java
new file mode 100644
index 0000000000..95e85035e2
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMysqlCreateTableSqlBuilderTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.jdbc.catalog.oceanbase;
+
+import org.apache.seatunnel.shade.com.google.common.collect.Lists;
+
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
+import org.apache.seatunnel.api.table.catalog.PhysicalColumn;
+import org.apache.seatunnel.api.table.catalog.PrimaryKey;
+import org.apache.seatunnel.api.table.catalog.TableIdentifier;
+import org.apache.seatunnel.api.table.catalog.TablePath;
+import org.apache.seatunnel.api.table.catalog.TableSchema;
+import org.apache.seatunnel.api.table.type.BasicType;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.mysql.MySqlCatalog;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oceanbase.OceanBaseMySqlTypeConverter;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OceanBaseMysqlCreateTableSqlBuilderTest {
+
+    @Test
+    public void testBuildCreateTableSqlWithTableOptions() {
+        TablePath tablePath = TablePath.of("test_db", "test_table");
+        TableSchema tableSchema =
+                TableSchema.builder()
+                        .column(PhysicalColumn.of("id", BasicType.LONG_TYPE, 
0, false, null, "id"))
+                        .primaryKey(PrimaryKey.of("id", 
Lists.newArrayList("id")))
+                        .build();
+        Map<String, String> options = new HashMap<>();
+        options.put(MySqlCatalog.TABLE_OPTION_ENGINE, "InnoDB");
+        options.put(MySqlCatalog.TABLE_OPTION_CHARSET, "utf8mb4");
+        options.put(MySqlCatalog.TABLE_OPTION_COLLATE, "utf8mb4_unicode_ci");
+        CatalogTable catalogTable =
+                CatalogTable.of(
+                        TableIdentifier.of("test_catalog", "test_db", 
"test_table"),
+                        tableSchema,
+                        options,
+                        Collections.emptyList(),
+                        "table with options");
+
+        String createTableSql =
+                OceanBaseMysqlCreateTableSqlBuilder.builder(
+                                tablePath, catalogTable, 
OceanBaseMySqlTypeConverter.INSTANCE, true)
+                        .build(DatabaseIdentifier.OCEANBASE);
+
+        Assertions.assertTrue(createTableSql.contains("ENGINE = InnoDB"));
+        Assertions.assertTrue(createTableSql.contains("DEFAULT CHARSET = 
utf8mb4"));
+        Assertions.assertTrue(createTableSql.contains("COLLATE = 
utf8mb4_unicode_ci"));
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/tidb/TiDBTableOptionsTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/tidb/TiDBTableOptionsTest.java
new file mode 100644
index 0000000000..16350db3f0
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/tidb/TiDBTableOptionsTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.jdbc.catalog.tidb;
+
+import org.apache.seatunnel.shade.com.google.common.collect.Lists;
+
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
+import org.apache.seatunnel.api.table.catalog.PhysicalColumn;
+import org.apache.seatunnel.api.table.catalog.PrimaryKey;
+import org.apache.seatunnel.api.table.catalog.TableIdentifier;
+import org.apache.seatunnel.api.table.catalog.TablePath;
+import org.apache.seatunnel.api.table.catalog.TableSchema;
+import org.apache.seatunnel.api.table.type.BasicType;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.mysql.MySqlCatalog;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.mysql.MysqlCreateTableSqlBuilder;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql.MySqlTypeConverter;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/** TiDB catalog inherits MySQL auto-create DDL; table_options use the same 
keys. */
+public class TiDBTableOptionsTest {
+
+    @Test
+    public void testBuildCreateTableSqlWithTableOptions() {
+        TablePath tablePath = TablePath.of("test_db", "test_table");
+        TableSchema tableSchema =
+                TableSchema.builder()
+                        .column(PhysicalColumn.of("id", BasicType.LONG_TYPE, 
0, false, null, "id"))
+                        .primaryKey(PrimaryKey.of("id", 
Lists.newArrayList("id")))
+                        .build();
+        Map<String, String> options = new HashMap<>();
+        options.put(MySqlCatalog.TABLE_OPTION_ENGINE, "InnoDB");
+        options.put(MySqlCatalog.TABLE_OPTION_CHARSET, "utf8mb4");
+        options.put(MySqlCatalog.TABLE_OPTION_COLLATE, "utf8mb4_unicode_ci");
+        CatalogTable catalogTable =
+                CatalogTable.of(
+                        TableIdentifier.of("test_catalog", "test_db", 
"test_table"),
+                        tableSchema,
+                        options,
+                        Collections.emptyList(),
+                        "tidb table with options");
+
+        String createTableSql =
+                MysqlCreateTableSqlBuilder.builder(
+                                tablePath, catalogTable, 
MySqlTypeConverter.DEFAULT_INSTANCE, true)
+                        .build(DatabaseIdentifier.TIDB);
+
+        Assertions.assertTrue(createTableSql.contains("ENGINE = InnoDB"));
+        Assertions.assertTrue(createTableSql.contains("DEFAULT CHARSET = 
utf8mb4"));
+        Assertions.assertTrue(createTableSql.contains("COLLATE = 
utf8mb4_unicode_ci"));
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialectTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialectTest.java
new file mode 100644
index 0000000000..f4ba5c1ec3
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialectTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.jdbc.internal.dialect.oceanbase;
+
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OceanBaseMysqlDialectTest {
+
+    @Test
+    public void testValidateTableOptions() {
+        OceanBaseMysqlDialect dialect = new OceanBaseMysqlDialect();
+        Map<String, String> tableOptions = new HashMap<>();
+        tableOptions.put("engine", "InnoDB");
+        tableOptions.put("charset", "utf8mb4");
+        tableOptions.put("collate", "utf8mb4_unicode_ci");
+
+        Assertions.assertDoesNotThrow(() -> 
dialect.validateTableOptions(tableOptions));
+    }
+
+    @Test
+    public void testValidateTableOptionsWithUnknownKey() {
+        OceanBaseMysqlDialect dialect = new OceanBaseMysqlDialect();
+
+        JdbcConnectorException exception =
+                Assertions.assertThrows(
+                        JdbcConnectorException.class,
+                        () ->
+                                dialect.validateTableOptions(
+                                        Collections.singletonMap("bucket_num", 
"3")));
+        Assertions.assertTrue(exception.getMessage().contains("Unsupported 
JDBC table_options"));
+        Assertions.assertTrue(exception.getMessage().contains("OceanBase"));
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcTableOptionsConditionExtensionTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcTableOptionsConditionExtensionTest.java
index dd8e99697c..65d4c31e00 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcTableOptionsConditionExtensionTest.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcTableOptionsConditionExtensionTest.java
@@ -74,6 +74,44 @@ class JdbcTableOptionsConditionExtensionTest {
         Assertions.assertDoesNotThrow(() -> validateSinkOptionRule(config));
     }
 
+    @Test
+    void testTidbTableOptionsPassViaOptionRule() {
+        Map<String, Object> config = tidbSinkConfig();
+        Map<String, String> tableOptions = new HashMap<>();
+        tableOptions.put("engine", "InnoDB");
+        tableOptions.put("charset", "utf8mb4");
+        tableOptions.put("collate", "utf8mb4_unicode_ci");
+        config.put(SinkConnectorCommonOptions.TABLE_OPTIONS.key(), 
tableOptions);
+
+        Assertions.assertDoesNotThrow(() -> validateSinkOptionRule(config));
+    }
+
+    @Test
+    void testOceanBaseMysqlTableOptionsPassViaOptionRule() {
+        Map<String, Object> config = oceanBaseMysqlSinkConfig();
+        Map<String, String> tableOptions = new HashMap<>();
+        tableOptions.put("engine", "InnoDB");
+        tableOptions.put("charset", "utf8mb4");
+        tableOptions.put("collate", "utf8mb4_unicode_ci");
+        config.put(SinkConnectorCommonOptions.TABLE_OPTIONS.key(), 
tableOptions);
+
+        Assertions.assertDoesNotThrow(() -> validateSinkOptionRule(config));
+    }
+
+    @Test
+    void testOceanBaseOracleRejectsNonEmptyTableOptionsViaOptionRule() {
+        Map<String, Object> config = oceanBaseOracleSinkConfig();
+        Map<String, String> tableOptions = new HashMap<>();
+        tableOptions.put("engine", "InnoDB");
+        config.put(SinkConnectorCommonOptions.TABLE_OPTIONS.key(), 
tableOptions);
+
+        OptionValidationException exception =
+                Assertions.assertThrows(
+                        OptionValidationException.class, () -> 
validateSinkOptionRule(config));
+        Assertions.assertTrue(
+                exception.getMessage().contains("not supported for dialect 
'Oracle'"));
+    }
+
     private static void validateSinkOptionRule(Map<String, Object> config) {
         ConfigValidator.of(ReadonlyConfig.fromMap(config))
                 .validate(new JdbcSinkFactory().optionRule());
@@ -94,4 +132,29 @@ class JdbcTableOptionsConditionExtensionTest {
         config.put("query", "INSERT INTO test_table VALUES (?)");
         return config;
     }
+
+    private static Map<String, Object> tidbSinkConfig() {
+        Map<String, Object> config = new HashMap<>();
+        config.put("url", "jdbc:mysql://127.0.0.1:4000/test");
+        config.put("driver", "com.mysql.cj.jdbc.Driver");
+        config.put("query", "INSERT INTO test_table VALUES (?)");
+        return config;
+    }
+
+    private static Map<String, Object> oceanBaseMysqlSinkConfig() {
+        Map<String, Object> config = new HashMap<>();
+        config.put("url", "jdbc:oceanbase://127.0.0.1:2881/test");
+        config.put("driver", "com.oceanbase.jdbc.Driver");
+        config.put("query", "INSERT INTO test_table VALUES (?)");
+        return config;
+    }
+
+    private static Map<String, Object> oceanBaseOracleSinkConfig() {
+        Map<String, Object> config = new HashMap<>();
+        config.put("url", "jdbc:oceanbase://127.0.0.1:2881/test");
+        config.put("driver", "com.oceanbase.jdbc.Driver");
+        config.put("compatible_mode", "oracle");
+        config.put("query", "INSERT INTO test_table VALUES (?)");
+        return config;
+    }
 }

Reply via email to