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 b610cfd5d2 [hive] Fix chain table HMS partition drop skipped by 
fallback scan. (#9272)
b610cfd5d2 is described below

commit b610cfd5d209cbc63b48852cffabb02fd28bbe25
Author: Wenchao Wu <[email protected]>
AuthorDate: Thu Aug 20 10:11:43 2026 +0800

    [hive] Fix chain table HMS partition drop skipped by fallback scan. (#9272)
---
 .../java/org/apache/paimon/hive/HiveCatalog.java   |  20 ++-
 .../org/apache/paimon/hive/HiveCatalogTest.java    | 196 +++++++++++++++++++++
 2 files changed, 215 insertions(+), 1 deletion(-)

diff --git 
a/paimon-hive/paimon-hive-catalog/src/main/java/org/apache/paimon/hive/HiveCatalog.java
 
b/paimon-hive/paimon-hive-catalog/src/main/java/org/apache/paimon/hive/HiveCatalog.java
index 7b57c6bcea..14a4ead094 100644
--- 
a/paimon-hive/paimon-hive-catalog/src/main/java/org/apache/paimon/hive/HiveCatalog.java
+++ 
b/paimon-hive/paimon-hive-catalog/src/main/java/org/apache/paimon/hive/HiveCatalog.java
@@ -45,6 +45,7 @@ import org.apache.paimon.schema.SchemaChange;
 import org.apache.paimon.schema.SchemaManager;
 import org.apache.paimon.schema.TableSchema;
 import org.apache.paimon.table.CatalogTableType;
+import org.apache.paimon.table.FallbackReadFileStoreTable;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.FormatTable;
 import org.apache.paimon.types.DataField;
@@ -741,7 +742,7 @@ public class HiveCatalog extends AbstractCatalog {
                 continue;
             }
 
-            mainTable.switchToBranch(branchName).newScan()
+            branchTableForPartitionExistence(mainTable, branchName).newScan()
                     .withPartitionsFilter(new 
ArrayList<>(inputsToRemove)).listPartitions().stream()
                     .map(partitionComputer::generatePartValues)
                     .forEach(inputsToRemove::remove);
@@ -750,6 +751,23 @@ public class HiveCatalog extends AbstractCatalog {
         return new ArrayList<>(inputsToRemove);
     }
 
+    /**
+     * Returns the physical table of {@code branchName} without fallback read.
+     *
+     * <p>{@link FallbackReadFileStoreTable#switchToBranch(String)} keeps the 
original fallback
+     * (snapshot/delta for chain tables). Listing partitions through that scan 
would treat fallback
+     * data as still present on the target branch, so Hive metastore 
partitions would never be
+     * dropped.
+     */
+    private FileStoreTable branchTableForPartitionExistence(
+            FileStoreTable table, String branchName) {
+        FileStoreTable branchTable = table.switchToBranch(branchName);
+        if (branchTable instanceof FallbackReadFileStoreTable) {
+            return ((FallbackReadFileStoreTable) branchTable).wrapped();
+        }
+        return branchTable;
+    }
+
     @Override
     protected void dropDatabaseImpl(String name) {
         try {
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 c0019880d1..6cfd1d4fa9 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.table.FallbackReadFileStoreTable;
+import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.object.ObjectTable;
 import org.apache.paimon.table.sink.BatchTableCommit;
 import org.apache.paimon.table.sink.BatchTableWrite;
@@ -65,10 +67,22 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
 
 import static 
org.apache.hadoop.hive.conf.HiveConf.ConfVars.METASTORECONNECTURLKEY;
+import static org.apache.paimon.CoreOptions.BUCKET;
+import static org.apache.paimon.CoreOptions.CHAIN_TABLE_ENABLED;
+import static org.apache.paimon.CoreOptions.FILE_COMPRESSION;
+import static org.apache.paimon.CoreOptions.FILE_FORMAT;
+import static org.apache.paimon.CoreOptions.MANIFEST_COMPRESSION;
 import static org.apache.paimon.CoreOptions.METASTORE_PARTITIONED_TABLE;
 import static org.apache.paimon.CoreOptions.METASTORE_TAG_TO_PARTITION;
+import static org.apache.paimon.CoreOptions.PARTITION_TIMESTAMP_FORMATTER;
+import static org.apache.paimon.CoreOptions.PARTITION_TIMESTAMP_PATTERN;
+import static org.apache.paimon.CoreOptions.SCAN_FALLBACK_BRANCH;
+import static org.apache.paimon.CoreOptions.SCAN_FALLBACK_DELTA_BRANCH;
+import static org.apache.paimon.CoreOptions.SCAN_FALLBACK_SNAPSHOT_BRANCH;
+import static org.apache.paimon.CoreOptions.SEQUENCE_FIELD;
 import static org.apache.paimon.hive.HiveCatalog.PAIMON_TABLE_IDENTIFIER;
 import static org.apache.paimon.hive.HiveCatalog.TABLE_TYPE_PROP;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -585,6 +599,188 @@ public class HiveCatalogTest extends CatalogTestBase {
                         Collections.singletonMap("dt", "20250101"));
     }
 
+    @Test
+    public void testDropPartitionsOnChainTableDoesNotSkipHmsByFallback() 
throws Exception {
+        String databaseName = "test_chain_drop_partition";
+        String tableName = "chain_table";
+        catalog.dropDatabase(databaseName, true, true);
+        catalog.createDatabase(databaseName, true);
+        Identifier identifier = Identifier.create(databaseName, tableName);
+        catalog.createTable(
+                identifier,
+                Schema.newBuilder()
+                        .option(METASTORE_PARTITIONED_TABLE.key(), "true")
+                        .option(BUCKET.key(), "1")
+                        .option(SEQUENCE_FIELD.key(), "v")
+                        .option(FILE_FORMAT.key(), "avro")
+                        .option(FILE_COMPRESSION.key(), "snappy")
+                        .option(MANIFEST_COMPRESSION.key(), "snappy")
+                        .column("dt", DataTypes.STRING())
+                        .column("pk", DataTypes.STRING())
+                        .column("v", DataTypes.STRING())
+                        .partitionKeys("dt")
+                        .primaryKey("pk", "dt")
+                        .build(),
+                false);
+
+        FileStoreTable table = (FileStoreTable) catalog.getTable(identifier);
+        table.createBranch("snapshot");
+        table.createBranch("delta");
+
+        List<SchemaChange> chainOptions =
+                Arrays.asList(
+                        SchemaChange.setOption(CHAIN_TABLE_ENABLED.key(), 
"true"),
+                        
SchemaChange.setOption(SCAN_FALLBACK_SNAPSHOT_BRANCH.key(), "snapshot"),
+                        
SchemaChange.setOption(SCAN_FALLBACK_DELTA_BRANCH.key(), "delta"),
+                        
SchemaChange.setOption(PARTITION_TIMESTAMP_PATTERN.key(), "$dt"),
+                        
SchemaChange.setOption(PARTITION_TIMESTAMP_FORMATTER.key(), "yyyyMMdd"));
+        catalog.alterTable(identifier, chainOptions, false);
+        catalog.alterTable(
+                new Identifier(databaseName, tableName, "snapshot"), 
chainOptions, false);
+        catalog.alterTable(new Identifier(databaseName, tableName, "delta"), 
chainOptions, false);
+
+        Identifier snapshotId = new Identifier(databaseName, tableName, 
"snapshot");
+        Identifier deltaId = new Identifier(databaseName, tableName, "delta");
+        writePartitionRow(snapshotId, "20250101", "1");
+        writePartitionRow(snapshotId, "20250102", "1");
+        writePartitionRow(deltaId, "20250102", "1");
+        writePartitionRow(identifier, "20250103", "1");
+
+        assertHmsDts(databaseName, tableName, "20250101", "20250102", 
"20250103");
+        assertPhysicalDts(snapshotId, "20250101", "20250102");
+        assertPhysicalDts(deltaId, "20250102");
+        assertPhysicalDts(identifier, "20250103");
+
+        // Dropping from snapshot must not keep HMS just because main falls 
back to snapshot.
+        catalog.dropPartitions(snapshotId, partitionSpecs("20250101"));
+        assertHmsDts(databaseName, tableName, "20250102", "20250103");
+        assertPhysicalDts(snapshotId, "20250102");
+
+        // Delta still has 20250102, so HMS must be kept.
+        catalog.dropPartitions(snapshotId, partitionSpecs("20250102"));
+        assertHmsDts(databaseName, tableName, "20250102", "20250103");
+        assertPhysicalDts(snapshotId);
+        assertPhysicalDts(deltaId, "20250102");
+
+        catalog.dropPartitions(deltaId, partitionSpecs("20250102"));
+        assertHmsDts(databaseName, tableName, "20250103");
+        assertPhysicalDts(deltaId);
+
+        // Snapshot still has no 20250103; dropping from main can drop HMS.
+        catalog.dropPartitions(identifier, partitionSpecs("20250103"));
+        assertHmsDts(databaseName, tableName);
+        assertPhysicalDts(identifier);
+
+        // Partition that still exists on snapshot must not be dropped from 
HMS via main.
+        writePartitionRow(snapshotId, "20250104", "1");
+        assertHmsDts(databaseName, tableName, "20250104");
+        catalog.dropPartitions(identifier, partitionSpecs("20250104"));
+        assertHmsDts(databaseName, tableName, "20250104");
+        assertPhysicalDts(snapshotId, "20250104");
+        assertPhysicalDts(identifier);
+    }
+
+    @Test
+    public void 
testDropPartitionsOnFallbackBranchTableDoesNotSkipHmsByFallback() throws 
Exception {
+        String databaseName = "test_fallback_drop_partition";
+        String tableName = "fallback_table";
+        catalog.dropDatabase(databaseName, true, true);
+        catalog.createDatabase(databaseName, true);
+        Identifier identifier = Identifier.create(databaseName, tableName);
+        catalog.createTable(
+                identifier,
+                Schema.newBuilder()
+                        .option(METASTORE_PARTITIONED_TABLE.key(), "true")
+                        .option(FILE_FORMAT.key(), "avro")
+                        .option(FILE_COMPRESSION.key(), "snappy")
+                        .option(MANIFEST_COMPRESSION.key(), "snappy")
+                        .column("col", DataTypes.INT())
+                        .column("dt", DataTypes.STRING())
+                        .partitionKeys("dt")
+                        .build(),
+                false);
+
+        FileStoreTable table = (FileStoreTable) catalog.getTable(identifier);
+        table.createBranch("fallback");
+        catalog.alterTable(
+                identifier,
+                Collections.singletonList(
+                        SchemaChange.setOption(SCAN_FALLBACK_BRANCH.key(), 
"fallback")),
+                false);
+
+        Identifier fallbackId = new Identifier(databaseName, tableName, 
"fallback");
+        writeAppendPartition(fallbackId, "20250101");
+        writeAppendPartition(identifier, "20250102");
+
+        assertHmsDts(databaseName, tableName, "20250101", "20250102");
+        assertPhysicalDts(fallbackId, "20250101");
+        assertPhysicalDts(identifier, "20250102");
+
+        catalog.dropPartitions(identifier, partitionSpecs("20250101"));
+        assertHmsDts(databaseName, tableName, "20250101", "20250102");
+        assertPhysicalDts(fallbackId, "20250101");
+
+        catalog.dropPartitions(fallbackId, partitionSpecs("20250101"));
+        assertHmsDts(databaseName, tableName, "20250102");
+        assertPhysicalDts(fallbackId);
+
+        catalog.dropPartitions(identifier, partitionSpecs("20250102"));
+        assertHmsDts(databaseName, tableName);
+        assertPhysicalDts(identifier);
+    }
+
+    private void writePartitionRow(Identifier identifier, String dt, String 
pk) throws Exception {
+        BatchWriteBuilder writeBuilder = 
catalog.getTable(identifier).newBatchWriteBuilder();
+        try (BatchTableWrite write = writeBuilder.newWrite();
+                BatchTableCommit commit = writeBuilder.newCommit()) {
+            write.write(
+                    GenericRow.of(
+                            BinaryString.fromString(dt),
+                            BinaryString.fromString(pk),
+                            BinaryString.fromString(pk)));
+            commit.commit(write.prepareCommit());
+        }
+    }
+
+    private void writeAppendPartition(Identifier identifier, String dt) throws 
Exception {
+        BatchWriteBuilder writeBuilder = 
catalog.getTable(identifier).newBatchWriteBuilder();
+        try (BatchTableWrite write = writeBuilder.newWrite();
+                BatchTableCommit commit = writeBuilder.newCommit()) {
+            write.write(GenericRow.of(0, BinaryString.fromString(dt)));
+            commit.commit(write.prepareCommit());
+        }
+    }
+
+    private static List<Map<String, String>> partitionSpecs(String... dts) {
+        List<Map<String, String>> specs = new ArrayList<>();
+        for (String dt : dts) {
+            specs.add(Collections.singletonMap("dt", dt));
+        }
+        return specs;
+    }
+
+    private void assertHmsDts(String databaseName, String tableName, String... 
dts)
+            throws Exception {
+        assertThat(
+                        ((HiveCatalog) catalog)
+                                .getHmsClient()
+                                .listPartitions(databaseName, tableName, 
Short.MAX_VALUE))
+                .extracting(p -> p.getValues().get(0))
+                .containsExactlyInAnyOrder(dts);
+    }
+
+    private void assertPhysicalDts(Identifier identifier, String... dts) 
throws Exception {
+        FileStoreTable table = (FileStoreTable) catalog.getTable(identifier);
+        if (table instanceof FallbackReadFileStoreTable) {
+            table = ((FallbackReadFileStoreTable) table).wrapped();
+        }
+        assertThat(
+                        table.newScan().listPartitions().stream()
+                                .map(p -> p.getString(0).toString())
+                                .collect(Collectors.toList()))
+                .containsExactlyInAnyOrder(dts);
+    }
+
     @Test
     public void testListPartitionsWithDoneStatus() throws Exception {
         String databaseName = "testListPartitionsWithDoneStatus";

Reply via email to