This is an automated email from the ASF dual-hosted git repository.
lvyanquan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink-cdc.git
The following commit(s) were added to refs/heads/master by this push:
new 23fb108e9b [FLINK-40340][mysql] Fix schema change exception when
parsing MySQL zerofill data types without unsigned keyword (#4500)
23fb108e9b is described below
commit 23fb108e9bea7f689230fa188e5f80c6eef0dbc3
Author: Jzjsnow <[email protected]>
AuthorDate: Fri Aug 21 10:24:22 2026 +0800
[FLINK-40340][mysql] Fix schema change exception when parsing MySQL
zerofill data types without unsigned keyword (#4500)
---
.../cdc/connectors/mysql/utils/MySqlTypeUtils.java | 26 ++++
.../mysql/source/MySqlFullTypesITCase.java | 71 +++++++++
.../mysql/source/MySqlMetadataAccessorITCase.java | 4 +-
.../mysql/source/MySqlPipelineITCase.java | 95 ++++++++++++
.../connectors/mysql/utils/MySqlTypeUtilsTest.java | 172 +++++++++++++++++++++
.../src/test/resources/ddl/column_type_test.sql | 27 +++-
.../test/resources/ddl/column_type_test_mysql8.sql | 27 +++-
.../connectors/mysql/schema/MySqlTypeUtils.java | 26 ++++
8 files changed, 445 insertions(+), 3 deletions(-)
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/main/java/org/apache/flink/cdc/connectors/mysql/utils/MySqlTypeUtils.java
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/main/java/org/apache/flink/cdc/connectors/mysql/utils/MySqlTypeUtils.java
index 4cc8758efc..3324f9fa61 100644
---
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/main/java/org/apache/flink/cdc/connectors/mysql/utils/MySqlTypeUtils.java
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/main/java/org/apache/flink/cdc/connectors/mysql/utils/MySqlTypeUtils.java
@@ -39,44 +39,57 @@ public class MySqlTypeUtils {
private static final String BOOL = "BOOL";
private static final String TINYINT = "TINYINT";
private static final String TINYINT_UNSIGNED = "TINYINT UNSIGNED";
+ private static final String TINYINT_ZEROFILL = "TINYINT ZEROFILL";
private static final String TINYINT_UNSIGNED_ZEROFILL = "TINYINT UNSIGNED
ZEROFILL";
private static final String SMALLINT = "SMALLINT";
private static final String SMALLINT_UNSIGNED = "SMALLINT UNSIGNED";
+ private static final String SMALLINT_ZEROFILL = "SMALLINT ZEROFILL";
private static final String SMALLINT_UNSIGNED_ZEROFILL = "SMALLINT
UNSIGNED ZEROFILL";
private static final String MEDIUMINT = "MEDIUMINT";
private static final String MEDIUMINT_UNSIGNED = "MEDIUMINT UNSIGNED";
+ private static final String MEDIUMINT_ZEROFILL = "MEDIUMINT ZEROFILL";
private static final String MEDIUMINT_UNSIGNED_ZEROFILL = "MEDIUMINT
UNSIGNED ZEROFILL";
private static final String INT = "INT";
private static final String INT_UNSIGNED = "INT UNSIGNED";
+ private static final String INT_ZEROFILL = "INT ZEROFILL";
private static final String INT_UNSIGNED_ZEROFILL = "INT UNSIGNED
ZEROFILL";
private static final String INTEGER = "INTEGER";
private static final String INTEGER_UNSIGNED = "INTEGER UNSIGNED";
+ private static final String INTEGER_ZEROFILL = "INTEGER ZEROFILL";
private static final String INTEGER_UNSIGNED_ZEROFILL = "INTEGER UNSIGNED
ZEROFILL";
private static final String BIGINT = "BIGINT";
private static final String SERIAL = "SERIAL";
private static final String BIGINT_UNSIGNED = "BIGINT UNSIGNED";
+ private static final String BIGINT_ZEROFILL = "BIGINT ZEROFILL";
private static final String BIGINT_UNSIGNED_ZEROFILL = "BIGINT UNSIGNED
ZEROFILL";
private static final String REAL = "REAL";
private static final String REAL_UNSIGNED = "REAL UNSIGNED";
+ private static final String REAL_ZEROFILL = "REAL ZEROFILL";
private static final String REAL_UNSIGNED_ZEROFILL = "REAL UNSIGNED
ZEROFILL";
private static final String FLOAT = "FLOAT";
private static final String FLOAT_UNSIGNED = "FLOAT UNSIGNED";
+ private static final String FLOAT_ZEROFILL = "FLOAT ZEROFILL";
private static final String FLOAT_UNSIGNED_ZEROFILL = "FLOAT UNSIGNED
ZEROFILL";
private static final String DOUBLE = "DOUBLE";
private static final String DOUBLE_UNSIGNED = "DOUBLE UNSIGNED";
+ private static final String DOUBLE_ZEROFILL = "DOUBLE ZEROFILL";
private static final String DOUBLE_UNSIGNED_ZEROFILL = "DOUBLE UNSIGNED
ZEROFILL";
private static final String DOUBLE_PRECISION = "DOUBLE PRECISION";
private static final String DOUBLE_PRECISION_UNSIGNED = "DOUBLE PRECISION
UNSIGNED";
+ private static final String DOUBLE_PRECISION_ZEROFILL = "DOUBLE PRECISION
ZEROFILL";
private static final String DOUBLE_PRECISION_UNSIGNED_ZEROFILL =
"DOUBLE PRECISION UNSIGNED ZEROFILL";
private static final String NUMERIC = "NUMERIC";
private static final String NUMERIC_UNSIGNED = "NUMERIC UNSIGNED";
+ private static final String NUMERIC_ZEROFILL = "NUMERIC ZEROFILL";
private static final String NUMERIC_UNSIGNED_ZEROFILL = "NUMERIC UNSIGNED
ZEROFILL";
private static final String FIXED = "FIXED";
private static final String FIXED_UNSIGNED = "FIXED UNSIGNED";
+ private static final String FIXED_ZEROFILL = "FIXED ZEROFILL";
private static final String FIXED_UNSIGNED_ZEROFILL = "FIXED UNSIGNED
ZEROFILL";
private static final String DECIMAL = "DECIMAL";
private static final String DECIMAL_UNSIGNED = "DECIMAL UNSIGNED";
+ private static final String DECIMAL_ZEROFILL = "DECIMAL ZEROFILL";
private static final String DECIMAL_UNSIGNED_ZEROFILL = "DECIMAL UNSIGNED
ZEROFILL";
private static final String CHAR = "CHAR";
private static final String VARCHAR = "VARCHAR";
@@ -146,30 +159,37 @@ public class MySqlTypeUtils {
? DataTypes.BOOLEAN()
: DataTypes.TINYINT();
case TINYINT_UNSIGNED:
+ case TINYINT_ZEROFILL:
case TINYINT_UNSIGNED_ZEROFILL:
case SMALLINT:
return DataTypes.SMALLINT();
case SMALLINT_UNSIGNED:
+ case SMALLINT_ZEROFILL:
case SMALLINT_UNSIGNED_ZEROFILL:
case INT:
case INTEGER:
case MEDIUMINT:
case MEDIUMINT_UNSIGNED:
+ case MEDIUMINT_ZEROFILL:
case MEDIUMINT_UNSIGNED_ZEROFILL:
case YEAR:
return DataTypes.INT();
case INT_UNSIGNED:
+ case INT_ZEROFILL:
case INT_UNSIGNED_ZEROFILL:
case INTEGER_UNSIGNED:
+ case INTEGER_ZEROFILL:
case INTEGER_UNSIGNED_ZEROFILL:
case BIGINT:
return DataTypes.BIGINT();
case BIGINT_UNSIGNED:
+ case BIGINT_ZEROFILL:
case BIGINT_UNSIGNED_ZEROFILL:
case SERIAL:
return DataTypes.DECIMAL(20, 0);
case FLOAT:
case FLOAT_UNSIGNED:
+ case FLOAT_ZEROFILL:
case FLOAT_UNSIGNED_ZEROFILL:
if (column.length() != FLOAT_LENGTH_UNSPECIFIED_FLAG) {
// For FLOAT types with length provided explicitly, treat
it like DOUBLE
@@ -179,22 +199,28 @@ public class MySqlTypeUtils {
}
case REAL:
case REAL_UNSIGNED:
+ case REAL_ZEROFILL:
case REAL_UNSIGNED_ZEROFILL:
case DOUBLE:
case DOUBLE_UNSIGNED:
+ case DOUBLE_ZEROFILL:
case DOUBLE_UNSIGNED_ZEROFILL:
case DOUBLE_PRECISION:
case DOUBLE_PRECISION_UNSIGNED:
+ case DOUBLE_PRECISION_ZEROFILL:
case DOUBLE_PRECISION_UNSIGNED_ZEROFILL:
return DataTypes.DOUBLE();
case NUMERIC:
case NUMERIC_UNSIGNED:
+ case NUMERIC_ZEROFILL:
case NUMERIC_UNSIGNED_ZEROFILL:
case FIXED:
case FIXED_UNSIGNED:
+ case FIXED_ZEROFILL:
case FIXED_UNSIGNED_ZEROFILL:
case DECIMAL:
case DECIMAL_UNSIGNED:
+ case DECIMAL_ZEROFILL:
case DECIMAL_UNSIGNED_ZEROFILL:
return column.length() <= 38
? DataTypes.DECIMAL(column.length(),
column.scale().orElse(0))
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlFullTypesITCase.java
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlFullTypesITCase.java
index a0303f54d6..648325daee 100644
---
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlFullTypesITCase.java
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlFullTypesITCase.java
@@ -257,6 +257,77 @@ class MySqlFullTypesITCase extends MySqlSourceTestBase {
testMysqlPrecisionTypes(fullTypesMySql8Database);
}
+ @Test
+ void testMysql57ZerofillTypes() throws Throwable {
+ testZerofillTypes(fullTypesMySql57Database);
+ }
+
+ @Test
+ void testMysql8ZerofillTypes() throws Throwable {
+ testZerofillTypes(fullTypesMySql8Database);
+ }
+
+ private void testZerofillTypes(UniqueDatabase database) throws Exception {
+ database.createAndInitialize();
+ Boolean useLegacyJsonFormat = true;
+ CloseableIterator<Event> iterator =
+ env.fromSource(
+ getFlinkSourceProvider(
+ new String[]
{"zerofill_types"},
+ database,
+ useLegacyJsonFormat)
+ .getSource(),
+ WatermarkStrategy.noWatermarks(),
+ "Event-Source")
+ .executeAndCollect();
+
+ // skip CreateTableEvent
+ List<Event> snapshotResults =
+ MySqSourceTestUtils.fetchResultsAndCreateTableEvent(iterator,
1).f0;
+ RecordData snapshotRecord = ((DataChangeEvent)
snapshotResults.get(0)).after();
+
+ RowType recordType =
+ RowType.of(
+ DataTypes.DECIMAL(20, 0).notNull(),
+ // SHOW CREATE TABLE may add UNSIGNED for ZEROFILL
types, so use the mapped
+ // types
+ DataTypes.SMALLINT(), // tiny_z_c -> SMALLINT
+ DataTypes.INT(), // small_z_c -> INT
+ DataTypes.INT(), // medium_z_c -> INT
+ DataTypes.BIGINT(), // int_z_c -> BIGINT
+ DataTypes.BIGINT(), // integer_z_c -> BIGINT
+ DataTypes.DECIMAL(20, 0), // big_z_c -> DECIMAL(20,0)
+ DataTypes.DOUBLE(), // real_z_c
+ DataTypes.FLOAT(), // float_z_c
+ DataTypes.DOUBLE(), // double_z_c
+ DataTypes.DOUBLE(), // double_precision_z_c
+ DataTypes.DECIMAL(8, 3), // numeric_z_c
+ DataTypes.DECIMAL(8, 3), // fixed_z_c
+ DataTypes.DECIMAL(8, 3) // decimal_z_c
+ );
+
+ Object[] expectedSnapshot =
+ new Object[] {
+ DecimalData.fromBigDecimal(new BigDecimal("1"), 20, 0),
+ (short) 1,
+ 2,
+ 3,
+ 4L,
+ 5L,
+ DecimalData.fromBigDecimal(new BigDecimal("6"), 20, 0),
+ 7.5d,
+ 8.5f,
+ 9.5d,
+ 10.5d,
+ DecimalData.fromBigDecimal(new BigDecimal("11.5"), 8, 3),
+ DecimalData.fromBigDecimal(new BigDecimal("12.5"), 8, 3),
+ DecimalData.fromBigDecimal(new BigDecimal("13.5"), 8, 3)
+ };
+
+ Assertions.assertThat(RecordDataTestUtils.recordFields(snapshotRecord,
recordType))
+ .isEqualTo(expectedSnapshot);
+ }
+
void testMysqlPrecisionTypes(UniqueDatabase database) throws Throwable {
RowType recordType =
RowType.of(
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlMetadataAccessorITCase.java
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlMetadataAccessorITCase.java
index 59873291e1..fcbf1ffe31 100644
---
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlMetadataAccessorITCase.java
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlMetadataAccessorITCase.java
@@ -374,7 +374,9 @@ class MySqlMetadataAccessorITCase extends
MySqlSourceTestBase {
database.createAndInitialize();
String[] tables =
- new String[] {"common_types", "time_types", "precision_types",
"json_types"};
+ new String[] {
+ "common_types", "time_types", "precision_types",
"json_types", "zerofill_types"
+ };
MySqlMetadataAccessor metadataAccessor = getMetadataAccessor(tables,
database, true);
assertThatThrownBy(metadataAccessor::listNamespaces)
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlPipelineITCase.java
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlPipelineITCase.java
index a553036d56..5023b335e0 100644
---
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlPipelineITCase.java
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/source/MySqlPipelineITCase.java
@@ -990,6 +990,101 @@ class MySqlPipelineITCase extends MySqlSourceTestBase {
?
DataTypes.BOOLEAN()
:
DataTypes.TINYINT())))));
+ // Add ZEROFILL types without UNSIGNED
+ statement.execute(
+ String.format(
+ "ALTER TABLE `%s`.`products` ADD COLUMN
`z_tinyint` TINYINT ZEROFILL NULL;",
+ inventoryDatabase.getDatabaseName()));
+ expected.add(
+ new AddColumnEvent(
+ tableId,
+ Collections.singletonList(
+ new AddColumnEvent.ColumnWithPosition(
+ Column.physicalColumn(
+ "z_tinyint",
DataTypes.SMALLINT())))));
+
+ statement.execute(
+ String.format(
+ "ALTER TABLE `%s`.`products` ADD COLUMN
`z_smallint` SMALLINT ZEROFILL NULL;",
+ inventoryDatabase.getDatabaseName()));
+ expected.add(
+ new AddColumnEvent(
+ tableId,
+ Collections.singletonList(
+ new AddColumnEvent.ColumnWithPosition(
+ Column.physicalColumn(
+ "z_smallint",
DataTypes.INT())))));
+
+ statement.execute(
+ String.format(
+ "ALTER TABLE `%s`.`products` ADD COLUMN
`z_mediumint` MEDIUMINT ZEROFILL NULL;",
+ inventoryDatabase.getDatabaseName()));
+ expected.add(
+ new AddColumnEvent(
+ tableId,
+ Collections.singletonList(
+ new AddColumnEvent.ColumnWithPosition(
+ Column.physicalColumn(
+ "z_mediumint",
DataTypes.INT())))));
+
+ statement.execute(
+ String.format(
+ "ALTER TABLE `%s`.`products` ADD COLUMN `z_int`
INT ZEROFILL NULL;",
+ inventoryDatabase.getDatabaseName()));
+ expected.add(
+ new AddColumnEvent(
+ tableId,
+ Collections.singletonList(
+ new AddColumnEvent.ColumnWithPosition(
+ Column.physicalColumn("z_int",
DataTypes.BIGINT())))));
+
+ statement.execute(
+ String.format(
+ "ALTER TABLE `%s`.`products` ADD COLUMN `z_bigint`
BIGINT ZEROFILL NULL;",
+ inventoryDatabase.getDatabaseName()));
+ expected.add(
+ new AddColumnEvent(
+ tableId,
+ Collections.singletonList(
+ new AddColumnEvent.ColumnWithPosition(
+ Column.physicalColumn(
+ "z_bigint",
DataTypes.DECIMAL(20, 0))))));
+
+ statement.execute(
+ String.format(
+ "ALTER TABLE `%s`.`products` ADD COLUMN `z_float`
FLOAT ZEROFILL NULL;",
+ inventoryDatabase.getDatabaseName()));
+ expected.add(
+ new AddColumnEvent(
+ tableId,
+ Collections.singletonList(
+ new AddColumnEvent.ColumnWithPosition(
+ Column.physicalColumn("z_float",
DataTypes.FLOAT())))));
+
+ statement.execute(
+ String.format(
+ "ALTER TABLE `%s`.`products` ADD COLUMN `z_double`
DOUBLE ZEROFILL NULL;",
+ inventoryDatabase.getDatabaseName()));
+ expected.add(
+ new AddColumnEvent(
+ tableId,
+ Collections.singletonList(
+ new AddColumnEvent.ColumnWithPosition(
+ Column.physicalColumn(
+ "z_double",
DataTypes.DOUBLE())))));
+
+ statement.execute(
+ String.format(
+ "ALTER TABLE `%s`.`products` ADD COLUMN
`z_decimal` DECIMAL(8, 4) ZEROFILL NULL;",
+ inventoryDatabase.getDatabaseName()));
+ expected.add(
+ new AddColumnEvent(
+ tableId,
+ Collections.singletonList(
+ new AddColumnEvent.ColumnWithPosition(
+ Column.physicalColumn(
+ "z_decimal",
DataTypes.DECIMAL(8, 4))))));
+
// Drop orders table first to remove foreign key restraints
statement.execute(
String.format(
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/utils/MySqlTypeUtilsTest.java
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/utils/MySqlTypeUtilsTest.java
new file mode 100644
index 0000000000..9ce016fabb
--- /dev/null
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/java/org/apache/flink/cdc/connectors/mysql/utils/MySqlTypeUtilsTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.flink.cdc.connectors.mysql.utils;
+
+import org.apache.flink.cdc.common.types.DataType;
+import org.apache.flink.cdc.common.types.DataTypes;
+
+import io.debezium.relational.Column;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+/** Unit tests for {@link MySqlTypeUtils}. */
+public class MySqlTypeUtilsTest {
+ private static final boolean TINY_INT_1_IS_BIT = true;
+
+ /** Build a nullable column with the given MySQL type name and default
length/scale. */
+ private static Column column(String typeName) {
+ return column(typeName, 10, 0);
+ }
+
+ /** Build a nullable column with the given MySQL type name, length and
scale. */
+ private static Column column(String typeName, int length, int scale) {
+ return Column.editor()
+ .name("test_col")
+ .type(typeName)
+ .length(length)
+ .scale(scale)
+ .optional(true)
+ .create();
+ }
+
+ private static DataType fromColumn(String typeName) {
+ return MySqlTypeUtils.fromDbzColumn(column(typeName),
TINY_INT_1_IS_BIT);
+ }
+
+ private static DataType fromColumn(String typeName, int length, int scale)
{
+ return MySqlTypeUtils.fromDbzColumn(column(typeName, length, scale),
TINY_INT_1_IS_BIT);
+ }
+
+ // ---- ZEROFILL (without UNSIGNED) should map the same as UNSIGNED
ZEROFILL ----
+
+ @Test
+ public void testTinyIntZerofill() {
+ assertThat(fromColumn("TINYINT
ZEROFILL")).isEqualTo(DataTypes.SMALLINT());
+ }
+
+ @Test
+ public void testSmallIntZerofill() {
+ assertThat(fromColumn("SMALLINT ZEROFILL")).isEqualTo(DataTypes.INT());
+ }
+
+ @Test
+ public void testMediumIntZerofill() {
+ assertThat(fromColumn("MEDIUMINT
ZEROFILL")).isEqualTo(DataTypes.INT());
+ }
+
+ @Test
+ public void testIntZerofill() {
+ // This is the type that triggered the original
UnsupportedOperationException.
+ assertThat(fromColumn("INT ZEROFILL")).isEqualTo(DataTypes.BIGINT());
+ }
+
+ @Test
+ public void testIntegerZerofill() {
+ assertThat(fromColumn("INTEGER
ZEROFILL")).isEqualTo(DataTypes.BIGINT());
+ }
+
+ @Test
+ public void testBigIntZerofill() {
+ assertThat(fromColumn("BIGINT
ZEROFILL")).isEqualTo(DataTypes.DECIMAL(20, 0));
+ }
+
+ @Test
+ public void testRealZerofill() {
+ assertThat(fromColumn("REAL ZEROFILL")).isEqualTo(DataTypes.DOUBLE());
+ }
+
+ @Test
+ public void testFloatZerofillUnspecifiedLength() {
+ // FLOAT without explicit length -> FLOAT
+ Column col =
+ Column.editor()
+ .name("test_col")
+ .type("FLOAT ZEROFILL")
+ .length(-1)
+ .optional(true)
+ .create();
+ assertThat(MySqlTypeUtils.fromDbzColumn(col, TINY_INT_1_IS_BIT))
+ .isEqualTo(DataTypes.FLOAT());
+ }
+
+ @Test
+ public void testFloatZerofillWithLength() {
+ // FLOAT with explicit length -> treated as DOUBLE
+ assertThat(fromColumn("FLOAT ZEROFILL", 10,
2)).isEqualTo(DataTypes.DOUBLE());
+ }
+
+ @Test
+ public void testDoubleZerofill() {
+ assertThat(fromColumn("DOUBLE
ZEROFILL")).isEqualTo(DataTypes.DOUBLE());
+ }
+
+ @Test
+ public void testDoublePrecisionZerofill() {
+ assertThat(fromColumn("DOUBLE PRECISION
ZEROFILL")).isEqualTo(DataTypes.DOUBLE());
+ }
+
+ @Test
+ public void testNumericZerofill() {
+ assertThat(fromColumn("NUMERIC ZEROFILL", 10,
0)).isEqualTo(DataTypes.DECIMAL(10, 0));
+ }
+
+ @Test
+ public void testFixedZerofill() {
+ assertThat(fromColumn("FIXED ZEROFILL", 10,
2)).isEqualTo(DataTypes.DECIMAL(10, 2));
+ }
+
+ @Test
+ public void testDecimalZerofill() {
+ assertThat(fromColumn("DECIMAL ZEROFILL", 10,
2)).isEqualTo(DataTypes.DECIMAL(10, 2));
+ }
+
+ // ---- ZEROFILL maps identically to UNSIGNED ZEROFILL ----
+
+ @Test
+ public void testIntZerofillEqualsIntUnsignedZerofill() {
+ assertThat(fromColumn("INT ZEROFILL")).isEqualTo(fromColumn("INT
UNSIGNED ZEROFILL"));
+ }
+
+ @Test
+ public void testBigIntZerofillEqualsBigIntUnsignedZerofill() {
+ assertThat(fromColumn("BIGINT ZEROFILL")).isEqualTo(fromColumn("BIGINT
UNSIGNED ZEROFILL"));
+ }
+
+ @Test
+ public void testDecimalZerofillEqualsDecimalUnsignedZerofill() {
+ assertThat(fromColumn("DECIMAL ZEROFILL", 10, 2))
+ .isEqualTo(fromColumn("DECIMAL UNSIGNED ZEROFILL", 10, 2));
+ }
+
+ // ---- Non-null column should produce NOT NULL type ----
+
+ @Test
+ public void testIntZerofillNotNull() {
+ Column col =
+ Column.editor()
+ .name("test_col")
+ .type("INT ZEROFILL")
+ .length(10)
+ .scale(0)
+ .optional(false)
+ .create();
+ assertThat(MySqlTypeUtils.fromDbzColumn(col, TINY_INT_1_IS_BIT))
+ .isEqualTo(DataTypes.BIGINT().notNull());
+ }
+}
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/resources/ddl/column_type_test.sql
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/resources/ddl/column_type_test.sql
index 7f408d7fb0..61ed8899ef 100644
---
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/resources/ddl/column_type_test.sql
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/resources/ddl/column_type_test.sql
@@ -202,4 +202,29 @@ VALUES (DEFAULT,
'{"key1":"value1","key2":"value2"}',
'[{"key1":"value1","key2":{"key2_1":"value2_1","key2_2":"value2_2"},"key3":["value3"],"key4":["value4_1","value4_2"]},{"key5":"value5"}]',
1
- );
\ No newline at end of file
+ );
+
+-- All ZEROFILL types without the UNSIGNED keyword. MySQL binlog preserves the
original DDL
+-- text, so these type names reach MySqlTypeUtils as e.g. "INT ZEROFILL"
during schema change
+-- parsing.
+CREATE TABLE zerofill_types
+(
+ id SERIAL,
+ tiny_z_c TINYINT ZEROFILL,
+ small_z_c SMALLINT ZEROFILL,
+ medium_z_c MEDIUMINT ZEROFILL,
+ int_z_c INT ZEROFILL,
+ integer_z_c INTEGER ZEROFILL,
+ big_z_c BIGINT ZEROFILL,
+ real_z_c REAL ZEROFILL,
+ float_z_c FLOAT ZEROFILL,
+ double_z_c DOUBLE ZEROFILL,
+ double_precision_z_c DOUBLE PRECISION ZEROFILL,
+ numeric_z_c NUMERIC(8, 3) ZEROFILL,
+ fixed_z_c FIXED(8, 3) ZEROFILL,
+ decimal_z_c DECIMAL(8, 3) ZEROFILL,
+ PRIMARY KEY (id)
+) DEFAULT CHARSET=utf8;
+
+INSERT INTO zerofill_types
+VALUES (DEFAULT, 1, 2, 3, 4, 5, 6, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5);
\ No newline at end of file
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/resources/ddl/column_type_test_mysql8.sql
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/resources/ddl/column_type_test_mysql8.sql
index 836296a720..e02c24dd27 100644
---
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/resources/ddl/column_type_test_mysql8.sql
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-mysql/src/test/resources/ddl/column_type_test_mysql8.sql
@@ -207,4 +207,29 @@ VALUES (DEFAULT,
'{"key1":"value1","key2":"value2"}',
'[{"key1":"value1","key2":{"key2_1":"value2_1","key2_2":"value2_2"},"key3":["value3"],"key4":["value4_1","value4_2"]},{"key5":"value5"}]',
1
- );
\ No newline at end of file
+ );
+
+-- All ZEROFILL types without the UNSIGNED keyword. MySQL binlog preserves the
original DDL
+-- text, so these type names reach MySqlTypeUtils as e.g. "INT ZEROFILL"
during schema change
+-- parsing.
+CREATE TABLE zerofill_types
+(
+ id SERIAL,
+ tiny_z_c TINYINT ZEROFILL,
+ small_z_c SMALLINT ZEROFILL,
+ medium_z_c MEDIUMINT ZEROFILL,
+ int_z_c INT ZEROFILL,
+ integer_z_c INTEGER ZEROFILL,
+ big_z_c BIGINT ZEROFILL,
+ real_z_c REAL ZEROFILL,
+ float_z_c FLOAT ZEROFILL,
+ double_z_c DOUBLE ZEROFILL,
+ double_precision_z_c DOUBLE PRECISION ZEROFILL,
+ numeric_z_c NUMERIC(8, 3) ZEROFILL,
+ fixed_z_c FIXED(8, 3) ZEROFILL,
+ decimal_z_c DECIMAL(8, 3) ZEROFILL,
+ PRIMARY KEY (id)
+) DEFAULT CHARSET=utf8;
+
+INSERT INTO zerofill_types
+VALUES (DEFAULT, 1, 2, 3, 4, 5, 6, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5);
\ No newline at end of file
diff --git
a/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-mysql-cdc/src/main/java/org/apache/flink/cdc/connectors/mysql/schema/MySqlTypeUtils.java
b/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-mysql-cdc/src/main/java/org/apache/flink/cdc/connectors/mysql/schema/MySqlTypeUtils.java
index 3a406ff9b0..960ce3a558 100644
---
a/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-mysql-cdc/src/main/java/org/apache/flink/cdc/connectors/mysql/schema/MySqlTypeUtils.java
+++
b/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-mysql-cdc/src/main/java/org/apache/flink/cdc/connectors/mysql/schema/MySqlTypeUtils.java
@@ -36,44 +36,57 @@ public class MySqlTypeUtils {
private static final String BOOL = "BOOL";
private static final String TINYINT = "TINYINT";
private static final String TINYINT_UNSIGNED = "TINYINT UNSIGNED";
+ private static final String TINYINT_ZEROFILL = "TINYINT ZEROFILL";
private static final String TINYINT_UNSIGNED_ZEROFILL = "TINYINT UNSIGNED
ZEROFILL";
private static final String SMALLINT = "SMALLINT";
private static final String SMALLINT_UNSIGNED = "SMALLINT UNSIGNED";
+ private static final String SMALLINT_ZEROFILL = "SMALLINT ZEROFILL";
private static final String SMALLINT_UNSIGNED_ZEROFILL = "SMALLINT
UNSIGNED ZEROFILL";
private static final String MEDIUMINT = "MEDIUMINT";
private static final String MEDIUMINT_UNSIGNED = "MEDIUMINT UNSIGNED";
+ private static final String MEDIUMINT_ZEROFILL = "MEDIUMINT ZEROFILL";
private static final String MEDIUMINT_UNSIGNED_ZEROFILL = "MEDIUMINT
UNSIGNED ZEROFILL";
private static final String INT = "INT";
private static final String INT_UNSIGNED = "INT UNSIGNED";
+ private static final String INT_ZEROFILL = "INT ZEROFILL";
private static final String INT_UNSIGNED_ZEROFILL = "INT UNSIGNED
ZEROFILL";
private static final String INTEGER = "INTEGER";
private static final String INTEGER_UNSIGNED = "INTEGER UNSIGNED";
+ private static final String INTEGER_ZEROFILL = "INTEGER ZEROFILL";
private static final String INTEGER_UNSIGNED_ZEROFILL = "INTEGER UNSIGNED
ZEROFILL";
private static final String BIGINT = "BIGINT";
private static final String SERIAL = "SERIAL";
private static final String BIGINT_UNSIGNED = "BIGINT UNSIGNED";
+ private static final String BIGINT_ZEROFILL = "BIGINT ZEROFILL";
private static final String BIGINT_UNSIGNED_ZEROFILL = "BIGINT UNSIGNED
ZEROFILL";
private static final String REAL = "REAL";
private static final String REAL_UNSIGNED = "REAL UNSIGNED";
+ private static final String REAL_ZEROFILL = "REAL ZEROFILL";
private static final String REAL_UNSIGNED_ZEROFILL = "REAL UNSIGNED
ZEROFILL";
private static final String FLOAT = "FLOAT";
private static final String FLOAT_UNSIGNED = "FLOAT UNSIGNED";
+ private static final String FLOAT_ZEROFILL = "FLOAT ZEROFILL";
private static final String FLOAT_UNSIGNED_ZEROFILL = "FLOAT UNSIGNED
ZEROFILL";
private static final String DOUBLE = "DOUBLE";
private static final String DOUBLE_UNSIGNED = "DOUBLE UNSIGNED";
+ private static final String DOUBLE_ZEROFILL = "DOUBLE ZEROFILL";
private static final String DOUBLE_UNSIGNED_ZEROFILL = "DOUBLE UNSIGNED
ZEROFILL";
private static final String DOUBLE_PRECISION = "DOUBLE PRECISION";
private static final String DOUBLE_PRECISION_UNSIGNED = "DOUBLE PRECISION
UNSIGNED";
+ private static final String DOUBLE_PRECISION_ZEROFILL = "DOUBLE PRECISION
ZEROFILL";
private static final String DOUBLE_PRECISION_UNSIGNED_ZEROFILL =
"DOUBLE PRECISION UNSIGNED ZEROFILL";
private static final String NUMERIC = "NUMERIC";
private static final String NUMERIC_UNSIGNED = "NUMERIC UNSIGNED";
+ private static final String NUMERIC_ZEROFILL = "NUMERIC ZEROFILL";
private static final String NUMERIC_UNSIGNED_ZEROFILL = "NUMERIC UNSIGNED
ZEROFILL";
private static final String FIXED = "FIXED";
private static final String FIXED_UNSIGNED = "FIXED UNSIGNED";
+ private static final String FIXED_ZEROFILL = "FIXED ZEROFILL";
private static final String FIXED_UNSIGNED_ZEROFILL = "FIXED UNSIGNED
ZEROFILL";
private static final String DECIMAL = "DECIMAL";
private static final String DECIMAL_UNSIGNED = "DECIMAL UNSIGNED";
+ private static final String DECIMAL_ZEROFILL = "DECIMAL ZEROFILL";
private static final String DECIMAL_UNSIGNED_ZEROFILL = "DECIMAL UNSIGNED
ZEROFILL";
private static final String CHAR = "CHAR";
private static final String VARCHAR = "VARCHAR";
@@ -139,10 +152,12 @@ public class MySqlTypeUtils {
? DataTypes.BOOLEAN()
: DataTypes.TINYINT();
case TINYINT_UNSIGNED:
+ case TINYINT_ZEROFILL:
case TINYINT_UNSIGNED_ZEROFILL:
case SMALLINT:
return DataTypes.SMALLINT();
case SMALLINT_UNSIGNED:
+ case SMALLINT_ZEROFILL:
case SMALLINT_UNSIGNED_ZEROFILL:
case INT:
case INTEGER:
@@ -151,38 +166,49 @@ public class MySqlTypeUtils {
return DataTypes.INT();
case INT_UNSIGNED:
case INT_UNSIGNED_ZEROFILL:
+ case INT_ZEROFILL:
case INTEGER_UNSIGNED:
+ case INTEGER_ZEROFILL:
case INTEGER_UNSIGNED_ZEROFILL:
case MEDIUMINT_UNSIGNED:
+ case MEDIUMINT_ZEROFILL:
case MEDIUMINT_UNSIGNED_ZEROFILL:
case BIGINT:
return DataTypes.BIGINT();
case BIGINT_UNSIGNED:
+ case BIGINT_ZEROFILL:
case BIGINT_UNSIGNED_ZEROFILL:
case SERIAL:
return DataTypes.DECIMAL(20, 0);
case FLOAT:
case FLOAT_UNSIGNED:
+ case FLOAT_ZEROFILL:
case FLOAT_UNSIGNED_ZEROFILL:
return DataTypes.FLOAT();
case REAL:
case REAL_UNSIGNED:
+ case REAL_ZEROFILL:
case REAL_UNSIGNED_ZEROFILL:
case DOUBLE:
case DOUBLE_UNSIGNED:
+ case DOUBLE_ZEROFILL:
case DOUBLE_UNSIGNED_ZEROFILL:
case DOUBLE_PRECISION:
case DOUBLE_PRECISION_UNSIGNED:
+ case DOUBLE_PRECISION_ZEROFILL:
case DOUBLE_PRECISION_UNSIGNED_ZEROFILL:
return DataTypes.DOUBLE();
case NUMERIC:
case NUMERIC_UNSIGNED:
+ case NUMERIC_ZEROFILL:
case NUMERIC_UNSIGNED_ZEROFILL:
case FIXED:
case FIXED_UNSIGNED:
+ case FIXED_ZEROFILL:
case FIXED_UNSIGNED_ZEROFILL:
case DECIMAL:
case DECIMAL_UNSIGNED:
+ case DECIMAL_ZEROFILL:
case DECIMAL_UNSIGNED_ZEROFILL:
return column.length() <= 38
? DataTypes.DECIMAL(column.length(),
column.scale().orElse(0))