This is an automated email from the ASF dual-hosted git repository.
yuqi1129 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 278102990e [#13547] fix(mysql): Handle decimal precision beyond
Gravitino limit (#13548)
278102990e is described below
commit 278102990eb60e48f46d3cc9db8786ddafa37a6e
Author: jarred0214 <[email protected]>
AuthorDate: Sat Oct 10 09:06:58 2026 +0800
[#13547] fix(mysql): Handle decimal precision beyond Gravitino limit
(#13548)
### What changes were proposed in this pull request?
Update the MySQL JDBC type converter to handle MySQL `DECIMAL` columns
whose precision exceeds Gravitino's maximum decimal precision of 38.
- Keep mapping `DECIMAL` columns with precision up to 38 to
`DecimalType`.
- Map higher-precision `DECIMAL` columns to `ExternalType`.
- Preserve the native precision, scale, and unsigned attribute, for
example:
- `decimal(65,30)`
- `decimal(65,30) unsigned`
- Add tests for precision 38, precision 39, precision 65, and unsigned
decimals.
### Why are the changes needed?
MySQL supports `DECIMAL` precision up to 65, while Gravitino
`DecimalType` only supports precision up to 38.
The existing converter directly calls `Types.DecimalType.of` with the
JDBC precision and scale. Loading a table containing `DECIMAL(65,30)`
therefore throws:
```text
Decimal precision must be in range[1, 38]: precision: 65
```
The table cannot be loaded, and operations that require table loading,
such as granting table privileges, fail as a consequence.
Fix: #13547
### Does this PR introduce _any_ user-facing change?
Yes. MySQL tables containing `DECIMAL` columns with precision above 38
can now be loaded. These columns are exposed as `ExternalType` values
that preserve their native MySQL declaration. `DECIMAL` columns with
precision up to 38 are unchanged.
### How was this patch tested?
- Added unit coverage to `TestMysqlTypeConverter`.
- Ran:
```shell
./gradlew :catalogs:catalog-jdbc-mysql:test -PskipITs
```
The MySQL JDBC catalog module tests passed.
---
.../mysql/converter/MysqlTypeConverter.java | 21 +++++++++++++++-
.../mysql/converter/TestMysqlTypeConverter.java | 12 +++++++++
.../mysql/integration/test/CatalogMysqlIT.java | 29 ++++++++++++++++++++++
3 files changed, 61 insertions(+), 1 deletion(-)
diff --git
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/converter/MysqlTypeConverter.java
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/converter/MysqlTypeConverter.java
index 317263690d..46c8c016f4 100644
---
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/converter/MysqlTypeConverter.java
+++
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/converter/MysqlTypeConverter.java
@@ -26,6 +26,8 @@ import org.apache.gravitino.rel.types.Types;
/** Type converter for MySQL. */
public class MysqlTypeConverter extends JdbcTypeConverter {
+ // Keep this limit in sync with Types.DecimalType.checkPrecisionScale.
+ static final int MAX_DECIMAL_PRECISION = 38;
static final String BIT = "bit";
static final String TINYINT = "tinyint";
static final String TINYINT_UNSIGNED = "tinyint unsigned";
@@ -91,7 +93,7 @@ public class MysqlTypeConverter extends JdbcTypeConverter {
.orElseGet(Types.TimestampType::withoutTimeZone);
case DECIMAL_UNSIGNED:
case DECIMAL:
- return Types.DecimalType.of(typeBean.getColumnSize(),
typeBean.getScale());
+ return toGravitinoDecimal(typeBean);
case VARCHAR:
return Types.VarCharType.of(typeBean.getColumnSize());
case CHAR:
@@ -166,4 +168,21 @@ public class MysqlTypeConverter extends JdbcTypeConverter {
throw new IllegalArgumentException(
String.format("Couldn't convert Gravitino type %s to MySQL type",
type.simpleString()));
}
+
+ private static Type toGravitinoDecimal(JdbcTypeBean typeBean) {
+ int precision = typeBean.getColumnSize();
+ int scale = typeBean.getScale();
+ if (precision <= MAX_DECIMAL_PRECISION) {
+ // Preserve the existing mapping for signed and unsigned decimals within
the core limit.
+ // DecimalType does not represent unsignedness.
+ return Types.DecimalType.of(precision, scale);
+ }
+
+ // MySQL supports precision up to 65, while Gravitino DecimalType is
limited to 38.
+ // Preserve larger decimals as ExternalType so loading the table does not
fail.
+ String unsignedSuffix =
+ DECIMAL_UNSIGNED.equalsIgnoreCase(typeBean.getTypeName()) ? "
unsigned" : "";
+ return Types.ExternalType.of(
+ String.format("%s(%d,%d)%s", DECIMAL, precision, scale,
unsignedSuffix));
+ }
}
diff --git
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/converter/TestMysqlTypeConverter.java
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/converter/TestMysqlTypeConverter.java
index 85ccc85ed9..b1851853fe 100644
---
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/converter/TestMysqlTypeConverter.java
+++
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/converter/TestMysqlTypeConverter.java
@@ -74,6 +74,14 @@ public class TestMysqlTypeConverter {
checkJdbcTypeToGravitinoType(Types.TimestampType.withTimeZone(9),
TIMESTAMP, 29, null, 9);
checkJdbcTypeToGravitinoType(Types.DecimalType.of(10, 2), DECIMAL, 10, 2,
0);
checkJdbcTypeToGravitinoType(Types.DecimalType.of(10, 2),
DECIMAL_UNSIGNED, 10, 2, 0);
+ checkJdbcTypeToGravitinoType(Types.DecimalType.of(38, 30), DECIMAL, 38,
30, 0);
+ checkJdbcTypeToGravitinoType(Types.DecimalType.of(38, 30),
DECIMAL_UNSIGNED, 38, 30, 0);
+ checkJdbcTypeToGravitinoType(
+ Types.ExternalType.of("decimal(39,30) unsigned"), DECIMAL_UNSIGNED,
39, 30, 0);
+ checkJdbcTypeToGravitinoType(Types.ExternalType.of("decimal(39,30)"),
DECIMAL, 39, 30, 0);
+ checkJdbcTypeToGravitinoType(Types.ExternalType.of("decimal(65,30)"),
DECIMAL, 65, 30, 0);
+ checkJdbcTypeToGravitinoType(
+ Types.ExternalType.of("decimal(65,30) unsigned"), DECIMAL_UNSIGNED,
65, 30, 0);
checkJdbcTypeToGravitinoType(Types.VarCharType.of(20), VARCHAR, 20, null,
0);
checkJdbcTypeToGravitinoType(Types.FixedCharType.of(20), CHAR, 20, null,
0);
checkJdbcTypeToGravitinoType(Types.StringType.get(), TEXT, null, null, 0);
@@ -94,6 +102,10 @@ public class TestMysqlTypeConverter {
checkGravitinoTypeToJdbcType(TIMESTAMP,
Types.TimestampType.withTimeZone());
checkGravitinoTypeToJdbcType(DATETIME,
Types.TimestampType.withoutTimeZone());
checkGravitinoTypeToJdbcType(DECIMAL + "(10,2)", Types.DecimalType.of(10,
2));
+ checkGravitinoTypeToJdbcType("decimal(39,30)",
Types.ExternalType.of("decimal(39,30)"));
+ checkGravitinoTypeToJdbcType("decimal(65,30)",
Types.ExternalType.of("decimal(65,30)"));
+ checkGravitinoTypeToJdbcType(
+ "decimal(65,30) unsigned", Types.ExternalType.of("decimal(65,30)
unsigned"));
checkGravitinoTypeToJdbcType(VARCHAR + "(20)", Types.VarCharType.of(20));
checkGravitinoTypeToJdbcType(CHAR + "(20)", Types.FixedCharType.of(20));
checkGravitinoTypeToJdbcType(TEXT, Types.StringType.get());
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 7d23739c5e..b0a8e08139 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
@@ -679,6 +679,35 @@ public class CatalogMysqlIT extends BaseIT {
}
}
+ @Test
+ void testLoadHighPrecisionDecimalTable() {
+ mysqlService.executeQuery(
+ String.format(
+ "CREATE TABLE %s.%s ("
+ + "decimal_38 DECIMAL(38,30), "
+ + "decimal_39 DECIMAL(39,30), "
+ + "decimal_65 DECIMAL(65,30), "
+ + "decimal_65_unsigned DECIMAL(65,30) UNSIGNED, "
+ + "decimal_10_unsigned DECIMAL(10,2) UNSIGNED, "
+ + "decimal_38_unsigned DECIMAL(38,30) UNSIGNED, "
+ + "decimal_39_unsigned DECIMAL(39,30) UNSIGNED)",
+ schemaName, tableName));
+
+ Table loadedTable =
+ catalog.asTableCatalog().loadTable(NameIdentifier.of(schemaName,
tableName));
+ Column[] columns = loadedTable.columns();
+ Assertions.assertEquals(7, columns.length);
+ Assertions.assertEquals(Types.DecimalType.of(38, 30),
columns[0].dataType());
+ Assertions.assertEquals(Types.ExternalType.of("decimal(39,30)"),
columns[1].dataType());
+ Assertions.assertEquals(Types.ExternalType.of("decimal(65,30)"),
columns[2].dataType());
+ Assertions.assertEquals(
+ Types.ExternalType.of("decimal(65,30) unsigned"),
columns[3].dataType());
+ Assertions.assertEquals(Types.DecimalType.of(10, 2),
columns[4].dataType());
+ Assertions.assertEquals(Types.DecimalType.of(38, 30),
columns[5].dataType());
+ Assertions.assertEquals(
+ Types.ExternalType.of("decimal(39,30) unsigned"),
columns[6].dataType());
+ }
+
@Test
void testColumnTypeConverter() {
// test convert from MySQL to Gravitino