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 70d114db4d [core] Refactor compact file identifiers
70d114db4d is described below

commit 70d114db4d5f6c839d2c751f6e7638f760e2e9d5
Author: JingsongLi <[email protected]>
AuthorDate: Tue Aug 11 13:44:16 2026 +0800

    [core] Refactor compact file identifiers
---
 .../DataEvolutionRowIdAssignmentPlanner.java       |   6 +-
 .../DataEvolutionGlobalIndexRefreshPlanner.java    |   8 +-
 .../paimon/manifest/BinaryManifestEntry.java       | 102 -------
 ...ifierSet.java => CompactFileIdentifierSet.java} |  12 +-
 .../java/org/apache/paimon/manifest/FileEntry.java | 109 ++++++++
 .../operation/ManifestEntryExternalSort.java       |  14 +-
 .../paimon/operation/ManifestFileSorter.java       |  20 +-
 .../BinaryManifestEntryReusableIdentifierTest.java | 137 ----------
 .../paimon/manifest/BinaryManifestEntryTest.java   |   3 +-
 ...Test.java => CompactFileIdentifierSetTest.java} |  14 +-
 .../manifest/FileEntryReusableIdentifierTest.java  | 297 +++++++++++++++++++++
 11 files changed, 444 insertions(+), 278 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdAssignmentPlanner.java
 
b/paimon-core/src/main/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdAssignmentPlanner.java
index 0b241afde5..1dd3ae4508 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdAssignmentPlanner.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdAssignmentPlanner.java
@@ -26,8 +26,8 @@ import org.apache.paimon.io.BinaryDataFileMeta;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.manifest.BinaryManifestEntry;
 import org.apache.paimon.manifest.BinaryManifestEntry.Projection;
-import org.apache.paimon.manifest.BinaryManifestEntry.ReusableIdentifier;
-import org.apache.paimon.manifest.DeletedIdentifierSet;
+import org.apache.paimon.manifest.CompactFileIdentifierSet;
+import org.apache.paimon.manifest.FileEntry.ReusableIdentifier;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.manifest.ManifestFile;
 import org.apache.paimon.manifest.ManifestFileMeta;
@@ -556,7 +556,7 @@ final class DataEvolutionRowIdAssignmentPlanner {
     private static final class GroupState {
 
         private final GroupPartitionDictionary partitions;
-        private final DeletedIdentifierSet deletedIdentifiers = new 
DeletedIdentifierSet();
+        private final CompactFileIdentifierSet deletedIdentifiers = new 
CompactFileIdentifierSet();
         private final LiveFileRowIdRangeCollector liveFileRanges;
 
         private GroupState(
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionGlobalIndexRefreshPlanner.java
 
b/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionGlobalIndexRefreshPlanner.java
index 36e11b38f4..53d110f827 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionGlobalIndexRefreshPlanner.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionGlobalIndexRefreshPlanner.java
@@ -25,7 +25,7 @@ import org.apache.paimon.index.GlobalIndexMeta;
 import org.apache.paimon.io.BinaryDataFileMeta;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.manifest.BinaryManifestEntry;
-import org.apache.paimon.manifest.DeletedIdentifierSet;
+import org.apache.paimon.manifest.CompactFileIdentifierSet;
 import org.apache.paimon.manifest.FileKind;
 import org.apache.paimon.manifest.IndexManifestEntry;
 import org.apache.paimon.manifest.ManifestEntry;
@@ -127,7 +127,7 @@ public final class DataEvolutionGlobalIndexRefreshPlanner {
             groupPartitions.add(key.getLeft());
         }
 
-        DeletedIdentifierSet deleted = new DeletedIdentifierSet();
+        CompactFileIdentifierSet deleted = new CompactFileIdentifierSet();
         try {
             collectDeletedIdentifiers(manifestFile, manifests, 
groupPartitions, deleted);
             collectUpdatedRanges(
@@ -178,7 +178,7 @@ public final class DataEvolutionGlobalIndexRefreshPlanner {
             ManifestFile manifestFile,
             List<ManifestFileMeta> manifests,
             Set<BinaryRow> groupPartitions,
-            DeletedIdentifierSet deleted) {
+            CompactFileIdentifierSet deleted) {
         for (ManifestFileMeta manifest : manifests) {
             if (manifest.numDeletedFiles() <= 0) {
                 continue;
@@ -204,7 +204,7 @@ public final class DataEvolutionGlobalIndexRefreshPlanner {
             SchemaManager schemaManager,
             ManifestFile manifestFile,
             List<ManifestFileMeta> manifests,
-            DeletedIdentifierSet deleted,
+            CompactFileIdentifierSet deleted,
             Map<Pair<BinaryRow, Integer>, RefreshGroup> groups,
             Set<Integer> indexedFieldIds) {
         Map<Pair<Long, List<String>>, Set<Integer>> fileFieldIdsCache = new 
HashMap<>();
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/manifest/BinaryManifestEntry.java 
b/paimon-core/src/main/java/org/apache/paimon/manifest/BinaryManifestEntry.java
index c66f165c96..646f19ae44 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/manifest/BinaryManifestEntry.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/manifest/BinaryManifestEntry.java
@@ -19,11 +19,9 @@
 package org.apache.paimon.manifest;
 
 import org.apache.paimon.data.BinaryRow;
-import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.io.BinaryDataFileMeta;
 import org.apache.paimon.io.DataFileMeta;
-import org.apache.paimon.memory.MemorySegmentUtils;
 import org.apache.paimon.types.DataField;
 import org.apache.paimon.types.RowType;
 
@@ -284,106 +282,6 @@ public final class BinaryManifestEntry implements 
ManifestEntry {
                 String.format("Binary manifest entry does not support %s.", 
operation));
     }
 
-    /**
-     * Reusable byte encoding of a binary manifest entry's identity fields.
-     *
-     * <p>The encoded identifier is the prefix of {@link #bytes()} ending at 
{@link #length()}. It
-     * is valid until the next call to {@link #replace(BinaryManifestEntry)} 
or {@link #release()}
-     * and must not be modified by callers.
-     */
-    public static final class ReusableIdentifier {
-
-        private byte[] bytes = new byte[256];
-        private int length;
-
-        public ReusableIdentifier replace(BinaryManifestEntry entry) {
-            checkArgument(entry != null, "Binary manifest entry cannot be 
null.");
-            length = 0;
-            return appendEntryFields(entry);
-        }
-
-        /** Replaces this encoding with the entry's partition and identity 
fields. */
-        public ReusableIdentifier replaceWithPartition(BinaryManifestEntry 
entry) {
-            checkArgument(entry != null, "Binary manifest entry cannot be 
null.");
-            length = 0;
-            putBytes(entry.partitionBytes());
-            return appendEntryFields(entry);
-        }
-
-        private ReusableIdentifier appendEntryFields(BinaryManifestEntry 
entry) {
-            putInt(entry.bucket());
-            BinaryDataFileMeta file = entry.file();
-            putInt(file.level());
-            putString(file.fileNameBinary());
-
-            int extraFileCount = file.extraFileCount();
-            putInt(extraFileCount);
-            for (int i = 0; i < extraFileCount; i++) {
-                putString(file.extraFile(i));
-            }
-
-            if (!file.hasEmbeddedIndex()) {
-                putInt(-1);
-            } else {
-                putBytes(file.embeddedIndex());
-            }
-            if (!file.hasExternalPath()) {
-                putInt(-1);
-            } else {
-                putString(file.externalPathBinary());
-            }
-            return this;
-        }
-
-        public byte[] bytes() {
-            return bytes;
-        }
-
-        public int length() {
-            return length;
-        }
-
-        public void release() {
-            bytes = new byte[0];
-            length = 0;
-        }
-
-        private void putString(BinaryString value) {
-            checkState(value != null, "Manifest string field cannot be null.");
-            int valueLength = value.getSizeInBytes();
-            putInt(valueLength);
-            ensureCapacity(valueLength);
-            MemorySegmentUtils.copyToBytes(
-                    value.getSegments(), value.getOffset(), bytes, length, 
valueLength);
-            length += valueLength;
-        }
-
-        private void putBytes(byte[] value) {
-            checkState(value != null, "Manifest binary field cannot be null.");
-            putInt(value.length);
-            ensureCapacity(value.length);
-            System.arraycopy(value, 0, bytes, length, value.length);
-            length += value.length;
-        }
-
-        private void putInt(int value) {
-            ensureCapacity(Integer.BYTES);
-            bytes[length++] = (byte) (value >>> 24);
-            bytes[length++] = (byte) (value >>> 16);
-            bytes[length++] = (byte) (value >>> 8);
-            bytes[length++] = (byte) value;
-        }
-
-        private void ensureCapacity(int additional) {
-            int required = Math.addExact(length, additional);
-            if (required <= bytes.length) {
-                return;
-            }
-            int grown = Math.max(required, bytes.length + (bytes.length >>> 
1));
-            bytes = Arrays.copyOf(bytes, grown);
-        }
-    }
-
     /**
      * Projected manifest schema together with its bound binary field layout.
      *
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/manifest/DeletedIdentifierSet.java
 
b/paimon-core/src/main/java/org/apache/paimon/manifest/CompactFileIdentifierSet.java
similarity index 95%
rename from 
paimon-core/src/main/java/org/apache/paimon/manifest/DeletedIdentifierSet.java
rename to 
paimon-core/src/main/java/org/apache/paimon/manifest/CompactFileIdentifierSet.java
index 8b97cb533c..5106edab1a 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/manifest/DeletedIdentifierSet.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/manifest/CompactFileIdentifierSet.java
@@ -18,7 +18,7 @@
 
 package org.apache.paimon.manifest;
 
-import org.apache.paimon.manifest.BinaryManifestEntry.ReusableIdentifier;
+import org.apache.paimon.manifest.FileEntry.ReusableIdentifier;
 
 import javax.annotation.Nullable;
 
@@ -28,12 +28,13 @@ import static 
org.apache.paimon.utils.Preconditions.checkArgument;
 import static org.apache.paimon.utils.Preconditions.checkState;
 
 /**
- * Compact, collision-safe set backed by primitive arrays and one identifier 
byte arena.
+ * Compact, collision-safe set for {@link FileEntry.Identifier} values, backed 
by primitive arrays
+ * and one identifier byte arena.
  *
  * <p>The reusable identifier is only used as lookup scratch. Added identifier 
bytes are copied into
  * the arena.
  */
-public final class DeletedIdentifierSet {
+public final class CompactFileIdentifierSet {
 
     private static final float LOAD_FACTOR = 0.75f;
 
@@ -145,8 +146,7 @@ public final class DeletedIdentifierSet {
     }
 
     private void growBuckets() {
-        checkState(
-                buckets.length < (1 << 30), "Too many deleted identifiers in 
one manifest group.");
+        checkState(buckets.length < (1 << 30), "Too many file identifiers in 
one manifest group.");
         int[] grown = filledWithMinusOne(buckets.length << 1);
         for (int entry = 0; entry < size; entry++) {
             int bucket = bucket(hashes[entry], grown.length);
@@ -174,7 +174,7 @@ public final class DeletedIdentifierSet {
             required = Math.addExact(arenaSize, additional);
         } catch (ArithmeticException e) {
             throw new IllegalStateException(
-                    "Deleted identifier arena exceeds the Java array limit.", 
e);
+                    "File identifier arena exceeds the Java array limit.", e);
         }
         if (required <= arena.length) {
             return;
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/manifest/FileEntry.java 
b/paimon-core/src/main/java/org/apache/paimon/manifest/FileEntry.java
index f619f02d60..e33f2e6f85 100644
--- a/paimon-core/src/main/java/org/apache/paimon/manifest/FileEntry.java
+++ b/paimon-core/src/main/java/org/apache/paimon/manifest/FileEntry.java
@@ -19,7 +19,10 @@
 package org.apache.paimon.manifest;
 
 import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.io.BinaryDataFileMeta;
+import org.apache.paimon.memory.MemorySegmentUtils;
 import org.apache.paimon.utils.CloseableIterator;
 import org.apache.paimon.utils.FileStorePathFactory;
 import org.apache.paimon.utils.Filter;
@@ -41,6 +44,7 @@ import java.util.stream.Collectors;
 
 import static 
org.apache.paimon.utils.ManifestReadThreadPool.randomlyExecuteSequentialReturn;
 import static 
org.apache.paimon.utils.ManifestReadThreadPool.sequentialBatchedExecute;
+import static org.apache.paimon.utils.Preconditions.checkArgument;
 import static org.apache.paimon.utils.Preconditions.checkState;
 
 /** Entry representing a file. */
@@ -178,6 +182,111 @@ public interface FileEntry {
         }
     }
 
+    /**
+     * Reusable byte encoding of a binary manifest entry's {@link Identifier} 
fields.
+     *
+     * <p>The encoded identifier is the prefix of {@link #bytes()} ending at 
{@link #length()}. It
+     * is valid until the next call to {@link #replace(BinaryManifestEntry)}, 
{@link
+     * #replaceWithPartition(BinaryManifestEntry)}, or {@link #release()} and 
must not be modified
+     * by callers.
+     *
+     * <p>{@link #replace(BinaryManifestEntry)} omits the partition so callers 
can represent it with
+     * a compact dictionary id. {@link 
#replaceWithPartition(BinaryManifestEntry)} includes the
+     * serialized partition and represents the complete base {@link 
Identifier}.
+     */
+    final class ReusableIdentifier {
+
+        private byte[] bytes = new byte[256];
+        private int length;
+
+        public ReusableIdentifier replace(BinaryManifestEntry entry) {
+            checkArgument(entry != null, "Binary manifest entry cannot be 
null.");
+            length = 0;
+            return appendEntryFields(entry);
+        }
+
+        /** Replaces this encoding with the entry's partition and identity 
fields. */
+        public ReusableIdentifier replaceWithPartition(BinaryManifestEntry 
entry) {
+            checkArgument(entry != null, "Binary manifest entry cannot be 
null.");
+            length = 0;
+            putBytes(entry.partitionBytes());
+            return appendEntryFields(entry);
+        }
+
+        private ReusableIdentifier appendEntryFields(BinaryManifestEntry 
entry) {
+            putInt(entry.bucket());
+            BinaryDataFileMeta file = entry.file();
+            putInt(file.level());
+            putString(file.fileNameBinary());
+
+            int extraFileCount = file.extraFileCount();
+            putInt(extraFileCount);
+            for (int i = 0; i < extraFileCount; i++) {
+                putString(file.extraFile(i));
+            }
+
+            if (!file.hasEmbeddedIndex()) {
+                putInt(-1);
+            } else {
+                putBytes(file.embeddedIndex());
+            }
+            if (!file.hasExternalPath()) {
+                putInt(-1);
+            } else {
+                putString(file.externalPathBinary());
+            }
+            return this;
+        }
+
+        public byte[] bytes() {
+            return bytes;
+        }
+
+        public int length() {
+            return length;
+        }
+
+        public void release() {
+            bytes = new byte[0];
+            length = 0;
+        }
+
+        private void putString(BinaryString value) {
+            checkState(value != null, "Manifest string field cannot be null.");
+            int valueLength = value.getSizeInBytes();
+            putInt(valueLength);
+            ensureCapacity(valueLength);
+            MemorySegmentUtils.copyToBytes(
+                    value.getSegments(), value.getOffset(), bytes, length, 
valueLength);
+            length += valueLength;
+        }
+
+        private void putBytes(byte[] value) {
+            checkState(value != null, "Manifest binary field cannot be null.");
+            putInt(value.length);
+            ensureCapacity(value.length);
+            System.arraycopy(value, 0, bytes, length, value.length);
+            length += value.length;
+        }
+
+        private void putInt(int value) {
+            ensureCapacity(Integer.BYTES);
+            bytes[length++] = (byte) (value >>> 24);
+            bytes[length++] = (byte) (value >>> 16);
+            bytes[length++] = (byte) (value >>> 8);
+            bytes[length++] = (byte) value;
+        }
+
+        private void ensureCapacity(int additional) {
+            int required = Math.addExact(length, additional);
+            if (required <= bytes.length) {
+                return;
+            }
+            int grown = Math.max(required, bytes.length + (bytes.length >>> 
1));
+            bytes = Arrays.copyOf(bytes, grown);
+        }
+    }
+
     static <T extends FileEntry> Collection<T> mergeEntries(Iterable<T> 
entries) {
         LinkedHashMap<Identifier, T> map = new LinkedHashMap<>();
         mergeEntries(entries, map);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryExternalSort.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryExternalSort.java
index 94cc2caf75..dd8bcd9d53 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryExternalSort.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/ManifestEntryExternalSort.java
@@ -26,8 +26,8 @@ import 
org.apache.paimon.data.serializer.InternalRowSerializer;
 import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.io.RollingFileWriter;
 import org.apache.paimon.manifest.BinaryManifestEntry;
-import org.apache.paimon.manifest.BinaryManifestEntry.ReusableIdentifier;
-import org.apache.paimon.manifest.DeletedIdentifierSet;
+import org.apache.paimon.manifest.CompactFileIdentifierSet;
+import org.apache.paimon.manifest.FileEntry.ReusableIdentifier;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.manifest.ManifestFile;
 import org.apache.paimon.manifest.ManifestFileMeta;
@@ -58,7 +58,7 @@ public class ManifestEntryExternalSort {
             @Nullable Integer manifestReadParallelism)
             throws Exception {
         try (EntrySorter sorter = new EntrySorter(sortKey, config)) {
-            DeletedIdentifierSet deleteEntries = new DeletedIdentifierSet();
+            CompactFileIdentifierSet deleteEntries = new 
CompactFileIdentifierSet();
             try {
                 scanEntries(
                         section,
@@ -84,7 +84,7 @@ public class ManifestEntryExternalSort {
             ExternalSortConfig config,
             ManifestFile manifestFile,
             List<ManifestFileMeta> newFilesForAbort,
-            DeletedIdentifierSet deleteEntries,
+            CompactFileIdentifierSet deleteEntries,
             @Nullable Integer manifestReadParallelism)
             throws Exception {
         try (EntrySorter sorter = new EntrySorter(sortKey, config)) {
@@ -262,7 +262,7 @@ public class ManifestEntryExternalSort {
 
         private Pair<List<ManifestFileMeta>, List<ManifestFileMeta>> 
writeMinorToManifest(
                 ManifestFile manifestFile,
-                DeletedIdentifierSet deleteEntries,
+                CompactFileIdentifierSet deleteEntries,
                 List<ManifestFileMeta> newFilesForAbort)
                 throws Exception {
             if (isEmpty()) {
@@ -273,8 +273,8 @@ public class ManifestEntryExternalSort {
                     manifestFile.createRollingWriter();
             RollingFileWriter<ManifestEntry, ManifestFileMeta> deleteWriter =
                     manifestFile.createRollingWriter();
-            DeletedIdentifierSet matchedEntries = new DeletedIdentifierSet();
-            DeletedIdentifierSet emittedDeletes = new DeletedIdentifierSet();
+            CompactFileIdentifierSet matchedEntries = new 
CompactFileIdentifierSet();
+            CompactFileIdentifierSet emittedDeletes = new 
CompactFileIdentifierSet();
             ReusableIdentifier identifier = new ReusableIdentifier();
             Exception exception = null;
             try {
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/ManifestFileSorter.java 
b/paimon-core/src/main/java/org/apache/paimon/operation/ManifestFileSorter.java
index 40a76a5914..bee9ddc187 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/ManifestFileSorter.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/ManifestFileSorter.java
@@ -27,7 +27,7 @@ import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.manifest.BinaryManifestEntry;
-import org.apache.paimon.manifest.DeletedIdentifierSet;
+import org.apache.paimon.manifest.CompactFileIdentifierSet;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.manifest.ManifestFile;
 import org.apache.paimon.manifest.ManifestFileMeta;
@@ -72,7 +72,7 @@ public class ManifestFileSorter {
         final boolean fullCompaction;
         final ManifestSortKey sortKey;
         final ManifestEntryExternalSort.ExternalSortConfig externalSortConfig;
-        final DeletedIdentifierSet deleteEntries;
+        final CompactFileIdentifierSet deleteEntries;
         /**
          * Manifest files that need unsorted compaction.
          *
@@ -90,7 +90,7 @@ public class ManifestFileSorter {
                 boolean fullCompaction,
                 ManifestSortKey sortKey,
                 ManifestEntryExternalSort.ExternalSortConfig 
externalSortConfig,
-                DeletedIdentifierSet deleteEntries,
+                CompactFileIdentifierSet deleteEntries,
                 Map<ManifestFileMeta, Boolean> compactWithoutSort,
                 List<ManifestAdjacentSortedRun> levelRuns,
                 List<ManifestAdjacentSortedRun> pickedRuns) {
@@ -112,7 +112,7 @@ public class ManifestFileSorter {
     /** Result of classifying manifest files. */
     private static class ClassifyResult {
         final List<ManifestFileMeta> lsmFiles;
-        final DeletedIdentifierSet deleteEntries;
+        final CompactFileIdentifierSet deleteEntries;
         /**
          * Manifest files that need unsorted compaction.
          *
@@ -125,7 +125,7 @@ public class ManifestFileSorter {
 
         ClassifyResult(
                 List<ManifestFileMeta> lsmFiles,
-                DeletedIdentifierSet deleteEntries,
+                CompactFileIdentifierSet deleteEntries,
                 Map<ManifestFileMeta, Boolean> compactWithoutSort) {
             this.lsmFiles = lsmFiles;
             this.deleteEntries = deleteEntries;
@@ -135,10 +135,10 @@ public class ManifestFileSorter {
 
     /** Binary identifiers and partition values collected from DELETE entries. 
*/
     private static class DeletedEntryInfo {
-        final DeletedIdentifierSet identifiers;
+        final CompactFileIdentifierSet identifiers;
         final Set<BinaryRow> partitions;
 
-        private DeletedEntryInfo(DeletedIdentifierSet identifiers, 
Set<BinaryRow> partitions) {
+        private DeletedEntryInfo(CompactFileIdentifierSet identifiers, 
Set<BinaryRow> partitions) {
             this.identifiers = identifiers;
             this.partitions = partitions;
         }
@@ -511,7 +511,7 @@ public class ManifestFileSorter {
         // Initialize classification containers and read delete entries
         Map<ManifestFileMeta, Boolean> compactWithoutSort = new 
LinkedHashMap<>();
         List<ManifestFileMeta> lsmFiles = new LinkedList<>(input);
-        DeletedIdentifierSet classifiedDeleteEntries = new 
DeletedIdentifierSet();
+        CompactFileIdentifierSet classifiedDeleteEntries = new 
CompactFileIdentifierSet();
         Set<BinaryRow> deletePartitions = Collections.emptySet();
         PartitionPredicate predicate = null;
         if (fullCompaction) {
@@ -557,7 +557,7 @@ public class ManifestFileSorter {
             ManifestFile manifestFile,
             List<ManifestFileMeta> manifestFiles,
             @Nullable Integer manifestReadParallelism) {
-        DeletedIdentifierSet identifiers = new DeletedIdentifierSet();
+        CompactFileIdentifierSet identifiers = new CompactFileIdentifierSet();
         Set<BinaryRow> partitions = new HashSet<>();
         List<ManifestFileMeta> filesWithDeletes = new ArrayList<>();
         for (ManifestFileMeta meta : manifestFiles) {
@@ -588,7 +588,7 @@ public class ManifestFileSorter {
     private static void collectDeletedEntries(
             ManifestFileMeta meta,
             ManifestFile manifestFile,
-            DeletedIdentifierSet identifiers,
+            CompactFileIdentifierSet identifiers,
             Set<BinaryRow> partitions,
             boolean synchronize) {
         try (CloseableIterator<BinaryManifestEntry> entries =
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/manifest/BinaryManifestEntryReusableIdentifierTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/manifest/BinaryManifestEntryReusableIdentifierTest.java
deleted file mode 100644
index 294418cfa3..0000000000
--- 
a/paimon-core/src/test/java/org/apache/paimon/manifest/BinaryManifestEntryReusableIdentifierTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * 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.manifest;
-
-import org.apache.paimon.data.BinaryRow;
-import org.apache.paimon.data.BinaryString;
-import org.apache.paimon.data.GenericArray;
-import org.apache.paimon.data.GenericRow;
-import org.apache.paimon.io.DataFileMeta;
-import org.apache.paimon.manifest.BinaryManifestEntry.ReusableIdentifier;
-import org.apache.paimon.types.DataField;
-import org.apache.paimon.types.RowType;
-
-import org.junit.jupiter.api.Test;
-
-import javax.annotation.Nullable;
-
-import java.util.Arrays;
-import java.util.List;
-
-import static org.apache.paimon.utils.SerializationUtils.serializeBinaryRow;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-/** Tests for {@link ReusableIdentifier}. */
-class BinaryManifestEntryReusableIdentifierTest {
-
-    @Test
-    void testEncodesIdentifierFields() {
-        ReusableIdentifier identifier = new ReusableIdentifier();
-
-        assertThat(
-                        identifier.replace(
-                                entry(3, 2, "f", new String[] {"a", "bc"}, new 
byte[] {7, 8}, "x")))
-                .isSameAs(identifier);
-
-        assertThat(Arrays.copyOf(identifier.bytes(), identifier.length()))
-                .containsExactly(
-                        0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, 102, 0, 0, 0, 2, 
0, 0, 0, 1, 97, 0, 0,
-                        0, 2, 98, 99, 0, 0, 0, 2, 7, 8, 0, 0, 0, 1, 120);
-    }
-
-    @Test
-    void testReusesAndReleasesBuffer() {
-        ReusableIdentifier identifier = new ReusableIdentifier();
-        BinaryManifestEntry entry = entry(1, 0, "file", new String[0], null, 
null);
-
-        identifier.replace(entry);
-        byte[] expected = Arrays.copyOf(identifier.bytes(), 
identifier.length());
-        assertThat(expected).endsWith((byte) 0xff, (byte) 0xff, (byte) 0xff, 
(byte) 0xff);
-
-        identifier.release();
-        assertThat(identifier.length()).isZero();
-        assertThat(identifier.bytes()).isEmpty();
-
-        identifier.replace(entry);
-        assertThat(Arrays.copyOf(identifier.bytes(), 
identifier.length())).isEqualTo(expected);
-    }
-
-    @Test
-    void testRejectsNullEntry() {
-        assertThatThrownBy(() -> new ReusableIdentifier().replace(null))
-                .isInstanceOf(IllegalArgumentException.class);
-    }
-
-    @Test
-    void testDeletedIdentifierSetReusesIdentifierForEntryLookup() {
-        DeletedIdentifierSet identifiers = new DeletedIdentifierSet();
-        BinaryManifestEntry first = entry(1, 0, "first", new String[0], null, 
null);
-        BinaryManifestEntry second = entry(2, 0, "second", new String[0], 
null, null);
-
-        identifiers.add(first);
-        assertThat(identifiers.contains(first)).isTrue();
-        assertThat(identifiers.contains(second)).isFalse();
-        assertThat(identifiers.contains(first)).isTrue();
-
-        identifiers.add(second);
-        assertThat(identifiers.contains(first)).isTrue();
-        assertThat(identifiers.contains(second)).isTrue();
-    }
-
-    private static BinaryManifestEntry entry(
-            int bucket,
-            int level,
-            String fileName,
-            String[] extraFiles,
-            @Nullable byte[] embeddedIndex,
-            @Nullable String externalPath) {
-        RowType manifestType = ManifestEntry.MANIFEST_ROW_TYPE;
-        RowType fileType =
-                DataFileMeta.SCHEMA.project(
-                        DataFileMeta.LEVEL,
-                        DataFileMeta.FILE_NAME,
-                        DataFileMeta.EXTRA_FILES,
-                        DataFileMeta.EMBEDDED_FILE_INDEX,
-                        DataFileMeta.EXTERNAL_PATH);
-        List<DataField> fields =
-                Arrays.asList(
-                        manifestType.getField(ManifestEntry.PARTITION),
-                        manifestType.getField(ManifestEntry.BUCKET),
-                        
manifestType.getField(ManifestEntry.FILE).newType(fileType));
-        Object[] extraFileValues = new Object[extraFiles.length];
-        for (int i = 0; i < extraFiles.length; i++) {
-            extraFileValues[i] = BinaryString.fromString(extraFiles[i]);
-        }
-        return BinaryManifestEntry.Projection.create(new RowType(false, 
fields))
-                .createEntry()
-                .replace(
-                        GenericRow.of(
-                                serializeBinaryRow(BinaryRow.EMPTY_ROW),
-                                bucket,
-                                GenericRow.of(
-                                        level,
-                                        BinaryString.fromString(fileName),
-                                        new GenericArray(extraFileValues),
-                                        embeddedIndex,
-                                        externalPath == null
-                                                ? null
-                                                : 
BinaryString.fromString(externalPath))));
-    }
-}
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/manifest/BinaryManifestEntryTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/manifest/BinaryManifestEntryTest.java
index aceb117264..e60615a1ca 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/manifest/BinaryManifestEntryTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/manifest/BinaryManifestEntryTest.java
@@ -215,8 +215,7 @@ public class BinaryManifestEntryTest {
                                 DataFileMeta.EMBEDDED_FILE_INDEX,
                                 DataFileMeta.EXTERNAL_PATH)
                         .createEntry();
-        BinaryManifestEntry.ReusableIdentifier identifier =
-                new BinaryManifestEntry.ReusableIdentifier();
+        FileEntry.ReusableIdentifier identifier = new 
FileEntry.ReusableIdentifier();
 
         entry.replace(identityRow(partition(1)));
         BinaryRow firstPartition = entry.partition();
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/manifest/DeletedIdentifierSetTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/manifest/CompactFileIdentifierSetTest.java
similarity index 87%
rename from 
paimon-core/src/test/java/org/apache/paimon/manifest/DeletedIdentifierSetTest.java
rename to 
paimon-core/src/test/java/org/apache/paimon/manifest/CompactFileIdentifierSetTest.java
index 0de63608e8..95d071eabe 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/manifest/DeletedIdentifierSetTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/manifest/CompactFileIdentifierSetTest.java
@@ -23,12 +23,12 @@ import org.junit.jupiter.api.Test;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-/** Tests for {@link DeletedIdentifierSet}. */
-class DeletedIdentifierSetTest {
+/** Tests for {@link CompactFileIdentifierSet}. */
+class CompactFileIdentifierSetTest {
 
     @Test
     void testDeduplicatesAndIncludesPartition() {
-        DeletedIdentifierSet identifiers = new DeletedIdentifierSet();
+        CompactFileIdentifierSet identifiers = new CompactFileIdentifierSet();
         byte[] identifier = {1, 2, 3, 4};
 
         identifiers.add(3, identifier, identifier.length);
@@ -46,7 +46,7 @@ class DeletedIdentifierSetTest {
 
     @Test
     void testCopiesOnlyIdentifierPrefix() {
-        DeletedIdentifierSet identifiers = new DeletedIdentifierSet();
+        CompactFileIdentifierSet identifiers = new CompactFileIdentifierSet();
         byte[] identifier = {1, 2, 3};
 
         identifiers.add(0, identifier, 2);
@@ -59,7 +59,7 @@ class DeletedIdentifierSetTest {
 
     @Test
     void testGrowsAndReleases() {
-        DeletedIdentifierSet identifiers = new DeletedIdentifierSet();
+        CompactFileIdentifierSet identifiers = new CompactFileIdentifierSet();
         for (int i = 0; i < 1_000; i++) {
             byte[] identifier = {(byte) i, (byte) (i >>> 8)};
             identifiers.add(i % 7, identifier, identifier.length);
@@ -81,7 +81,7 @@ class DeletedIdentifierSetTest {
 
     @Test
     void testRejectsInvalidIdentifier() {
-        DeletedIdentifierSet identifiers = new DeletedIdentifierSet();
+        CompactFileIdentifierSet identifiers = new CompactFileIdentifierSet();
 
         assertThatThrownBy(() -> identifiers.add(0, (byte[]) null, 0))
                 .isInstanceOf(IllegalArgumentException.class);
@@ -89,7 +89,7 @@ class DeletedIdentifierSetTest {
                 .isInstanceOf(IllegalArgumentException.class);
         assertThatThrownBy(() -> identifiers.contains(0, new byte[1], 2))
                 .isInstanceOf(IllegalArgumentException.class);
-        assertThatThrownBy(() -> identifiers.add(0, 
(BinaryManifestEntry.ReusableIdentifier) null))
+        assertThatThrownBy(() -> identifiers.add(0, 
(FileEntry.ReusableIdentifier) null))
                 .isInstanceOf(IllegalArgumentException.class);
     }
 }
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/manifest/FileEntryReusableIdentifierTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/manifest/FileEntryReusableIdentifierTest.java
new file mode 100644
index 0000000000..82a4c70b8c
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/manifest/FileEntryReusableIdentifierTest.java
@@ -0,0 +1,297 @@
+/*
+ * 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.manifest;
+
+import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.GenericArray;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.manifest.FileEntry.ReusableIdentifier;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.RowType;
+
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.apache.paimon.utils.SerializationUtils.serializeBinaryRow;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for {@link ReusableIdentifier}. */
+class FileEntryReusableIdentifierTest {
+
+    @Test
+    void testEncodesIdentifierFields() {
+        ReusableIdentifier identifier = new ReusableIdentifier();
+
+        assertThat(
+                        identifier.replace(
+                                entry(3, 2, "f", new String[] {"a", "bc"}, new 
byte[] {7, 8}, "x")))
+                .isSameAs(identifier);
+
+        assertThat(Arrays.copyOf(identifier.bytes(), identifier.length()))
+                .containsExactly(
+                        0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, 102, 0, 0, 0, 2, 
0, 0, 0, 1, 97, 0, 0,
+                        0, 2, 98, 99, 0, 0, 0, 2, 7, 8, 0, 0, 0, 1, 120);
+    }
+
+    @Test
+    void testReusesAndReleasesBuffer() {
+        ReusableIdentifier identifier = new ReusableIdentifier();
+        BinaryManifestEntry entry = entry(1, 0, "file", new String[0], null, 
null);
+
+        identifier.replace(entry);
+        byte[] expected = Arrays.copyOf(identifier.bytes(), 
identifier.length());
+        assertThat(expected).endsWith((byte) 0xff, (byte) 0xff, (byte) 0xff, 
(byte) 0xff);
+
+        identifier.release();
+        assertThat(identifier.length()).isZero();
+        assertThat(identifier.bytes()).isEmpty();
+
+        identifier.replace(entry);
+        assertThat(Arrays.copyOf(identifier.bytes(), 
identifier.length())).isEqualTo(expected);
+    }
+
+    @Test
+    void testRejectsNullEntry() {
+        assertThatThrownBy(() -> new ReusableIdentifier().replace(null))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void testCompactFileIdentifierSetReusesIdentifierForEntryLookup() {
+        CompactFileIdentifierSet identifiers = new CompactFileIdentifierSet();
+        BinaryManifestEntry first = entry(1, 0, "first", new String[0], null, 
null);
+        BinaryManifestEntry second = entry(2, 0, "second", new String[0], 
null, null);
+
+        identifiers.add(first);
+        assertThat(identifiers.contains(first)).isTrue();
+        assertThat(identifiers.contains(second)).isFalse();
+        assertThat(identifiers.contains(first)).isTrue();
+
+        identifiers.add(second);
+        assertThat(identifiers.contains(first)).isTrue();
+        assertThat(identifiers.contains(second)).isTrue();
+    }
+
+    @Test
+    void testCompactAndMaterializedIdentifierSemanticsMatch() {
+        BinaryManifestEntry base =
+                entry(
+                        BinaryRow.EMPTY_ROW,
+                        3,
+                        8,
+                        2,
+                        "f",
+                        new String[] {"a", "bc"},
+                        new byte[] {7, 8},
+                        "x");
+        List<BinaryManifestEntry> candidates =
+                Arrays.asList(
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                8,
+                                2,
+                                "f",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 8},
+                                "x"),
+                        // totalBuckets is deliberately not part of 
FileEntry.Identifier.
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                32,
+                                2,
+                                "f",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 8},
+                                "x"),
+                        entry(
+                                BinaryRow.singleColumn(1),
+                                3,
+                                8,
+                                2,
+                                "f",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 8},
+                                "x"),
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                4,
+                                8,
+                                2,
+                                "f",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 8},
+                                "x"),
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                8,
+                                1,
+                                "f",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 8},
+                                "x"),
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                8,
+                                2,
+                                "g",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 8},
+                                "x"),
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                8,
+                                2,
+                                "f",
+                                new String[] {"bc", "a"},
+                                new byte[] {7, 8},
+                                "x"),
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                8,
+                                2,
+                                "f",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 9},
+                                "x"),
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                8,
+                                2,
+                                "f",
+                                new String[] {"a", "bc"},
+                                null,
+                                "x"),
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                8,
+                                2,
+                                "f",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 8},
+                                "y"),
+                        entry(
+                                BinaryRow.EMPTY_ROW,
+                                3,
+                                8,
+                                2,
+                                "f",
+                                new String[] {"a", "bc"},
+                                new byte[] {7, 8},
+                                null));
+
+        for (BinaryManifestEntry candidate : candidates) {
+            assertIdentifierSemanticsMatch(base, candidate);
+        }
+    }
+
+    private static void assertIdentifierSemanticsMatch(
+            BinaryManifestEntry left, BinaryManifestEntry right) {
+        boolean expected = left.identifier().equals(right.identifier());
+
+        CompactFileIdentifierSet completeIdentifiers = new 
CompactFileIdentifierSet();
+        completeIdentifiers.add(left);
+        assertThat(completeIdentifiers.contains(right)).isEqualTo(expected);
+
+        ReusableIdentifier reusable = new ReusableIdentifier();
+        CompactFileIdentifierSet dictionaryPartitionIdentifiers = new 
CompactFileIdentifierSet();
+        dictionaryPartitionIdentifiers.add(1, reusable.replace(left));
+        int rightPartitionId = left.partition().equals(right.partition()) ? 1 
: 2;
+        assertThat(
+                        dictionaryPartitionIdentifiers.contains(
+                                rightPartitionId, reusable.replace(right)))
+                .isEqualTo(expected);
+    }
+
+    private static BinaryManifestEntry entry(
+            int bucket,
+            int level,
+            String fileName,
+            String[] extraFiles,
+            @Nullable byte[] embeddedIndex,
+            @Nullable String externalPath) {
+        return entry(
+                BinaryRow.EMPTY_ROW,
+                bucket,
+                1,
+                level,
+                fileName,
+                extraFiles,
+                embeddedIndex,
+                externalPath);
+    }
+
+    private static BinaryManifestEntry entry(
+            BinaryRow partition,
+            int bucket,
+            int totalBuckets,
+            int level,
+            String fileName,
+            String[] extraFiles,
+            @Nullable byte[] embeddedIndex,
+            @Nullable String externalPath) {
+        RowType manifestType = ManifestEntry.MANIFEST_ROW_TYPE;
+        RowType fileType =
+                DataFileMeta.SCHEMA.project(
+                        DataFileMeta.LEVEL,
+                        DataFileMeta.FILE_NAME,
+                        DataFileMeta.EXTRA_FILES,
+                        DataFileMeta.EMBEDDED_FILE_INDEX,
+                        DataFileMeta.EXTERNAL_PATH);
+        List<DataField> fields =
+                Arrays.asList(
+                        manifestType.getField(ManifestEntry.PARTITION),
+                        manifestType.getField(ManifestEntry.BUCKET),
+                        manifestType.getField(ManifestEntry.TOTAL_BUCKETS),
+                        
manifestType.getField(ManifestEntry.FILE).newType(fileType));
+        Object[] extraFileValues = new Object[extraFiles.length];
+        for (int i = 0; i < extraFiles.length; i++) {
+            extraFileValues[i] = BinaryString.fromString(extraFiles[i]);
+        }
+        return BinaryManifestEntry.Projection.create(new RowType(false, 
fields))
+                .createEntry()
+                .replace(
+                        GenericRow.of(
+                                serializeBinaryRow(partition),
+                                bucket,
+                                totalBuckets,
+                                GenericRow.of(
+                                        level,
+                                        BinaryString.fromString(fileName),
+                                        new GenericArray(extraFileValues),
+                                        embeddedIndex,
+                                        externalPath == null
+                                                ? null
+                                                : 
BinaryString.fromString(externalPath))));
+    }
+}

Reply via email to