This is an automated email from the ASF dual-hosted git repository. github-actions[bot] pushed a commit to branch cherry-pick-5259e9c5-to-branch-1.3 in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit 3d2517e24e52103206da65b6238154f90e27544f Author: mchades <[email protected]> AuthorDate: Thu Jun 11 17:13:29 2026 +0800 [#10909] fix(client): Fix list/load inconsistency for schema and table names containing dots (#11597) ### What changes were proposed in this pull request? Fix the `list*`/`load*` inconsistency for MySQL schemas and tables whose names contain dots (e.g. a MySQL database named `db.v2` created externally with `` CREATE DATABASE `db.v2` ``). - `GenericSchema`: replace `MetadataObjects.of(String parent, String name, Type)` with `MetadataObjects.of(List, Type)`, consistent with `GenericFileset`, `GenericTopic`, and `GenericModel`. - `RelationalTable`: replace `MetadataObjects.parse(tableFullName(...), TABLE)` with `MetadataObjects.of(List, TABLE)`, and remove the now-unused `tableFullName()` helper and `DOT_JOINER` field. ### Why are the changes needed? Fix: #10909 `MetadataObjects.of(String parent, String name, Type)` joins parent and name with `.` then re-parses by splitting on `.`. When `name` itself contains a dot (e.g. `"db.v2"`), the split produces too many segments and the length check throws `IllegalArgumentException`: ``` IllegalArgumentException: If the type is SCHEMA, the length of names must be 2 at GenericSchema.<init>(GenericSchema.java:48) at BaseSchemaCatalog.loadSchema(BaseSchemaCatalog.java:216) ``` `listSchemas()` had already returned the dotted name successfully, so `loadSchema()` crashing breaks the `list*`/`load*` contract. ### Does this PR introduce _any_ user-facing change? No API changes. Bug fix only: `loadSchema` and `loadTable` now correctly handle names containing dots, consistent with what `listSchemas`/`listTables` already returns. ### How was this patch tested? Added `testObjectNamesWithDots()` to `CatalogMysqlIT` (`@Tag("gravitino-docker-test")`). The test: 1. Creates a MySQL database with a dot in its name directly via JDBC. 2. Asserts `listSchemas()` returns that schema. 3. Asserts `loadSchema()` succeeds for the same name. 4. Creates a table in that schema directly via JDBC. 5. Asserts `listTables()` returns the table. 6. Asserts `loadTable()` succeeds for the same name. Co-authored-by: Copilot <[email protected]> --- .../mysql/integration/test/CatalogMysqlIT.java | 46 ++++++++++++++++++++++ .../org/apache/gravitino/client/GenericSchema.java | 3 +- .../apache/gravitino/client/RelationalTable.java | 11 ++---- 3 files changed, 51 insertions(+), 9 deletions(-) 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 59a83b8e85..db9035d6c3 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 @@ -2380,4 +2380,50 @@ public class CatalogMysqlIT extends BaseIT { } } } + + @Test + void testObjectNamesWithDots() { + // Create a MySQL database with a dot in its name directly (bypassing Gravitino) + String dottedSchemaName = GravitinoITUtils.genRandomName("db") + ".nested"; + mysqlService.executeQuery("CREATE DATABASE `" + dottedSchemaName + "`"); + try { + // listSchemas should include the schema with dot in name + String[] listedSchemas = catalog.asSchemas().listSchemas(); + Set<String> schemaNames = Sets.newHashSet(listedSchemas); + Assertions.assertTrue( + schemaNames.contains(dottedSchemaName), + "listSchemas should return schema with dot in name: " + dottedSchemaName); + + // loadSchema must succeed for a name that appeared in listSchemas + Schema loadedSchema = catalog.asSchemas().loadSchema(dottedSchemaName); + Assertions.assertEquals( + dottedSchemaName, + loadedSchema.name(), + "loadSchema must succeed for schema that appeared in listSchemas"); + + // Create a table directly in MySQL (bypassing Gravitino, since dotted schema names are not + // allowed at creation time, but may exist externally) + String testTableName = GravitinoITUtils.genRandomName("test_table"); + mysqlService.executeQuery( + "CREATE TABLE `" + dottedSchemaName + "`.`" + testTableName + "` (id INT)"); + + // listTables under the dotted schema should include the created table + NameIdentifier[] listedTables = + catalog.asTableCatalog().listTables(Namespace.of(dottedSchemaName)); + Set<String> tableNames = + Arrays.stream(listedTables).map(NameIdentifier::name).collect(Collectors.toSet()); + Assertions.assertTrue( + tableNames.contains(testTableName), "listTables should return table under dotted schema"); + + // loadTable must succeed for a table that appeared in listTables + Table loadedTable = + catalog.asTableCatalog().loadTable(NameIdentifier.of(dottedSchemaName, testTableName)); + Assertions.assertEquals( + testTableName, + loadedTable.name(), + "loadTable must succeed for table that appeared in listTables"); + } finally { + mysqlService.executeQuery("DROP DATABASE IF EXISTS `" + dottedSchemaName + "`"); + } + } } diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java index 79a90d3d4a..bbb5f84300 100644 --- a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java +++ b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java @@ -18,6 +18,7 @@ */ package org.apache.gravitino.client; +import java.util.Arrays; import java.util.Map; import org.apache.gravitino.Audit; import org.apache.gravitino.MetadataObject; @@ -45,7 +46,7 @@ class GenericSchema implements Schema, SupportsTags, SupportsRoles, SupportsPoli GenericSchema(SchemaDTO schemaDTO, RESTClient restClient, String metalake, String catalog) { this.schemaDTO = schemaDTO; MetadataObject schemaObject = - MetadataObjects.of(catalog, schemaDTO.name(), MetadataObject.Type.SCHEMA); + MetadataObjects.of(Arrays.asList(catalog, schemaDTO.name()), MetadataObject.Type.SCHEMA); this.objectTagOperations = new MetadataObjectTagOperations(metalake, schemaObject, restClient); this.objectRoleOperations = new MetadataObjectRoleOperations(metalake, schemaObject, restClient); diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java b/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java index 7a3dd671d9..fac38e76ac 100644 --- a/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java +++ b/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java @@ -22,7 +22,6 @@ import static org.apache.gravitino.dto.util.DTOConverters.fromDTO; import static org.apache.gravitino.dto.util.DTOConverters.toDTO; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -81,8 +80,6 @@ class RelationalTable SupportsStatistics, SupportsPartitionStatistics { - private static final Joiner DOT_JOINER = Joiner.on("."); - private final Table table; private final RESTClient restClient; @@ -119,7 +116,9 @@ class RelationalTable this.restClient = restClient; this.table = fromDTO(tableDTO); MetadataObject tableObject = - MetadataObjects.parse(tableFullName(namespace, tableDTO.name()), MetadataObject.Type.TABLE); + MetadataObjects.of( + Arrays.asList(namespace.level(1), namespace.level(2), tableDTO.name()), + MetadataObject.Type.TABLE); this.objectTagOperations = new MetadataObjectTagOperations(namespace.level(0), tableObject, restClient); this.objectRoleOperations = @@ -373,10 +372,6 @@ class RelationalTable return this; } - private static String tableFullName(Namespace tableNS, String tableName) { - return DOT_JOINER.join(tableNS.level(1), tableNS.level(2), tableName); - } - @Override public String[] listTags() { return objectTagOperations.listTags();
