This is an automated email from the ASF dual-hosted git repository.
diqiu50 pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new 61b113fb20 [Cherry-pick to branch-1.3] [#13032]
fix(catalog-jdbc-mysql): Preserve full type declaration for lossy MySQL column
types (#13036) (#13056)
61b113fb20 is described below
commit 61b113fb201c50794ab1d2c0d17c3b8b2bceaa7f
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Sep 10 11:51:15 2026 +0800
[Cherry-pick to branch-1.3] [#13032] fix(catalog-jdbc-mysql): Preserve full
type declaration for lossy MySQL column types (#13036) (#13056)
**Cherry-pick Information:**
- Original commit: ddf9fc7bd083ffd31b4d8c1773d901b9df93fadd
- Target branch: `branch-1.3`
- Status: ✅ Clean cherry-pick (no conflicts)
Co-authored-by: Yuhui <[email protected]>
---
.../apache/gravitino/catalog/jdbc/JdbcTable.java | 8 ++
.../mysql/operation/MysqlTableOperations.java | 100 +++++++++++++++++++++
.../mysql/integration/test/CatalogMysqlIT.java | 18 +++-
.../mysql/operation/TestMysqlTableOperations.java | 53 +++++++++++
.../catalog/jdbc/mysql/MySQLExternalDataType.java | 12 ++-
.../jdbc/mysql/TestMySQLDataTypeTransformer.java | 22 +++++
6 files changed, 211 insertions(+), 2 deletions(-)
diff --git
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcTable.java
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcTable.java
index f1008f2d03..477cc17296 100644
---
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcTable.java
+++
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcTable.java
@@ -25,6 +25,7 @@ import org.apache.commons.lang3.ArrayUtils;
import org.apache.gravitino.catalog.jdbc.operation.TableOperation;
import org.apache.gravitino.connector.BaseTable;
import org.apache.gravitino.connector.TableOperations;
+import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.SupportsPartitions;
/** Represents a Jdbc Table entity in the jdbc table. */
@@ -113,6 +114,13 @@ public class JdbcTable extends BaseTable {
public Map<String, String> properties() {
return properties;
}
+
+ /**
+ * @return The columns currently set on this builder.
+ */
+ public Column[] columns() {
+ return columns;
+ }
}
/**
diff --git
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
index f30ef31f4d..813f44b6de 100644
---
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
+++
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
@@ -36,6 +36,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
@@ -55,6 +57,7 @@ import
org.apache.gravitino.rel.expressions.distributions.Distributions;
import org.apache.gravitino.rel.expressions.transforms.Transform;
import org.apache.gravitino.rel.indexes.Index;
import org.apache.gravitino.rel.indexes.Indexes;
+import org.apache.gravitino.rel.types.Type;
import org.apache.gravitino.rel.types.Types;
/** Table operations for MySQL. */
@@ -65,6 +68,14 @@ public class MysqlTableOperations extends
JdbcTableOperations {
private static final String MYSQL_NOT_SUPPORT_NESTED_COLUMN_MSG =
"Mysql does not support nested column names.";
+ // MySQL's JDBC driver reduces these types to a bare name and/or an unusable
COLUMN_SIZE via
+ // DatabaseMetaData.getColumns, so the JdbcTypeConverter maps them to
Types.BinaryType or a
+ // bare Types.ExternalType, losing the length/enumerated values. See
correctLossyTypeColumns.
+ private static final String VARBINARY = "varbinary";
+ private static final String ENUM = "enum";
+ private static final String SET = "set";
+ private static final Pattern LENGTH_PATTERN =
Pattern.compile("\\((\\d+)\\)");
+
@Override
protected String generateCreateTableSql(
String tableName,
@@ -203,6 +214,95 @@ public class MysqlTableOperations extends
JdbcTableOperations {
tableBuilder.withComment(
tableBuilder.properties().getOrDefault(COMMENT,
tableBuilder.comment()));
}
+ correctLossyTypeColumns(connection, databaseName, tableName, tableBuilder);
+ }
+
+ /**
+ * VARBINARY/ENUM/SET fall back to a bare Types.ExternalType, and BIT/BINARY
of any width both
+ * collapse to plain Types.BinaryType, because JdbcTypeConverter only sees
TYPE_NAME/COLUMN_SIZE.
+ * If any column needs it, this queries information_schema.columns once for
the whole table to
+ * recover the full declaration (e.g. "enum('a','b','c')", "binary(16)") and
fixes those columns.
+ */
+ private static void correctLossyTypeColumns(
+ Connection connection, String databaseName, String tableName,
JdbcTable.Builder tableBuilder)
+ throws SQLException {
+ Column[] columns = tableBuilder.columns();
+ if (ArrayUtils.isEmpty(columns)
+ ||
Arrays.stream(columns).noneMatch(MysqlTableOperations::isLossyTypeCandidate)) {
+ return;
+ }
+
+ Map<String, String> fullTypesByColumn =
+ fetchColumnFullTypes(connection, databaseName, tableName);
+ JdbcColumn[] correctedColumns = new JdbcColumn[columns.length];
+ for (int i = 0; i < columns.length; i++) {
+ JdbcColumn column = (JdbcColumn) columns[i];
+ correctedColumns[i] =
+ isLossyTypeCandidate(column)
+ ? correctColumnType(column, fullTypesByColumn.get(column.name()))
+ : column;
+ }
+ tableBuilder.withColumns(correctedColumns);
+ }
+
+ private static boolean isLossyTypeCandidate(Column column) {
+ Type type = column.dataType();
+ if (type instanceof Types.BinaryType) {
+ return true;
+ }
+ if (type instanceof Types.ExternalType) {
+ String catalogString = ((Types.ExternalType) type).catalogString();
+ return VARBINARY.equalsIgnoreCase(catalogString)
+ || ENUM.equalsIgnoreCase(catalogString)
+ || SET.equalsIgnoreCase(catalogString);
+ }
+ return false;
+ }
+
+ private static JdbcColumn correctColumnType(JdbcColumn column, String
fullType) {
+ if (StringUtils.isEmpty(fullType)) {
+ return column;
+ }
+ // A plain BINARY(1) (or unspecified length) already round-trips to
Types.BinaryType as-is.
+ if (column.dataType() instanceof Types.BinaryType && parseLength(fullType)
<= 1) {
+ return column;
+ }
+ return JdbcColumn.builder()
+ .withName(column.name())
+ .withType(Types.ExternalType.of(fullType))
+ .withComment(column.comment())
+ .withNullable(column.nullable())
+ .withAutoIncrement(column.autoIncrement())
+ .withDefaultValue(column.defaultValue())
+ .build();
+ }
+
+ private static int parseLength(String fullType) {
+ Matcher matcher = LENGTH_PATTERN.matcher(fullType);
+ return matcher.find() ? Integer.parseInt(matcher.group(1)) : 1;
+ }
+
+ private static Map<String, String> fetchColumnFullTypes(
+ Connection connection, String databaseName, String tableName) throws
SQLException {
+ Map<String, String> columnTypes = new HashMap<>();
+ // TABLE_SCHEMA/TABLE_NAME comparisons in information_schema use a
case-insensitive collation,
+ // so with lower_case_table_names=0 a schema holding both "a_b" and "A_B"
would return rows for
+ // both; re-check TABLE_NAME exactly, matching the pattern used in
getColumnBuilder.
+ String query =
+ "SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE FROM
information_schema.columns "
+ + "WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?";
+ try (PreparedStatement statement = connection.prepareStatement(query)) {
+ statement.setString(1, databaseName);
+ statement.setString(2, tableName);
+ try (ResultSet resultSet = statement.executeQuery()) {
+ while (resultSet.next()) {
+ if (Objects.equals(resultSet.getString("TABLE_NAME"), tableName)) {
+ columnTypes.put(resultSet.getString("COLUMN_NAME"),
resultSet.getString("COLUMN_TYPE"));
+ }
+ }
+ }
+ }
+ return columnTypes;
}
@Override
diff --git
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
index 0aba67f646..d496c5f2dd 100644
---
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
+++
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
@@ -714,6 +714,10 @@ public class CatalogMysqlIT extends BaseIT {
+ " varchar20_col varchar(20),\n"
+ " text_col text,\n"
+ " binary_col binary,\n"
+ + " binary_col_16 binary(16),\n"
+ + " varbinary_col varbinary(100),\n"
+ + " enum_col enum('a','b','c'),\n"
+ + " set_col set('x','y','z'),\n"
+ " blob_col blob,\n"
+ " bit_col_8 bit(8),\n"
+ " bit_col bit\n"
@@ -797,8 +801,20 @@ public class CatalogMysqlIT extends BaseIT {
case "binary_col":
Assertions.assertEquals(Types.BinaryType.get(), column.dataType());
break;
+ case "binary_col_16":
+ Assertions.assertEquals(Types.ExternalType.of("binary(16)"),
column.dataType());
+ break;
+ case "varbinary_col":
+ Assertions.assertEquals(Types.ExternalType.of("varbinary(100)"),
column.dataType());
+ break;
+ case "enum_col":
+ Assertions.assertEquals(Types.ExternalType.of("enum('a','b','c')"),
column.dataType());
+ break;
+ case "set_col":
+ Assertions.assertEquals(Types.ExternalType.of("set('x','y','z')"),
column.dataType());
+ break;
case "bit_col_8":
- Assertions.assertEquals(Types.BinaryType.get(), column.dataType());
+ Assertions.assertEquals(Types.ExternalType.of("bit(8)"),
column.dataType());
break;
case "bit_col":
Assertions.assertEquals(Types.BooleanType.get(), column.dataType());
diff --git
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
index eb47047219..f4cd86e9e6 100644
---
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
+++
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperations.java
@@ -21,6 +21,8 @@ package org.apache.gravitino.catalog.mysql.operation;
import static
org.apache.gravitino.catalog.mysql.MysqlTablePropertiesMetadata.MYSQL_AUTO_INCREMENT_OFFSET_KEY;
import static
org.apache.gravitino.catalog.mysql.MysqlTablePropertiesMetadata.MYSQL_ENGINE_KEY;
+import java.sql.Connection;
+import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
@@ -29,10 +31,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import javax.sql.DataSource;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.catalog.jdbc.JdbcColumn;
import org.apache.gravitino.catalog.jdbc.JdbcTable;
+import org.apache.gravitino.catalog.jdbc.utils.DataSourceUtils;
import org.apache.gravitino.exceptions.GravitinoRuntimeException;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.TableChange;
@@ -1139,6 +1143,55 @@ public class TestMysqlTableOperations extends TestMysql {
"Non-datetime type should return 0 precision");
}
+ @Test
+ public void testLoadColumnsWithLossyTypeDeclarations() throws Exception {
+ // These types either drop their parameters (VARBINARY/ENUM/SET) or
collapse distinct widths
+ // into the same Gravitino type (BIT/BINARY) when only JDBC's
TYPE_NAME/COLUMN_SIZE metadata
+ // is used.
+ String tableName = RandomStringUtils.randomAlphabetic(16) +
"_lossy_type_table";
+ DataSource dataSource =
DataSourceUtils.createDataSource(getMySQLCatalogProperties());
+ try {
+ try (Connection connection = dataSource.getConnection()) {
+ connection.setCatalog(TEST_DB_NAME.toString());
+ try (Statement statement = connection.createStatement()) {
+ statement.execute(
+ String.format(
+ "CREATE TABLE `%s` ("
+ + "c_int INT, "
+ + "c_varbinary VARBINARY(100), "
+ + "c_enum ENUM('a','b','c'), "
+ + "c_set SET('x','y','z'), "
+ + "c_bit BIT(8), "
+ + "c_bit1 BIT(1), "
+ + "c_binary1 BINARY(1), "
+ + "c_binary BINARY(16))",
+ tableName));
+ }
+ }
+ } finally {
+ DataSourceUtils.closeDataSource(dataSource);
+ }
+
+ JdbcTable table = TABLE_OPERATIONS.load(TEST_DB_NAME.toString(),
tableName);
+ Map<String, Type> typeByColumn =
+ Arrays.stream(table.columns()).collect(Collectors.toMap(Column::name,
Column::dataType));
+
+ // A plain, non-candidate column must be left untouched by the correction.
+ Assertions.assertEquals(Types.IntegerType.get(),
typeByColumn.get("c_int"));
+ Assertions.assertEquals(
+ Types.ExternalType.of("varbinary(100)"),
typeByColumn.get("c_varbinary"));
+ Assertions.assertEquals(Types.ExternalType.of("enum('a','b','c')"),
typeByColumn.get("c_enum"));
+ Assertions.assertEquals(Types.ExternalType.of("set('x','y','z')"),
typeByColumn.get("c_set"));
+ Assertions.assertEquals(Types.ExternalType.of("bit(8)"),
typeByColumn.get("c_bit"));
+ Assertions.assertEquals(Types.ExternalType.of("binary(16)"),
typeByColumn.get("c_binary"));
+ // BIT(1) still maps to boolean, and a default-width BINARY(1) already
round-trips correctly
+ // as Types.BinaryType, so neither must be affected by this fix.
+ Assertions.assertEquals(Types.BooleanType.get(),
typeByColumn.get("c_bit1"));
+ Assertions.assertEquals(Types.BinaryType.get(),
typeByColumn.get("c_binary1"));
+
+ TABLE_OPERATIONS.drop(TEST_DB_NAME.toString(), tableName);
+ }
+
@Test
public void testCalculateDatetimePrecisionWithUnsupportedDriverVersion() {
MysqlTableOperations operationsWithOldDriver =
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/MySQLExternalDataType.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/MySQLExternalDataType.java
index 03f4661689..916f50ca7f 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/MySQLExternalDataType.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/MySQLExternalDataType.java
@@ -49,6 +49,8 @@ public enum MySQLExternalDataType {
SET("set", VarcharType.VARCHAR),
JSON("json", JSON_TYPE),
VARBINARY("varbinary", VarbinaryType.VARBINARY),
+ BINARY("binary", VarbinaryType.VARBINARY),
+ BIT("bit", VarbinaryType.VARBINARY),
TINYBLOB("tinyblob", VarbinaryType.VARBINARY),
BLOB("blob", VarbinaryType.VARBINARY),
MEDIUMBLOB("mediumblob", VarbinaryType.VARBINARY),
@@ -84,11 +86,19 @@ public enum MySQLExternalDataType {
}
public static MySQLExternalDataType safeValueOf(String mysqlTypeName) {
+ // catalogString may carry type parameters, e.g. "varbinary(100)" or
"enum('a','b','c')", so
+ // match on the bare type name.
+ String baseTypeName = stripTypeParameters(mysqlTypeName);
for (MySQLExternalDataType mySQLExternalDataType :
MySQLExternalDataType.values()) {
- if (mySQLExternalDataType.mysqlTypeName.equalsIgnoreCase(mysqlTypeName))
{
+ if (mySQLExternalDataType.mysqlTypeName.equalsIgnoreCase(baseTypeName)) {
return mySQLExternalDataType;
}
}
return UNKNOWN;
}
+
+ private static String stripTypeParameters(String mysqlTypeName) {
+ int parenIndex = mysqlTypeName.indexOf('(');
+ return parenIndex == -1 ? mysqlTypeName.trim() :
mysqlTypeName.substring(0, parenIndex).trim();
+ }
}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/TestMySQLDataTypeTransformer.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/TestMySQLDataTypeTransformer.java
index 3629edd883..8aee5f8e02 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/TestMySQLDataTypeTransformer.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/jdbc/mysql/TestMySQLDataTypeTransformer.java
@@ -157,6 +157,28 @@ public class TestMySQLDataTypeTransformer {
Assertions.assertEquals(
generalDataTypeTransformer.getTrinoType(varbinaryType),
VarbinaryType.VARBINARY);
+ // catalogString may carry type parameters, e.g. "varbinary(100)",
"enum('a','b','c')",
+ // "binary(16)" or "bit(8)" -- these must resolve the same as their bare
form.
+ Type varbinaryWithLengthType = Types.ExternalType.of("varbinary(100)");
+ Assertions.assertEquals(
+ generalDataTypeTransformer.getTrinoType(varbinaryWithLengthType),
VarbinaryType.VARBINARY);
+
+ Type enumWithValuesType = Types.ExternalType.of("enum('a','b','c')");
+ Assertions.assertEquals(
+ generalDataTypeTransformer.getTrinoType(enumWithValuesType),
VarcharType.VARCHAR);
+
+ Type setWithValuesType = Types.ExternalType.of("set('x','y','z')");
+ Assertions.assertEquals(
+ generalDataTypeTransformer.getTrinoType(setWithValuesType),
VarcharType.VARCHAR);
+
+ Type binaryWithLengthType = Types.ExternalType.of("binary(16)");
+ Assertions.assertEquals(
+ generalDataTypeTransformer.getTrinoType(binaryWithLengthType),
VarbinaryType.VARBINARY);
+
+ Type bitWithLengthType = Types.ExternalType.of("bit(8)");
+ Assertions.assertEquals(
+ generalDataTypeTransformer.getTrinoType(bitWithLengthType),
VarbinaryType.VARBINARY);
+
Type tinyblobType = Types.ExternalType.of("tinyblob");
Assertions.assertEquals(
generalDataTypeTransformer.getTrinoType(tinyblobType),
VarbinaryType.VARBINARY);