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 94c698c656 [core] Derive partition layout from active files (#10052)
94c698c656 is described below

commit 94c698c656599802a4fa36926de61195ac4e27e1
Author: dwangatt <[email protected]>
AuthorDate: Thu Sep 24 19:37:29 2026 +1000

    [core] Derive partition layout from active files (#10052)
---
 .../paimon/operation/AbstractFileStoreScan.java    |  38 ++++++-
 .../FileStoreScanPartitionBucketEntryTest.java     |  32 ++++++
 .../paimon/table/PartitionLayoutScanTest.java      | 124 +++++++++++++++++++++
 3 files changed, 189 insertions(+), 5 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreScan.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreScan.java
index e40d27e789..d5aaa35766 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreScan.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreScan.java
@@ -56,6 +56,7 @@ import javax.annotation.Nullable;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -368,13 +369,40 @@ public abstract class AbstractFileStoreScan implements 
FileStoreScan {
     @Override
     public List<PartitionEntry> readPartitionEntries() {
         List<ManifestFileMeta> manifests = readManifests().filteredManifests;
+        Set<Identifier> deletedEntries =
+                FileEntry.readDeletedEntries(
+                        manifest ->
+                                readManifest(
+                                        manifest,
+                                        SimpleFileEntry::from,
+                                        FileEntry.deletedFilter(),
+                                        null),
+                        manifests,
+                        parallelism);
+
         Map<BinaryRow, PartitionEntry> partitions = new ConcurrentHashMap<>();
         Consumer<ManifestFileMeta> processor =
-                m ->
-                        PartitionEntry.merge(
-                                readManifest(m, 
PartitionEntry::fromManifestEntry, null, null),
-                                partitions);
-        randomlyOnlyExecute(getExecutorService(parallelism), processor, 
manifests);
+                manifest -> {
+                    Map<BinaryRow, PartitionEntry> entries = new HashMap<>();
+                    for (ManifestEntry manifestEntry :
+                            readManifest(
+                                    manifest,
+                                    Function.identity(),
+                                    FileEntry.addFilter(),
+                                    entry -> 
!deletedEntries.contains(entry.identifier()))) {
+                        PartitionEntry entry = 
PartitionEntry.fromManifestEntry(manifestEntry);
+                        entries.compute(
+                                entry.partition(),
+                                (partition, old) -> old == null ? entry : 
old.merge(entry));
+                    }
+                    PartitionEntry.merge(entries.values(), partitions);
+                };
+        randomlyOnlyExecute(
+                getExecutorService(parallelism),
+                processor,
+                manifests.stream()
+                        .filter(manifest -> manifest.numAddedFiles() > 0)
+                        .collect(Collectors.toList()));
         return partitions.values().stream()
                 .filter(p -> p.fileCount() > 0)
                 .collect(Collectors.toList());
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreScanPartitionBucketEntryTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreScanPartitionBucketEntryTest.java
index 8ff5023b16..b91464f9e6 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreScanPartitionBucketEntryTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreScanPartitionBucketEntryTest.java
@@ -18,12 +18,18 @@
 
 package org.apache.paimon.operation;
 
+import org.apache.paimon.CoreOptions;
 import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.fs.Path;
 import org.apache.paimon.manifest.BucketEntry;
 import org.apache.paimon.manifest.PartitionEntry;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.table.sink.BatchTableCommit;
 import org.apache.paimon.table.sink.BatchTableWrite;
 import org.apache.paimon.table.sink.CommitMessage;
+import org.apache.paimon.table.sink.PostponeFixedBucketWriteBuilder;
 import org.apache.paimon.table.sink.TableCommitImpl;
+import org.apache.paimon.table.sink.TableWriteImpl;
 import org.apache.paimon.table.source.snapshot.ScannerTestBase;
 import org.apache.paimon.utils.Pair;
 
@@ -157,6 +163,32 @@ public class FileStoreScanPartitionBucketEntryTest extends 
ScannerTestBase {
         assertThat(entries.get(0).recordCount()).isEqualTo(5);
     }
 
+    @Test
+    public void testReadPartitionEntriesIgnoresDeletedBucketLayout() throws 
Exception {
+        Options options = new Options();
+        options.set(CoreOptions.BUCKET, -2);
+        table = createFileStoreTable(true, options, new Path(tablePath, 
"postpone-bucket-table"));
+        snapshotReader = table.newSnapshotReader();
+
+        PostponeFixedBucketWriteBuilder builder = 
table.newPostponeFixedBucketWriteBuilder();
+        try (TableWriteImpl<?> write = builder.newWrite();
+                BatchTableCommit commit = builder.newCommit()) {
+            write.writeAndReturn(rowData(1, 1, 1L), 0, 4);
+            commit.commit(write.prepareCommit());
+        }
+
+        builder = 
table.newPostponeFixedBucketWriteBuilder().withOverwrite(Collections.emptyMap());
+        try (TableWriteImpl<?> write = builder.newWrite();
+                BatchTableCommit commit = builder.newCommit()) {
+            write.writeAndReturn(rowData(1, 2, 2L), 0, 2);
+            commit.commit(write.prepareCommit());
+        }
+
+        assertThat(snapshotReader.partitionEntries())
+                .extracting(PartitionEntry::totalBuckets)
+                .containsExactly(2);
+    }
+
     @Test
     public void testReadPartitionEntriesAppendOnlyTable() throws Exception {
         // Create an append-only table (no primary keys)
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/PartitionLayoutScanTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/table/PartitionLayoutScanTest.java
new file mode 100644
index 0000000000..5cfa170f2b
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/table/PartitionLayoutScanTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.table;
+
+import org.apache.paimon.FileStore;
+import org.apache.paimon.Snapshot;
+import org.apache.paimon.manifest.FileKind;
+import org.apache.paimon.manifest.ManifestEntry;
+import org.apache.paimon.manifest.ManifestFileMeta;
+import org.apache.paimon.manifest.PartitionEntry;
+import org.apache.paimon.manifest.PojoManifestEntry;
+import org.apache.paimon.utils.Pair;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests partition-layout metadata derived while scanning manifests. */
+public class PartitionLayoutScanTest extends TableTestBase {
+
+    @Test
+    public void testPartitionLayoutIgnoresDeletedFileInSameManifest() throws 
Exception {
+        createTableDefault();
+        FileStoreTable table = getTableDefault();
+        writeDataDefault(Collections.singletonList(dataDefault(1, 1)));
+        writeDataDefault(Collections.singletonList(dataDefault(2, 2)));
+
+        FileStore<?> store = table.store();
+        List<ManifestEntry> existingEntries =
+                store.manifestListFactory().create()
+                        
.readDataManifests(store.snapshotManager().latestSnapshot()).stream()
+                        .flatMap(
+                                manifest ->
+                                        store.manifestFileFactory().create()
+                                                .read(manifest.fileName(), 
manifest.fileSize())
+                                                .stream())
+                        .filter(entry -> entry.kind() == FileKind.ADD)
+                        .collect(Collectors.toList());
+
+        ManifestEntry oldLayoutFile = existingEntries.get(0);
+        ManifestEntry liveLayoutFile = existingEntries.get(1);
+        List<ManifestFileMeta> manifests =
+                store.manifestFileFactory()
+                        .create()
+                        .write(
+                                Arrays.asList(
+                                        new PojoManifestEntry(
+                                                FileKind.ADD,
+                                                oldLayoutFile.partition(),
+                                                oldLayoutFile.bucket(),
+                                                4,
+                                                oldLayoutFile.file()),
+                                        new PojoManifestEntry(
+                                                FileKind.ADD,
+                                                liveLayoutFile.partition(),
+                                                liveLayoutFile.bucket(),
+                                                2,
+                                                liveLayoutFile.file()),
+                                        new PojoManifestEntry(
+                                                FileKind.DELETE,
+                                                oldLayoutFile.partition(),
+                                                oldLayoutFile.bucket(),
+                                                4,
+                                                oldLayoutFile.file())));
+
+        Pair<String, Long> baseManifestList = 
store.manifestListFactory().create().write(manifests);
+        Pair<String, Long> emptyManifestList =
+                
store.manifestListFactory().create().write(Collections.emptyList());
+        long snapshotId = store.snapshotManager().latestSnapshotId() + 1;
+        Snapshot snapshot =
+                new Snapshot(
+                        snapshotId,
+                        table.schema().id(),
+                        baseManifestList.getKey(),
+                        baseManifestList.getValue(),
+                        emptyManifestList.getKey(),
+                        emptyManifestList.getValue(),
+                        null,
+                        null,
+                        null,
+                        commitUser,
+                        null,
+                        snapshotId,
+                        Snapshot.CommitKind.OVERWRITE,
+                        System.currentTimeMillis(),
+                        liveLayoutFile.file().rowCount(),
+                        0,
+                        null,
+                        null,
+                        null,
+                        null,
+                        null,
+                        null);
+        store.snapshotManager()
+                .fileIO()
+                .tryToWriteAtomic(
+                        store.snapshotManager().snapshotPath(snapshotId), 
snapshot.toJson());
+
+        
assertThat(table.newSnapshotReader().withSnapshot(snapshotId).partitionEntries())
+                .extracting(PartitionEntry::totalBuckets)
+                .containsExactly(2);
+    }
+}

Reply via email to