From 885574fa06288fe122bed3506ed9ae5032f0c4cb Mon Sep 17 00:00:00 2001 From: yangjie01 Date: Sat, 26 Sep 2026 16:47:29 +0800 Subject: [PATCH] [core] Harden catalog table type dispatch and external path parsing The create-table switch over TableType had no default branch, so a DDL with an unsupported but parseable type like iceberg-table or lance-table fell through silently on filesystem and JDBC catalogs: the statement reported success without creating anything and every later query failed with table-not-exist. Reject unsupported types explicitly in both AbstractCatalog and JdbcCatalog's duplicated switch. getSchemaExternalPaths fed the raw comma-split tokens of the external path options straight into new Path while the create-time validation trims and tolerates blanks, so a value with empty segments or spaces made every DROP TABLE crash. Trim and skip blank segments like the write side does. Assisted-by: GLM-5.3 --- .../paimon/catalog/AbstractCatalog.java | 10 ++++- .../org/apache/paimon/jdbc/JdbcCatalog.java | 5 +++ .../paimon/catalog/FileSystemCatalogTest.java | 37 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java b/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java index e0f02de21ce8..6d0ac477343d 100644 --- a/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java +++ b/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java @@ -415,6 +415,8 @@ private List getSchemaExternalPaths(List schemas) { }) .filter(Objects::nonNull) .flatMap(externalPath -> Arrays.stream(externalPath.split(","))) + .map(String::trim) + .filter(path -> !path.isEmpty()) .map(Path::new) .distinct() .collect(Collectors.toList()); @@ -445,7 +447,8 @@ public void createTable(Identifier identifier, Schema schema, boolean ignoreIfEx copyTableDefaultOptions(schema.options()); validateCreateTable(schema, false); - switch (Options.fromMap(schema.options()).get(TYPE)) { + TableType type = Options.fromMap(schema.options()).get(TYPE); + switch (type) { case TABLE: case MATERIALIZED_TABLE: createTableImpl(identifier, schema); @@ -456,6 +459,11 @@ public void createTable(Identifier identifier, Schema schema, boolean ignoreIfEx case OBJECT_TABLE: createObjectTable(identifier, schema); break; + default: + // silently falling through would report a successful DDL without + // creating anything + throw new UnsupportedOperationException( + "Create table with type '" + type + "' is not supported."); } } diff --git a/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcCatalog.java b/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcCatalog.java index e73db8a5c007..fd8f7d5dda62 100644 --- a/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcCatalog.java +++ b/paimon-core/src/main/java/org/apache/paimon/jdbc/JdbcCatalog.java @@ -437,6 +437,11 @@ public void createTable(Identifier identifier, Schema schema, boolean ignoreIfEx "Failed to create table " + identifier.getFullName(), e); } break; + default: + // silently falling through would report a successful DDL without + // creating anything + throw new UnsupportedOperationException( + "Create table with type '" + tableType + "' is not supported."); } } diff --git a/paimon-core/src/test/java/org/apache/paimon/catalog/FileSystemCatalogTest.java b/paimon-core/src/test/java/org/apache/paimon/catalog/FileSystemCatalogTest.java index d6706b4bdaf6..71207df9870d 100644 --- a/paimon-core/src/test/java/org/apache/paimon/catalog/FileSystemCatalogTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/catalog/FileSystemCatalogTest.java @@ -69,6 +69,43 @@ public void testCreateTableCaseSensitive() throws Exception { catalog.createTable(identifier, schema, false); } + @Test + public void testCreateTableWithUnsupportedTypeFailsLoudly() throws Exception { + catalog.createDatabase("test_db", false); + Identifier identifier = Identifier.create("test_db", "iceberg_t"); + Schema schema = + Schema.newBuilder() + .column("k", DataTypes.INT()) + .option("type", "iceberg-table") + .build(); + + // falling through the switch silently would report a successful DDL without + // creating anything + assertThatThrownBy(() -> catalog.createTable(identifier, schema, false)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessageContaining("iceberg-table"); + assertThat(catalog.listTables("test_db")).doesNotContain(identifier.getObjectName()); + } + + @Test + public void testDropTableToleratesBlankExternalPaths() throws Exception { + catalog.createDatabase("test_db", false); + Identifier identifier = Identifier.create("test_db", "external_paths_t"); + Path external = new Path(new Path(warehouse), "test_db/external_dir"); + fileIO.mkdirs(external); + Schema schema = + Schema.newBuilder() + .column("k", DataTypes.INT()) + .option("data-file.external-paths", " , " + external + " ,") + .build(); + catalog.createTable(identifier, schema, false); + + // the drop must not crash on the blank segments the create-time validation + // tolerates + catalog.dropTable(identifier, false); + assertThat(catalog.listTables("test_db")).doesNotContain(identifier.getObjectName()); + } + @Test public void testValidateFormatTableDefaultOptions() throws Exception { String database = "format_table_default_validation_db";