This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 030b38f994 [core] Decouple catalog alter and drop from runtime table 
loading (#10830)
030b38f994 is described below

commit 030b38f9948217d0851d29b3583f4f2ee37fd7ed
Author: zhoulii <[email protected]>
AuthorDate: Tue Oct 27 19:26:42 2026 +0800

    [core] Decouple catalog alter and drop from runtime table loading (#10830)
---
 .../org/apache/paimon/catalog/AbstractCatalog.java |  27 +-
 .../paimon/utils/FileSystemBranchManager.java      |  12 +-
 .../paimon/catalog/FileSystemCatalogTest.java      | 290 +++++++++++++++++++++
 .../org/apache/paimon/hive/HiveCatalogTest.java    |  54 ++++
 4 files changed, 364 insertions(+), 19 deletions(-)

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 48587b1770..d8a43f019b 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
@@ -49,6 +49,7 @@ import org.apache.paimon.table.sink.TableCommitImpl;
 import org.apache.paimon.table.system.SystemTableLoader;
 import org.apache.paimon.types.DataField;
 import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.FileSystemBranchManager;
 import org.apache.paimon.utils.SnapshotNotExistException;
 
 import org.slf4j.Logger;
@@ -383,18 +384,17 @@ public abstract class AbstractCatalog implements Catalog {
         }
 
         Set<Path> externalPaths = new HashSet<>();
-        if (tableExistsInFileSystem(getTableLocation(identifier), 
DEFAULT_MAIN_BRANCH)) {
-            Table table = getTable(identifier);
-            if (table instanceof FileStoreTable) {
-                FileStoreTable fileStoreTable = (FileStoreTable) table;
-                List<Path> schemaExternalPaths =
-                        
getSchemaExternalPaths(fileStoreTable.schemaManager().listAll());
-                externalPaths.addAll(schemaExternalPaths);
-                List<String> branches = 
fileStoreTable.branchManager().branches();
-                for (String branch : branches) {
-                    SchemaManager schemaManager =
-                            
fileStoreTable.schemaManager().copyWithBranch(branch);
-                    
externalPaths.addAll(getSchemaExternalPaths(schemaManager.listAll()));
+        Path tablePath = getTableLocation(identifier);
+        if (tableExistsInFileSystem(tablePath, DEFAULT_MAIN_BRANCH)) {
+            TableType tableType = 
CoreOptions.fromMap(loadTableSchema(identifier).options()).type();
+            if (tableType == TableType.TABLE || tableType == 
TableType.MATERIALIZED_TABLE) {
+                // Cleanup only needs persisted metadata, even if runtime 
options are invalid.
+                FileIO tableFileIO = fileIO(tablePath);
+                SchemaManager schemaManager = new 
FileSystemSchemaManager(tableFileIO, tablePath);
+                
externalPaths.addAll(getSchemaExternalPaths(schemaManager.listAll()));
+                for (String branch : 
FileSystemBranchManager.branches(tableFileIO, tablePath)) {
+                    externalPaths.addAll(
+                            
getSchemaExternalPaths(schemaManager.copyWithBranch(branch).listAll()));
                 }
             }
         }
@@ -503,7 +503,8 @@ public abstract class AbstractCatalog implements Catalog {
         checkNotSystemTable(identifier, "alterTable");
 
         try {
-            getTable(identifier);
+            // Validate the resulting schema, rather than the old runtime 
options.
+            loadTableSchema(identifier);
         } catch (TableNotExistException e) {
             if (ignoreIfNotExists) {
                 return;
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/utils/FileSystemBranchManager.java
 
b/paimon-core/src/main/java/org/apache/paimon/utils/FileSystemBranchManager.java
index 872d864eeb..c84b2559ca 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/utils/FileSystemBranchManager.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/utils/FileSystemBranchManager.java
@@ -72,11 +72,6 @@ public class FileSystemBranchManager implements 
BranchManager {
         this.mergeHandler = mergeHandler;
     }
 
-    /** Return the root Directory of branch. */
-    private Path branchDirectory() {
-        return new Path(tablePath + "/branch");
-    }
-
     /** Return the path of a branch. */
     public Path branchPath(String branchName) {
         return new Path(BranchManager.branchPath(tablePath, branchName));
@@ -458,8 +453,13 @@ public class FileSystemBranchManager implements 
BranchManager {
 
     @Override
     public List<String> branches() {
+        return branches(fileIO, tablePath);
+    }
+
+    /** List branches without constructing the table's runtime managers. */
+    public static List<String> branches(FileIO fileIO, Path tablePath) {
         try {
-            return listVersionedDirectories(fileIO, branchDirectory(), 
BRANCH_PREFIX)
+            return listVersionedDirectories(fileIO, new Path(tablePath, 
"branch"), BRANCH_PREFIX)
                     .map(status -> 
status.getPath().getName().substring(BRANCH_PREFIX.length()))
                     .collect(Collectors.toList());
         } catch (IOException e) {
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 d6706b4bda..c50bebd439 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
@@ -20,19 +20,39 @@ package org.apache.paimon.catalog;
 
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.TableType;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.schema.FileSystemSchemaManager;
 import org.apache.paimon.schema.Schema;
+import org.apache.paimon.schema.SchemaChange;
+import org.apache.paimon.schema.TableSchema;
+import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.table.sink.BatchTableCommit;
+import org.apache.paimon.table.sink.BatchTableWrite;
+import org.apache.paimon.table.sink.BatchWriteBuilder;
 import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.utils.SnapshotManager;
 
 import org.apache.paimon.shade.guava30.com.google.common.collect.Lists;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
 
 /** Tests for {@link FileSystemCatalog}. */
 public class FileSystemCatalogTest extends CatalogTestBase {
@@ -151,4 +171,274 @@ public class FileSystemCatalogTest extends 
CatalogTestBase {
                 
.hasMessageContaining(CoreOptions.METASTORE_PARTITIONED_TABLE.key())
                 .hasMessageContaining("REST catalog");
     }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testAlterTableWithInvalidOptions(boolean cacheEnabled) throws 
Exception {
+        Identifier identifier = Identifier.create("test_db", "legacy_table");
+        FileSystemSchemaManager schemaManager = createLegacyTable(identifier, 
cacheEnabled);
+
+        assertThatThrownBy(() -> catalog.getTable(identifier))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("primary key");
+
+        // Fixing just one of the two invalid options must not persist an 
invalid schema.
+        assertThatThrownBy(
+                        () ->
+                                catalog.alterTable(
+                                        identifier,
+                                        
SchemaChange.removeOption("full-compaction.delta-commits"),
+                                        false))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("primary key");
+        assertThat(schemaManager.listAllIds()).containsExactly(0L);
+
+        catalog.alterTable(
+                identifier,
+                Arrays.asList(
+                        
SchemaChange.removeOption("full-compaction.delta-commits"),
+                        SchemaChange.setOption("fields.seq.sequence-group", 
"v")),
+                false);
+
+        assertThat(schemaManager.latest().get().id()).isEqualTo(1L);
+        assertThat(catalog.getTable(identifier).options())
+                .doesNotContainKey("full-compaction.delta-commits")
+                .containsEntry("fields.seq.sequence-group", "v");
+
+        assertThatThrownBy(
+                        () ->
+                                catalog.alterTable(
+                                        identifier,
+                                        
SchemaChange.setOption("fields.seq.sequence-group", "id,v"),
+                                        false))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("primary key");
+        assertThat(schemaManager.latest().get().id()).isEqualTo(1L);
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testAlterTableWithInvalidOptionsAndSnapshots(boolean 
cacheEnabled)
+            throws Exception {
+        Identifier identifier = Identifier.create("test_db", "legacy_table");
+        FileSystemSchemaManager schemaManager = createLegacyTable(identifier, 
cacheEnabled, true);
+        Path tablePath =
+                ((FileSystemCatalog) DelegateCatalog.rootCatalog(catalog))
+                        .getTableLocation(identifier);
+        SnapshotManager snapshotManager =
+                new SnapshotManager(fileIO, tablePath, 
Identifier.DEFAULT_MAIN_BRANCH, null, null);
+        assertThat(snapshotManager.latestSnapshot()).isNotNull();
+        long snapshotId = snapshotManager.latestSnapshot().id();
+        assertThat(snapshotManager.latestSnapshot().schemaId()).isZero();
+        assertThatThrownBy(() -> catalog.getTable(identifier))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("primary key");
+
+        List<SchemaChange> repairChanges =
+                Arrays.asList(
+                        
SchemaChange.removeOption("full-compaction.delta-commits"),
+                        SchemaChange.setOption("fields.seq.sequence-group", 
"v"));
+        List<SchemaChange> immutableChanges = new ArrayList<>(repairChanges);
+        immutableChanges.add(SchemaChange.setOption("merge-engine", 
"deduplicate"));
+        assertThatThrownBy(() -> catalog.alterTable(identifier, 
immutableChanges, false))
+                .isInstanceOf(UnsupportedOperationException.class)
+                .hasMessage("Change 'merge-engine' is not supported yet.");
+        assertThat(schemaManager.listAllIds()).containsExactly(0L);
+        assertThat(schemaManager.latest().get().options())
+                .containsEntry("merge-engine", "partial-update")
+                .containsEntry("full-compaction.delta-commits", "1")
+                .containsEntry("fields.seq.sequence-group", "id,v");
+
+        catalog.alterTable(identifier, repairChanges, false);
+
+        FileStoreTable repairedTable = (FileStoreTable) 
catalog.getTable(identifier);
+        assertThat(repairedTable.schema().id()).isEqualTo(1L);
+        assertThat(repairedTable.options())
+                .containsEntry("merge-engine", "partial-update")
+                .containsEntry("fields.seq.sequence-group", "v")
+                .doesNotContainKey("full-compaction.delta-commits");
+        assertThat(snapshotManager.latestSnapshotId()).isEqualTo(snapshotId);
+        assertThat(snapshotManager.latestSnapshot().schemaId()).isZero();
+        assertThat(read(repairedTable, null, null, null, null))
+                .extracting(row -> row.getInt(0), row -> row.getInt(1), row -> 
row.getInt(2))
+                .containsExactlyInAnyOrder(tuple(1, 10, 100), tuple(2, 20, 
200));
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testAlterTableWithInvalidOptionsOnBranch(boolean cacheEnabled) 
throws Exception {
+        Identifier identifier = Identifier.create("test_db", "legacy_table");
+        FileSystemSchemaManager mainSchemaManager = 
createLegacyTable(identifier, cacheEnabled);
+        TableSchema legacySchema = mainSchemaManager.latest().get();
+        FileSystemSchemaManager branchSchemaManager = 
mainSchemaManager.copyWithBranch("dev");
+        writeLegacySchema(branchSchemaManager, legacySchema, 0, 
legacySchema.options());
+        Identifier branchIdentifier = new Identifier("test_db", 
"legacy_table", "dev");
+
+        catalog.alterTable(
+                branchIdentifier,
+                Arrays.asList(
+                        
SchemaChange.removeOption("full-compaction.delta-commits"),
+                        SchemaChange.setOption("fields.seq.sequence-group", 
"v")),
+                false);
+
+        assertThat(branchSchemaManager.latest().get().id()).isEqualTo(1L);
+        assertThat(mainSchemaManager.latest().get().id()).isZero();
+        assertThat(catalog.getTable(branchIdentifier).options())
+                .containsEntry("fields.seq.sequence-group", "v");
+        assertThatThrownBy(() -> catalog.getTable(identifier))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testDropTableWithInvalidOptions(boolean cacheEnabled) throws 
Exception {
+        Identifier identifier = Identifier.create("test_db", "legacy_table");
+        createLegacyTable(identifier, cacheEnabled);
+        Path tablePath =
+                ((FileSystemCatalog) DelegateCatalog.rootCatalog(catalog))
+                        .getTableLocation(identifier);
+
+        assertThatThrownBy(() -> catalog.getTable(identifier))
+                .isInstanceOf(IllegalArgumentException.class);
+        catalog.dropTable(identifier, false);
+
+        assertThat(fileIO.exists(tablePath)).isFalse();
+        assertThatThrownBy(() -> catalog.dropTable(identifier, false))
+                .isInstanceOf(Catalog.TableNotExistException.class);
+        catalog.dropTable(identifier, true);
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testDropTableWithInvalidOptionsAndExternalPaths(boolean 
cacheEnabled)
+            throws Exception {
+        Identifier identifier = Identifier.create("test_db", "legacy_table");
+        FileSystemSchemaManager schemaManager = createLegacyTable(identifier, 
cacheEnabled);
+        TableSchema legacySchema = schemaManager.latest().get();
+        java.nio.file.Path oldMainPath = 
Files.createDirectory(tempFile.resolve("old-main-data"));
+        java.nio.file.Path newMainPath = 
Files.createDirectory(tempFile.resolve("new-main-data"));
+        java.nio.file.Path oldBranchPath =
+                Files.createDirectory(tempFile.resolve("old-branch-index"));
+        java.nio.file.Path newBranchPath =
+                Files.createDirectory(tempFile.resolve("new-branch-index"));
+        java.nio.file.Path unrelatedPath = 
Files.createDirectory(tempFile.resolve("unrelated"));
+
+        Map<String, String> mainOptions = new 
HashMap<>(legacySchema.options());
+        mainOptions.put(CoreOptions.DATA_FILE_EXTERNAL_PATHS.key(), 
oldMainPath.toUri().toString());
+        writeLegacySchema(schemaManager, legacySchema, 0, mainOptions);
+        mainOptions.put(CoreOptions.DATA_FILE_EXTERNAL_PATHS.key(), 
newMainPath.toUri().toString());
+        writeLegacySchema(schemaManager, legacySchema, 1, mainOptions);
+
+        FileSystemSchemaManager branchSchemaManager = 
schemaManager.copyWithBranch("dev");
+        Map<String, String> branchOptions = new 
HashMap<>(legacySchema.options());
+        branchOptions.put(
+                CoreOptions.GLOBAL_INDEX_EXTERNAL_PATH.key(), 
oldBranchPath.toUri().toString());
+        writeLegacySchema(branchSchemaManager, legacySchema, 0, branchOptions);
+        branchOptions.put(
+                CoreOptions.GLOBAL_INDEX_EXTERNAL_PATH.key(), 
newBranchPath.toUri().toString());
+        writeLegacySchema(branchSchemaManager, legacySchema, 1, branchOptions);
+
+        assertThatThrownBy(() -> catalog.getTable(identifier))
+                .isInstanceOf(IllegalArgumentException.class);
+        catalog.dropTable(identifier, false);
+
+        for (java.nio.file.Path externalPath :
+                Arrays.asList(oldMainPath, newMainPath, oldBranchPath, 
newBranchPath)) {
+            assertThat(externalPath).doesNotExist();
+        }
+        assertThat(unrelatedPath).exists();
+    }
+
+    @ParameterizedTest
+    @EnumSource(
+            value = TableType.class,
+            names = {"FORMAT_TABLE", "OBJECT_TABLE"})
+    public void testDropNonDataTablePreservesExternalPaths(TableType 
tableType) throws Exception {
+        Identifier identifier = Identifier.create("test_db", "non_data_table");
+        java.nio.file.Path externalPath = 
Files.createDirectory(tempFile.resolve("external-data"));
+        catalog.createDatabase(identifier.getDatabaseName(), false);
+        // The filesystem catalog can load these table types but does not 
create them.
+        Path tablePath = ((FileSystemCatalog) 
catalog).getTableLocation(identifier);
+        new FileSystemSchemaManager(fileIO, tablePath)
+                .createTable(
+                        Schema.newBuilder()
+                                .column("id", DataTypes.INT())
+                                .option(CoreOptions.TYPE.key(), 
tableType.toString())
+                                .option(
+                                        
CoreOptions.DATA_FILE_EXTERNAL_PATHS.key(),
+                                        externalPath.toUri().toString())
+                                .build());
+
+        catalog.dropTable(identifier, false);
+
+        assertThat(externalPath).exists();
+    }
+
+    private FileSystemSchemaManager createLegacyTable(Identifier identifier, 
boolean cacheEnabled)
+            throws Exception {
+        return createLegacyTable(identifier, cacheEnabled, false);
+    }
+
+    private FileSystemSchemaManager createLegacyTable(
+            Identifier identifier, boolean cacheEnabled, boolean withData) 
throws Exception {
+        if (cacheEnabled) {
+            catalog = new CachingCatalog(catalog, new Options());
+        }
+        catalog.createDatabase(identifier.getDatabaseName(), false);
+        catalog.createTable(
+                identifier,
+                Schema.newBuilder()
+                        .column("id", DataTypes.INT().notNull())
+                        .column("seq", DataTypes.INT())
+                        .column("v", DataTypes.INT())
+                        .primaryKey("id")
+                        .option("bucket", "1")
+                        .option("merge-engine", "partial-update")
+                        .option("changelog-producer", "lookup")
+                        .option("fields.seq.sequence-group", "v")
+                        .build(),
+                false);
+        Path tablePath =
+                ((FileSystemCatalog) DelegateCatalog.rootCatalog(catalog))
+                        .getTableLocation(identifier);
+        FileSystemSchemaManager schemaManager = new 
FileSystemSchemaManager(fileIO, tablePath);
+        if (withData) {
+            BatchWriteBuilder writeBuilder = 
catalog.getTable(identifier).newBatchWriteBuilder();
+            try (IOManager ioManager = 
IOManager.create(tempFile.resolve("io").toString());
+                    BatchTableWrite write = 
writeBuilder.newWrite().withIOManager(ioManager);
+                    BatchTableCommit commit = writeBuilder.newCommit()) {
+                write.write(GenericRow.of(1, 10, 100));
+                write.write(GenericRow.of(2, 20, 200));
+                commit.commit(write.prepareCommit());
+            }
+            catalog.invalidateTable(identifier);
+        }
+        TableSchema schema = schemaManager.latest().get();
+        Map<String, String> options = new HashMap<>(schema.options());
+        options.put("full-compaction.delta-commits", "1");
+        options.put("fields.seq.sequence-group", "id,v");
+        // Persist a legacy schema without passing it through the current 
CREATE validator.
+        writeLegacySchema(schemaManager, schema, schema.id(), options);
+        return schemaManager;
+    }
+
+    private void writeLegacySchema(
+            FileSystemSchemaManager schemaManager,
+            TableSchema schema,
+            long id,
+            Map<String, String> options)
+            throws Exception {
+        TableSchema legacySchema =
+                new TableSchema(
+                        id,
+                        schema.fields(),
+                        schema.highestFieldId(),
+                        schema.partitionKeys(),
+                        schema.primaryKeys(),
+                        new HashMap<>(options),
+                        schema.comment());
+        Path schemaPath = schemaManager.toSchemaPath(id);
+        fileIO.mkdirs(schemaPath.getParent());
+        fileIO.overwriteFileUtf8(schemaPath, legacySchema.toString());
+    }
 }
diff --git 
a/paimon-hive/paimon-hive-catalog/src/test/java/org/apache/paimon/hive/HiveCatalogTest.java
 
b/paimon-hive/paimon-hive-catalog/src/test/java/org/apache/paimon/hive/HiveCatalogTest.java
index 31bb910b97..9e22b024e4 100644
--- 
a/paimon-hive/paimon-hive-catalog/src/test/java/org/apache/paimon/hive/HiveCatalogTest.java
+++ 
b/paimon-hive/paimon-hive-catalog/src/test/java/org/apache/paimon/hive/HiveCatalogTest.java
@@ -34,6 +34,8 @@ import org.apache.paimon.partition.Partition;
 import org.apache.paimon.partition.PartitionStatistics;
 import org.apache.paimon.schema.Schema;
 import org.apache.paimon.schema.SchemaChange;
+import org.apache.paimon.schema.SchemaManager;
+import org.apache.paimon.schema.TableSchema;
 import org.apache.paimon.table.FallbackReadFileStoreTable;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.object.ObjectTable;
@@ -56,6 +58,8 @@ import org.apache.thrift.TException;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -305,6 +309,56 @@ public class HiveCatalogTest extends CatalogTestBase {
         }
     }
 
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testAlterAndDropTableWithInvalidOptions(boolean external) 
throws Exception {
+        String database = "legacy_options_db";
+        Identifier identifier = Identifier.create(database, "legacy_table");
+        catalog.createDatabase(database, false);
+        Path externalPath = new Path(warehouse, "external-data");
+        fileIO.mkdirs(externalPath);
+        fileIO.overwriteFileUtf8(new Path(externalPath, "data"), "data");
+        catalog.createTable(
+                identifier,
+                Schema.newBuilder()
+                        .column("id", DataTypes.INT())
+                        .option("EXTERNAL", Boolean.toString(external))
+                        .option(CoreOptions.DATA_FILE_EXTERNAL_PATHS.key(), 
externalPath.toString())
+                        .build(),
+                false);
+        FileStoreTable table = (FileStoreTable) catalog.getTable(identifier);
+        SchemaManager schemaManager = table.schemaManager();
+        TableSchema schema = schemaManager.latest().get();
+        Map<String, String> invalidOptions = new HashMap<>(schema.options());
+        invalidOptions.put(CoreOptions.RECORD_LEVEL_TIME_FIELD.key(), 
"missing");
+        // Simulate options persisted before the corresponding runtime 
validation was added.
+        fileIO.overwriteFileUtf8(
+                schemaManager.toSchemaPath(schema.id()), 
schema.copy(invalidOptions).toString());
+
+        assertThatThrownBy(() -> catalog.getTable(identifier))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("missing");
+        catalog.alterTable(
+                identifier,
+                
SchemaChange.removeOption(CoreOptions.RECORD_LEVEL_TIME_FIELD.key()),
+                false);
+        assertThat(catalog.getTable(identifier).options())
+                .doesNotContainKey(CoreOptions.RECORD_LEVEL_TIME_FIELD.key());
+        TableSchema repairedSchema = schemaManager.latest().get();
+        assertThat(repairedSchema.id()).isEqualTo(schema.id() + 1);
+
+        fileIO.overwriteFileUtf8(
+                schemaManager.toSchemaPath(repairedSchema.id()),
+                repairedSchema.copy(invalidOptions).toString());
+        assertThatThrownBy(() -> catalog.getTable(identifier))
+                .isInstanceOf(IllegalArgumentException.class);
+        catalog.dropTable(identifier, false);
+
+        
assertThat(catalog.listTables(database)).doesNotContain(identifier.getTableName());
+        assertThat(fileIO.exists(table.location())).isEqualTo(external);
+        assertThat(fileIO.exists(externalPath)).isEqualTo(external);
+    }
+
     @Test
     public void testDropTableWhenTablePathMissing() throws Exception {
         String databaseName = "test_db";

Reply via email to