From 406bb1fb6636fd5fae37c2f128da058be203ef18 Mon Sep 17 00:00:00 2001 From: Nevin Zheng Date: Fri, 17 Jul 2026 23:12:24 -0700 Subject: [PATCH 1/5] [MINOR] fix(jdbc-mysql): reject nanosecond timestamps --- .../mysql/converter/MysqlTypeConverter.java | 10 ++++++ .../converter/TestMysqlTypeConverter.java | 26 ++++++++++++++-- .../integration/test/CatalogMysqlIT.java | 31 +++++++++++++++++++ docs/jdbc-mysql-catalog.md | 7 +++-- 4 files changed, 70 insertions(+), 4 deletions(-) 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 317263690df..59e2860275d 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 @@ /** Type converter for MySQL. */ public class MysqlTypeConverter extends JdbcTypeConverter { + private static final int MAX_DATETIME_PRECISION = 6; + static final String BIT = "bit"; static final String TINYINT = "tinyint"; static final String TINYINT_UNSIGNED = "tinyint unsigned"; @@ -146,6 +148,9 @@ public String fromGravitino(Type type) { // from UTC to the current time zone for retrieval. (This does not occur for other types // such as DATETIME.) see more details: https://dev.mysql.com/doc/refman/8.0/en/datetime.html Types.TimestampType timestampType = (Types.TimestampType) type; + if (timestampType.hasPrecisionSet() && timestampType.precision() > MAX_DATETIME_PRECISION) { + throw unsupportedType(type, "fractional-second precision must be between 0 and 6"); + } String baseType = timestampType.hasTimeZone() ? TIMESTAMP : DATETIME; return timestampType.hasPrecisionSet() ? String.format("%s(%d)", baseType, timestampType.precision()) @@ -166,4 +171,9 @@ public String fromGravitino(Type type) { throw new IllegalArgumentException( String.format("Couldn't convert Gravitino type %s to MySQL type", type.simpleString())); } + + private static IllegalArgumentException unsupportedType(Type type, String reason) { + return new IllegalArgumentException( + String.format("MySQL does not support Gravitino type %s: %s", type.simpleString(), reason)); + } } 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 85ccc85ed92..438262eb1a7 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 @@ -65,13 +65,11 @@ public void testToGravitinoType() { checkJdbcTypeToGravitinoType(Types.TimestampType.withoutTimeZone(0), DATETIME, 19, null, 0); checkJdbcTypeToGravitinoType(Types.TimestampType.withoutTimeZone(3), DATETIME, 23, null, 3); checkJdbcTypeToGravitinoType(Types.TimestampType.withoutTimeZone(6), DATETIME, 26, null, 6); - checkJdbcTypeToGravitinoType(Types.TimestampType.withoutTimeZone(9), DATETIME, 29, null, 9); checkJdbcTypeToGravitinoType(Types.TimestampType.withTimeZone(), TIMESTAMP, null, null, null); checkJdbcTypeToGravitinoType(Types.TimestampType.withTimeZone(0), TIMESTAMP, null, null, 0); checkJdbcTypeToGravitinoType(Types.TimestampType.withTimeZone(0), TIMESTAMP, null, null, 0); checkJdbcTypeToGravitinoType(Types.TimestampType.withTimeZone(3), TIMESTAMP, 23, null, 3); checkJdbcTypeToGravitinoType(Types.TimestampType.withTimeZone(6), TIMESTAMP, 26, null, 6); - 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.VarCharType.of(20), VARCHAR, 20, null, 0); @@ -104,6 +102,30 @@ public void testFromGravitinoType() { () -> MYSQL_TYPE_CONVERTER.fromGravitino(Types.UnparsedType.of(USER_DEFINED_TYPE))); } + @Test + public void testRejectNanosecondTimestampTypes() { + checkGravitinoTypeToJdbcType("datetime(6)", Types.TimestampType.withoutTimeZone(6)); + checkGravitinoTypeToJdbcType("timestamp(6)", Types.TimestampType.withTimeZone(6)); + + IllegalArgumentException timestampException = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> MYSQL_TYPE_CONVERTER.fromGravitino(Types.TimestampType.withoutTimeZone(9))); + Assertions.assertTrue( + timestampException + .getMessage() + .contains("fractional-second precision must be between 0 and 6")); + + IllegalArgumentException timestampTzException = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> MYSQL_TYPE_CONVERTER.fromGravitino(Types.TimestampType.withTimeZone(9))); + Assertions.assertTrue( + timestampTzException + .getMessage() + .contains("fractional-second precision must be between 0 and 6")); + } + protected void checkGravitinoTypeToJdbcType(String jdbcTypeName, Type gravitinoType) { Assertions.assertEquals(jdbcTypeName, MYSQL_TYPE_CONVERTER.fromGravitino(gravitinoType)); } 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 db9035d6c38..b1d6c30b4ab 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 @@ -72,6 +72,7 @@ import org.apache.gravitino.rel.indexes.Index; import org.apache.gravitino.rel.indexes.Indexes; import org.apache.gravitino.rel.types.Decimal; +import org.apache.gravitino.rel.types.Type; import org.apache.gravitino.rel.types.Types; import org.apache.gravitino.utils.RandomNameUtils; import org.junit.jupiter.api.AfterAll; @@ -2381,6 +2382,18 @@ void testTimeTypePrecision() { } } + @Test + void testRejectNanosecondTimestampsWithoutSideEffects() { + assertUnsupportedV3TypeDoesNotCreateTable( + "test_timestamp_ns", + Types.TimestampType.withoutTimeZone(9), + "fractional-second precision must be between 0 and 6"); + assertUnsupportedV3TypeDoesNotCreateTable( + "test_timestamp_tz_ns", + Types.TimestampType.withTimeZone(9), + "fractional-second precision must be between 0 and 6"); + } + @Test void testObjectNamesWithDots() { // Create a MySQL database with a dot in its name directly (bypassing Gravitino) @@ -2426,4 +2439,22 @@ void testObjectNamesWithDots() { mysqlService.executeQuery("DROP DATABASE IF EXISTS `" + dottedSchemaName + "`"); } } + + private void assertUnsupportedV3TypeDoesNotCreateTable( + String tablePrefix, Type type, String expectedMessage) { + NameIdentifier tableIdent = + NameIdentifier.of(schemaName, GravitinoITUtils.genRandomName(tablePrefix)); + Column[] columns = {Column.of("v3_column", type, "V3 column")}; + + IllegalArgumentException exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> + catalog + .asTableCatalog() + .createTable(tableIdent, columns, null, Collections.emptyMap())); + + Assertions.assertTrue(exception.getMessage().contains(expectedMessage), exception::getMessage); + Assertions.assertFalse(catalog.asTableCatalog().tableExists(tableIdent)); + } } diff --git a/docs/jdbc-mysql-catalog.md b/docs/jdbc-mysql-catalog.md index dfee4ac223b..130de767bb5 100644 --- a/docs/jdbc-mysql-catalog.md +++ b/docs/jdbc-mysql-catalog.md @@ -134,14 +134,17 @@ Refer to [Manage Relational Metadata Using Gravitino](./manage-relational-metada | `String` | `Text` | | `Date` | `Date` | | `Time[(p)]` | `Time[(p)]` | -| `Timestamp_tz[(p)]` | `Timestamp(p)` | -| `Timestamp[(p)]` | `Datetime[(p)]` | +| `Timestamp_tz[(p)]` | `Timestamp[(p)]`, p in [0, 6] | +| `Timestamp[(p)]` | `Datetime[(p)]`, p in [0, 6] | | `Decimal` | `Decimal` | | `VarChar` | `VarChar` | | `FixedChar` | `FixedChar` | | `Binary` | `Binary` | | `BOOLEAN` | `BIT` | +MySQL supports fractional-second precision from 0 through 6. The catalog rejects Gravitino +`Timestamp` and `Timestamp_tz` precision from 7 through 9 before executing DDL. + :::info MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` type. Meanwhile, the data types other than listed above are mapped to Gravitino **[External Type](./manage-relational-metadata-using-gravitino.md#external-type)** that represents an unresolvable data type since 0.6.0-incubating. From dfd3593b07382478daba3f1b85c29bee962c097b Mon Sep 17 00:00:00 2001 From: Nevin Zheng Date: Fri, 17 Jul 2026 23:13:29 -0700 Subject: [PATCH 2/5] [MINOR] fix(jdbc-mysql): reject variant type --- .../catalog/mysql/converter/MysqlTypeConverter.java | 3 +++ .../mysql/converter/TestMysqlTypeConverter.java | 12 ++++++++++++ .../mysql/integration/test/CatalogMysqlIT.java | 8 ++++++++ docs/jdbc-mysql-catalog.md | 5 ++++- 4 files changed, 27 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 59e2860275d..474c587ab03 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 @@ -165,6 +165,9 @@ public String fromGravitino(Type type) { return type.simpleString(); } else if (type instanceof Types.BooleanType) { return BIT; + } else if (type instanceof Types.VariantType) { + throw unsupportedType( + type, "MySQL JSON does not represent the complete Variant value domain"); } else if (type instanceof Types.ExternalType) { return ((Types.ExternalType) type).catalogString(); } 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 438262eb1a7..a26cdfd18a1 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 @@ -126,6 +126,18 @@ public void testRejectNanosecondTimestampTypes() { .contains("fractional-second precision must be between 0 and 6")); } + @Test + public void testRejectVariantType() { + IllegalArgumentException exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> MYSQL_TYPE_CONVERTER.fromGravitino(Types.VariantType.get())); + Assertions.assertTrue( + exception + .getMessage() + .contains("MySQL JSON does not represent the complete Variant value domain")); + } + protected void checkGravitinoTypeToJdbcType(String jdbcTypeName, Type gravitinoType) { Assertions.assertEquals(jdbcTypeName, MYSQL_TYPE_CONVERTER.fromGravitino(gravitinoType)); } 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 b1d6c30b4ab..788eafd5745 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 @@ -2394,6 +2394,14 @@ void testRejectNanosecondTimestampsWithoutSideEffects() { "fractional-second precision must be between 0 and 6"); } + @Test + void testRejectVariantWithoutSideEffects() { + assertUnsupportedV3TypeDoesNotCreateTable( + "test_variant", + Types.VariantType.get(), + "MySQL JSON does not represent the complete Variant value domain"); + } + @Test void testObjectNamesWithDots() { // Create a MySQL database with a dot in its name directly (bypassing Gravitino) diff --git a/docs/jdbc-mysql-catalog.md b/docs/jdbc-mysql-catalog.md index 130de767bb5..8b4e795144a 100644 --- a/docs/jdbc-mysql-catalog.md +++ b/docs/jdbc-mysql-catalog.md @@ -145,8 +145,11 @@ Refer to [Manage Relational Metadata Using Gravitino](./manage-relational-metada MySQL supports fractional-second precision from 0 through 6. The catalog rejects Gravitino `Timestamp` and `Timestamp_tz` precision from 7 through 9 before executing DDL. +Gravitino `Variant` is rejected before executing DDL because MySQL `JSON` does not represent the +complete Variant value domain. + :::info -MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` type. +MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` `Variant` type. Meanwhile, the data types other than listed above are mapped to Gravitino **[External Type](./manage-relational-metadata-using-gravitino.md#external-type)** that represents an unresolvable data type since 0.6.0-incubating. ::: From fdd301bf21cad608cced88a260ce42f652085f97 Mon Sep 17 00:00:00 2001 From: Nevin Zheng Date: Fri, 17 Jul 2026 23:14:16 -0700 Subject: [PATCH 3/5] [MINOR] fix(jdbc-mysql): reject unknown type --- .../catalog/mysql/converter/MysqlTypeConverter.java | 2 ++ .../mysql/converter/TestMysqlTypeConverter.java | 10 ++++++++++ .../catalog/mysql/integration/test/CatalogMysqlIT.java | 6 ++++++ docs/jdbc-mysql-catalog.md | 5 ++++- 4 files changed, 22 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 474c587ab03..bc17e604c73 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 @@ -168,6 +168,8 @@ public String fromGravitino(Type type) { } else if (type instanceof Types.VariantType) { throw unsupportedType( type, "MySQL JSON does not represent the complete Variant value domain"); + } else if (type instanceof Types.NullType) { + throw unsupportedType(type, "the null-only placeholder has no MySQL column type"); } else if (type instanceof Types.ExternalType) { return ((Types.ExternalType) type).catalogString(); } 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 a26cdfd18a1..d4af39636f7 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 @@ -138,6 +138,16 @@ public void testRejectVariantType() { .contains("MySQL JSON does not represent the complete Variant value domain")); } + @Test + public void testRejectNullType() { + IllegalArgumentException exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> MYSQL_TYPE_CONVERTER.fromGravitino(Types.NullType.get())); + Assertions.assertTrue( + exception.getMessage().contains("the null-only placeholder has no MySQL column type")); + } + protected void checkGravitinoTypeToJdbcType(String jdbcTypeName, Type gravitinoType) { Assertions.assertEquals(jdbcTypeName, MYSQL_TYPE_CONVERTER.fromGravitino(gravitinoType)); } 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 788eafd5745..f1a78c72f7f 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 @@ -2402,6 +2402,12 @@ void testRejectVariantWithoutSideEffects() { "MySQL JSON does not represent the complete Variant value domain"); } + @Test + void testRejectNullTypeWithoutSideEffects() { + assertUnsupportedV3TypeDoesNotCreateTable( + "test_null", Types.NullType.get(), "the null-only placeholder has no MySQL column type"); + } + @Test void testObjectNamesWithDots() { // Create a MySQL database with a dot in its name directly (bypassing Gravitino) diff --git a/docs/jdbc-mysql-catalog.md b/docs/jdbc-mysql-catalog.md index 8b4e795144a..756350c16f8 100644 --- a/docs/jdbc-mysql-catalog.md +++ b/docs/jdbc-mysql-catalog.md @@ -148,8 +148,11 @@ MySQL supports fractional-second precision from 0 through 6. The catalog rejects Gravitino `Variant` is rejected before executing DDL because MySQL `JSON` does not represent the complete Variant value domain. +Gravitino `Null` (the V3 unknown type) is rejected before executing DDL because it is a null-only +placeholder, not a MySQL column type. + :::info -MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` `Variant` type. +MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` `Variant` `Null` type. Meanwhile, the data types other than listed above are mapped to Gravitino **[External Type](./manage-relational-metadata-using-gravitino.md#external-type)** that represents an unresolvable data type since 0.6.0-incubating. ::: From 27068856cb5c54182e625f8cb045b15d09cdba03 Mon Sep 17 00:00:00 2001 From: Nevin Zheng Date: Fri, 17 Jul 2026 23:15:14 -0700 Subject: [PATCH 4/5] [MINOR] fix(jdbc-mysql): reject geometry type --- .../catalog/mysql/converter/MysqlTypeConverter.java | 2 ++ .../mysql/converter/TestMysqlTypeConverter.java | 12 ++++++++++++ .../mysql/integration/test/CatalogMysqlIT.java | 8 ++++++++ docs/jdbc-mysql-catalog.md | 5 ++++- 4 files changed, 26 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 bc17e604c73..f12200020f0 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 @@ -170,6 +170,8 @@ public String fromGravitino(Type type) { type, "MySQL JSON does not represent the complete Variant value domain"); } else if (type instanceof Types.NullType) { throw unsupportedType(type, "the null-only placeholder has no MySQL column type"); + } else if (type instanceof Types.GeometryType) { + throw unsupportedType(type, "the JDBC catalog cannot round-trip Geometry CRS/SRID metadata"); } else if (type instanceof Types.ExternalType) { return ((Types.ExternalType) type).catalogString(); } 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 d4af39636f7..fba590db33a 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 @@ -148,6 +148,18 @@ public void testRejectNullType() { exception.getMessage().contains("the null-only placeholder has no MySQL column type")); } + @Test + public void testRejectGeometryType() { + IllegalArgumentException exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> MYSQL_TYPE_CONVERTER.fromGravitino(Types.GeometryType.of("SRID:3857"))); + Assertions.assertTrue( + exception + .getMessage() + .contains("the JDBC catalog cannot round-trip Geometry CRS/SRID metadata")); + } + protected void checkGravitinoTypeToJdbcType(String jdbcTypeName, Type gravitinoType) { Assertions.assertEquals(jdbcTypeName, MYSQL_TYPE_CONVERTER.fromGravitino(gravitinoType)); } 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 f1a78c72f7f..0e2f8302fa9 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 @@ -2408,6 +2408,14 @@ void testRejectNullTypeWithoutSideEffects() { "test_null", Types.NullType.get(), "the null-only placeholder has no MySQL column type"); } + @Test + void testRejectGeometryWithoutSideEffects() { + assertUnsupportedV3TypeDoesNotCreateTable( + "test_geometry", + Types.GeometryType.of("SRID:3857"), + "the JDBC catalog cannot round-trip Geometry CRS/SRID metadata"); + } + @Test void testObjectNamesWithDots() { // Create a MySQL database with a dot in its name directly (bypassing Gravitino) diff --git a/docs/jdbc-mysql-catalog.md b/docs/jdbc-mysql-catalog.md index 756350c16f8..4e98dd75323 100644 --- a/docs/jdbc-mysql-catalog.md +++ b/docs/jdbc-mysql-catalog.md @@ -151,8 +151,11 @@ complete Variant value domain. Gravitino `Null` (the V3 unknown type) is rejected before executing DDL because it is a null-only placeholder, not a MySQL column type. +Although MySQL has a native `GEOMETRY` type, Gravitino `Geometry` is rejected before executing DDL +because the JDBC catalog cannot round-trip its CRS/SRID metadata without loss. + :::info -MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` `Variant` `Null` type. +MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` `Variant` `Null` `Geometry` type. Meanwhile, the data types other than listed above are mapped to Gravitino **[External Type](./manage-relational-metadata-using-gravitino.md#external-type)** that represents an unresolvable data type since 0.6.0-incubating. ::: From 48e07ec1e7b045f0561f3ed5886e17eade380c0d Mon Sep 17 00:00:00 2001 From: Nevin Zheng Date: Fri, 17 Jul 2026 23:16:24 -0700 Subject: [PATCH 5/5] [MINOR] fix(jdbc-mysql): reject geography type --- .../mysql/converter/MysqlTypeConverter.java | 3 +++ .../mysql/converter/TestMysqlTypeConverter.java | 14 ++++++++++++++ .../mysql/integration/test/CatalogMysqlIT.java | 8 ++++++++ docs/jdbc-mysql-catalog.md | 5 ++++- 4 files changed, 29 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 f12200020f0..0548c898776 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 @@ -172,6 +172,9 @@ public String fromGravitino(Type type) { throw unsupportedType(type, "the null-only placeholder has no MySQL column type"); } else if (type instanceof Types.GeometryType) { throw unsupportedType(type, "the JDBC catalog cannot round-trip Geometry CRS/SRID metadata"); + } else if (type instanceof Types.GeographyType) { + throw unsupportedType( + type, "MySQL has no Geography type that preserves CRS and edge-algorithm metadata"); } else if (type instanceof Types.ExternalType) { return ((Types.ExternalType) type).catalogString(); } 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 fba590db33a..6e87ceb106c 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 @@ -160,6 +160,20 @@ public void testRejectGeometryType() { .contains("the JDBC catalog cannot round-trip Geometry CRS/SRID metadata")); } + @Test + public void testRejectGeographyType() { + IllegalArgumentException exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> + MYSQL_TYPE_CONVERTER.fromGravitino(Types.GeographyType.of("EPSG:4326", "karney"))); + Assertions.assertTrue( + exception + .getMessage() + .contains( + "MySQL has no Geography type that preserves CRS and edge-algorithm metadata")); + } + protected void checkGravitinoTypeToJdbcType(String jdbcTypeName, Type gravitinoType) { Assertions.assertEquals(jdbcTypeName, MYSQL_TYPE_CONVERTER.fromGravitino(gravitinoType)); } 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 0e2f8302fa9..57f0ac8ffdc 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 @@ -2416,6 +2416,14 @@ void testRejectGeometryWithoutSideEffects() { "the JDBC catalog cannot round-trip Geometry CRS/SRID metadata"); } + @Test + void testRejectGeographyWithoutSideEffects() { + assertUnsupportedV3TypeDoesNotCreateTable( + "test_geography", + Types.GeographyType.of("EPSG:4326", "karney"), + "MySQL has no Geography type that preserves CRS and edge-algorithm metadata"); + } + @Test void testObjectNamesWithDots() { // Create a MySQL database with a dot in its name directly (bypassing Gravitino) diff --git a/docs/jdbc-mysql-catalog.md b/docs/jdbc-mysql-catalog.md index 4e98dd75323..b7eb55feb73 100644 --- a/docs/jdbc-mysql-catalog.md +++ b/docs/jdbc-mysql-catalog.md @@ -154,8 +154,11 @@ placeholder, not a MySQL column type. Although MySQL has a native `GEOMETRY` type, Gravitino `Geometry` is rejected before executing DDL because the JDBC catalog cannot round-trip its CRS/SRID metadata without loss. +Gravitino `Geography` is rejected before executing DDL because MySQL has no distinct geography type +that preserves both its CRS and edge-algorithm metadata. + :::info -MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` `Variant` `Null` `Geometry` type. +MySQL doesn't support Gravitino `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` `Variant` `Null` `Geometry` `Geography` type. Meanwhile, the data types other than listed above are mapped to Gravitino **[External Type](./manage-relational-metadata-using-gravitino.md#external-type)** that represents an unresolvable data type since 0.6.0-incubating. :::