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 5c82868d50 [core] Advance index builds across row ID reassignment 
(#10848)
5c82868d50 is described below

commit 5c82868d506d233ef922135fc618f8e684d43c80
Author: YeJunHao <[email protected]>
AuthorDate: Thu Oct 29 15:14:18 2026 +0800

    [core] Advance index builds across row ID reassignment (#10848)
---
 .../paimon/operation/FileStoreCommitImpl.java      |  32 +-
 ...ider.java => DataEvolutionChangesProvider.java} |   6 +-
 .../paimon/operation/commit/StrictModeChecker.java |   3 +-
 .../dataevolution/CompactReassignReuseTest.java    | 330 +++++++++++++++++++--
 .../paimon/operation/FileStoreCommitTest.java      |   6 +-
 5 files changed, 332 insertions(+), 45 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
index a460a5234a..3354d4ab1a 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
@@ -50,8 +50,8 @@ import org.apache.paimon.operation.commit.CommitResult;
 import org.apache.paimon.operation.commit.CommitRollback;
 import org.apache.paimon.operation.commit.CommitScanner;
 import org.apache.paimon.operation.commit.ConflictDetection;
+import org.apache.paimon.operation.commit.DataEvolutionChangesProvider;
 import org.apache.paimon.operation.commit.ManifestEntryChanges;
-import org.apache.paimon.operation.commit.ReassignCompactChangesProvider;
 import org.apache.paimon.operation.commit.RetryCommitResult;
 import 
org.apache.paimon.operation.commit.RetryCommitResult.CommitFailRetryResult;
 import 
org.apache.paimon.operation.commit.RetryCommitResult.ManifestMergeResult;
@@ -381,10 +381,13 @@ public class FileStoreCommitImpl implements 
FileStoreCommit {
 
                 attempts +=
                         tryCommit(
-                                CommitChangesProvider.provider(
-                                        changes.appendTableFiles,
-                                        changes.appendChangelog,
-                                        changes.appendIndexFiles),
+                                dataEvolutionChangesProvider(
+                                        new CommitChanges(
+                                                changes.appendTableFiles,
+                                                changes.appendChangelog,
+                                                changes.appendIndexFiles),
+                                        commitKind,
+                                        Collections.emptySet()),
                                 committable.identifier(),
                                 committable.watermark(),
                                 committable.properties(),
@@ -400,11 +403,12 @@ public class FileStoreCommitImpl implements 
FileStoreCommit {
                     || !changes.compactIndexFiles.isEmpty()) {
                 attempts +=
                         tryCommit(
-                                compactChangesProvider(
+                                dataEvolutionChangesProvider(
                                         new CommitChanges(
                                                 changes.compactTableFiles,
                                                 changes.compactChangelog,
                                                 changes.compactIndexFiles),
+                                        CommitKind.COMPACT,
                                         materializedBuckets),
                                 committable.identifier(),
                                 committable.watermark(),
@@ -589,11 +593,12 @@ public class FileStoreCommitImpl implements 
FileStoreCommit {
             if (withCompact) {
                 attempts +=
                         tryCommit(
-                                compactChangesProvider(
+                                dataEvolutionChangesProvider(
                                         new CommitChanges(
                                                 changes.compactTableFiles,
                                                 emptyList(),
                                                 changes.compactIndexFiles),
+                                        CommitKind.COMPACT,
                                         Collections.emptySet()),
                                 committable.identifier(),
                                 committable.watermark(),
@@ -858,15 +863,22 @@ public class FileStoreCommitImpl implements 
FileStoreCommit {
     }
 
     @VisibleForTesting
-    CommitChangesProvider compactChangesProvider(
-            CommitChanges changes, Set<Pair<BinaryRow, Integer>> 
materializedBuckets) {
+    CommitChangesProvider dataEvolutionChangesProvider(
+            CommitChanges changes,
+            CommitKind commitKind,
+            Set<Pair<BinaryRow, Integer>> materializedBuckets) {
         if (materializedBuckets.isEmpty()) {
             Long lastSafeSnapshot = 
options.commitLastSafeSnapshot().orElse(null);
             if (options.dataEvolutionEnabled()
+                    && (commitKind == CommitKind.COMPACT
+                            || (commitKind == CommitKind.APPEND
+                                    && changes.tableFiles.isEmpty()
+                                    && changes.changelogFiles.isEmpty()
+                                    && !changes.indexFiles.isEmpty()))
                     && 
!conflictDetection.shouldCheckRowIdFromSnapshot(CommitKind.COMPACT)
                     && lastSafeSnapshot != null
                     && lastSafeSnapshot >= 0) {
-                return new ReassignCompactChangesProvider(
+                return new DataEvolutionChangesProvider(
                         fileIO, pathFactory, snapshotManager, 
lastSafeSnapshot, changes);
             }
             return CommitChangesProvider.provider(
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/commit/ReassignCompactChangesProvider.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/commit/DataEvolutionChangesProvider.java
similarity index 97%
rename from 
paimon-core/src/main/java/org/apache/paimon/operation/commit/ReassignCompactChangesProvider.java
rename to 
paimon-core/src/main/java/org/apache/paimon/operation/commit/DataEvolutionChangesProvider.java
index 88d3dcf906..620326f69e 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/commit/ReassignCompactChangesProvider.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/commit/DataEvolutionChangesProvider.java
@@ -44,8 +44,8 @@ import java.util.Set;
 
 import static org.apache.paimon.utils.Preconditions.checkState;
 
-/** Reuses immutable compact outputs after verified, metadata-only row-id 
reassignments. */
-public final class ReassignCompactChangesProvider implements 
CommitChangesProvider {
+/** Reuses immutable outputs after verified, metadata-only row-id 
reassignments. */
+public final class DataEvolutionChangesProvider implements 
CommitChangesProvider {
 
     private final FileIO fileIO;
     private final FileStorePathFactory paths;
@@ -54,7 +54,7 @@ public final class ReassignCompactChangesProvider implements 
CommitChangesProvid
     private long checkedSnapshot;
     private CommitChanges changes;
 
-    public ReassignCompactChangesProvider(
+    public DataEvolutionChangesProvider(
             FileIO fileIO,
             FileStorePathFactory paths,
             SnapshotManager snapshots,
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/commit/StrictModeChecker.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/commit/StrictModeChecker.java
index 092c2d331b..cfb700982b 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/commit/StrictModeChecker.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/commit/StrictModeChecker.java
@@ -71,7 +71,8 @@ public class StrictModeChecker {
             Set<Long> rebasedReassignments) {
         Set<BinaryRow> newPartitions = new HashSet<>(newChangedPartitions);
         for (long id = lastSafeSnapshot + 1; id < newSnapshotId; id++) {
-            if (newCommitKind == CommitKind.COMPACT && 
rebasedReassignments.contains(id)) {
+            if ((newCommitKind == CommitKind.COMPACT || newCommitKind == 
CommitKind.APPEND)
+                    && rebasedReassignments.contains(id)) {
                 continue;
             }
             Snapshot snapshot = snapshotManager.snapshot(id);
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/CompactReassignReuseTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/CompactReassignReuseTest.java
index 62d5a1e62a..6346f25d03 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/CompactReassignReuseTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/CompactReassignReuseTest.java
@@ -33,6 +33,7 @@ import org.apache.paimon.globalindex.GlobalIndexBuilderUtils;
 import org.apache.paimon.globalindex.GlobalIndexResult;
 import org.apache.paimon.globalindex.sorted.SortedGlobalIndexScanner;
 import org.apache.paimon.globalindex.sorted.SortedGlobalIndexTestUtils;
+import org.apache.paimon.index.DataEvolutionIndexSourceMeta;
 import org.apache.paimon.index.IndexFileMeta;
 import org.apache.paimon.io.CompactIncrement;
 import org.apache.paimon.io.DataFileMeta;
@@ -42,7 +43,7 @@ import org.apache.paimon.manifest.IndexManifestEntry;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.operation.Lock;
 import org.apache.paimon.operation.commit.CommitChanges;
-import org.apache.paimon.operation.commit.ReassignCompactChangesProvider;
+import org.apache.paimon.operation.commit.DataEvolutionChangesProvider;
 import org.apache.paimon.partition.PartitionStatistics;
 import org.apache.paimon.predicate.Predicate;
 import org.apache.paimon.predicate.PredicateBuilder;
@@ -82,6 +83,7 @@ import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
@@ -158,19 +160,44 @@ public class CompactReassignReuseTest extends 
TableTestBase {
         return message;
     }
 
-    private CommitMessageImpl compactIndex(FileStoreTable table) throws 
Exception {
+    private List<CommitMessage> buildIndex(FileStoreTable table, String field, 
String partition)
+            throws Exception {
+        return buildIndex(table, "btree", field, partition);
+    }
+
+    private List<CommitMessage> buildIndex(
+            FileStoreTable table, String indexType, String field, String 
partition)
+            throws Exception {
+        return buildIndex(table, indexType, Collections.singletonList(field), 
partition);
+    }
+
+    private List<CommitMessage> buildIndex(
+            FileStoreTable table, String indexType, List<String> fields, 
String partition)
+            throws Exception {
         List<CommitMessage> builds = new ArrayList<>();
         for (DataSplit split :
                 GlobalIndexBuilderUtils.splitByContiguousRowRange(
-                        new SortedGlobalIndexScanner(table, "btree")
-                                
.withIndexFields(Collections.singletonList("id"))
+                        new SortedGlobalIndexScanner(table, indexType)
+                                .withIndexFields(fields)
                                 .scan()
                                 .get()
                                 .entries())) {
-            builds.addAll(SortedGlobalIndexTestUtils.buildIndex(table, 
"btree", "id", split, 4));
+            if (partition == null || 
split.partition().getString(0).toString().equals(partition)) {
+                builds.addAll(
+                        SortedGlobalIndexTestUtils.buildIndex(
+                                table,
+                                indexType,
+                                fields,
+                                split,
+                                table.snapshotManager().latestSnapshotId()));
+            }
         }
+        return builds;
+    }
+
+    private CommitMessageImpl compactIndex(FileStoreTable table) throws 
Exception {
         try (BatchTableCommit commit = 
table.newBatchWriteBuilder().newCommit()) {
-            commit.commit(builds);
+            commit.commit(buildIndex(table, "id", null));
         }
         Snapshot snapshot = table.snapshotManager().latestSnapshot();
         List<IndexManifestEntry> inputs =
@@ -257,6 +284,148 @@ public class CompactReassignReuseTest extends 
TableTestBase {
                         
Arrays.stream(expected).boxed().collect(Collectors.toList()));
     }
 
+    @ParameterizedTest
+    @CsvSource({
+        "btree,false,after-scan", "btree,true,after-scan",
+        "bitmap,false,after-scan", "bitmap,true,after-scan",
+        "composite,false,after-scan", "composite,true,after-scan",
+        "btree,false,before-scan", "btree,true,before-scan",
+        "bitmap,false,before-scan", "bitmap,true,before-scan",
+        "composite,false,before-scan", "composite,true,before-scan",
+        "btree,false,index-first", "btree,true,index-first",
+        "bitmap,false,index-first", "bitmap,true,index-first",
+        "composite,false,index-first", "composite,true,index-first"
+    })
+    void testIndexBuildAcrossReassignment(String indexType, boolean strict, 
String order)
+            throws Exception {
+        FileStoreTable table = prepare();
+        try (BatchTableCommit commit = 
table.newBatchWriteBuilder().newCommit()) {
+            commit.commit(buildIndex(table, "pt", null));
+        }
+        long lastSafeSnapshot = table.snapshotManager().latestSnapshotId();
+        boolean reassignBeforeScan = order.equals("before-scan");
+        if (reassignBeforeScan) {
+            assertThat(new 
DataEvolutionRowIdReassigner(table).reassign().reassigned).isTrue();
+        }
+        long scanSnapshot = table.snapshotManager().latestSnapshotId();
+        boolean composite = indexType.equals("composite");
+        List<CommitMessage> messages =
+                buildIndex(
+                        table,
+                        composite ? "btree" : indexType,
+                        composite ? Arrays.asList("id", "pt") : 
Collections.singletonList("id"),
+                        null);
+        List<String> files =
+                messages.stream()
+                        .flatMap(
+                                m ->
+                                        ((CommitMessageImpl) m)
+                                                
.newFilesIncrement().newIndexFiles().stream())
+                        .map(IndexFileMeta::fileName)
+                        .collect(Collectors.toList());
+        Map<String, String> options = new HashMap<>();
+        options.put(CoreOptions.COMMIT_STRICT_MODE_ENABLED.key(), 
Boolean.toString(strict));
+        options.put(CoreOptions.COMMIT_LAST_SAFE_SNAPSHOT.key(), 
Long.toString(lastSafeSnapshot));
+        FileStoreTable committingTable = table.copy(options);
+        AtomicBoolean indexCommitted = new AtomicBoolean();
+        Runnable commitIndex =
+                () -> {
+                    if (indexCommitted.compareAndSet(false, true)) {
+                        try (BatchTableCommit commit =
+                                
committingTable.newBatchWriteBuilder().newCommit()) {
+                            commit.commit(messages);
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
+                };
+        boolean indexFirst = order.equals("index-first");
+        if (indexFirst) {
+            DataEvolutionRowIdReassigner.Result result =
+                    new DataEvolutionRowIdReassigner(table, null, 
commitIndex).reassign();
+            assertThat(result.reassigned).isTrue();
+            assertThat(result.previousSnapshotId).isEqualTo(scanSnapshot + 1);
+        } else {
+            if (!reassignBeforeScan) {
+                assertThat(new 
DataEvolutionRowIdReassigner(table).reassign().reassigned).isTrue();
+            }
+            commitIndex.run();
+        }
+        assertThat(indexCommitted).isTrue();
+        assertThat(table.snapshotManager().latestSnapshot().commitKind())
+                .isEqualTo(indexFirst ? Snapshot.CommitKind.OVERWRITE : 
Snapshot.CommitKind.APPEND);
+        List<IndexManifestEntry> current =
+                table.store().newIndexFileHandler().scanEntries().stream()
+                        .filter(e -> 
e.indexFile().globalIndexMeta().indexFieldId() == 1)
+                        .collect(Collectors.toList());
+        assertThat(current)
+                .extracting(e -> e.indexFile().fileName())
+                .containsExactlyInAnyOrderElementsOf(files);
+        for (IndexManifestEntry entry : current) {
+            assertThat(
+                            
DataEvolutionIndexSourceMeta.fromIndexFile(entry.indexFile())
+                                    .scanSnapshotId())
+                    .isEqualTo(scanSnapshot);
+        }
+        long[] rowIds = {4, 5, 2, 6};
+        for (int id = 0; id < rowIds.length; id++) {
+            PredicateBuilder builder = new PredicateBuilder(table.rowType());
+            Predicate predicate = builder.equal(1, id);
+            if (composite) {
+                predicate =
+                        PredicateBuilder.and(
+                                predicate,
+                                builder.equal(0, BinaryString.fromString(id == 
2 ? "b" : "a")));
+            }
+            try (DataEvolutionGlobalIndexScanner scanner =
+                    DataEvolutionGlobalIndexScanner.create(table, null, 
predicate).get()) {
+                GlobalIndexResult result = scanner.scan(predicate).get();
+                assertThat(result.results().getLongCardinality()).isEqualTo(1);
+                assertThat(result.results().contains(rowIds[id])).isTrue();
+            }
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    void testIndexBuildKeepsExistingColumnUpdateBehavior(boolean reassign) 
throws Exception {
+        FileStoreTable table = prepare();
+        long scanSnapshot = table.snapshotManager().latestSnapshotId();
+        List<CommitMessage> messages = buildIndex(table, "id", null);
+        BatchWriteBuilder builder = table.newBatchWriteBuilder();
+        try (BatchTableWrite write =
+                        builder.newWrite()
+                                
.withWriteType(table.rowType().project(Arrays.asList("pt", "id")));
+                BatchTableCommit commit = builder.newCommit()) {
+            write.write(GenericRow.of(BinaryString.fromString("a"), 100));
+            List<CommitMessage> updates = write.prepareCommit();
+            for (CommitMessage update : updates) {
+                ((CommitMessageImpl) update)
+                        .newFilesIncrement()
+                        .newFiles()
+                        .replaceAll(file -> file.assignFirstRowId(0L));
+            }
+            commit.commit(updates);
+        }
+        if (reassign) {
+            new DataEvolutionRowIdReassigner(table).reassign();
+        }
+        long snapshot = table.snapshotManager().latestSnapshotId();
+        try (BatchTableCommit commit =
+                table.copy(
+                                Collections.singletonMap(
+                                        
CoreOptions.COMMIT_LAST_SAFE_SNAPSHOT.key(),
+                                        Long.toString(scanSnapshot)))
+                        .newBatchWriteBuilder()
+                        .newCommit()) {
+            commit.commit(messages);
+        }
+        
assertThat(table.snapshotManager().latestSnapshotId()).isEqualTo(snapshot + 1);
+        assertThat(read(table))
+                .extracting(row -> row.getInt(1))
+                .containsExactlyInAnyOrder(100, 1, 2, 3);
+    }
+
     @ParameterizedTest
     @ValueSource(booleans = {false, true})
     void testReuseNormalAndBlobAcrossRepeatedReassignments(boolean blob) 
throws Exception {
@@ -291,12 +460,32 @@ public class CompactReassignReuseTest extends 
TableTestBase {
     }
 
     @ParameterizedTest
-    @ValueSource(booleans = {false, true})
-    void testIndexReplacementKeepsPhysicalFilesAndPointLookup(boolean 
loseSuccessfulResponse)
-            throws Exception {
+    @CsvSource({"false,false", "false,true", "true,false", "true,true"})
+    void testIndexReplacementKeepsPhysicalFilesAndPointLookup(
+            boolean indexBuild, boolean loseSuccessfulResponse) throws 
Exception {
         FileStoreTable table = prepare();
         CommitMessageImpl message = compactIndex(table);
         List<IndexFileMeta> outputs = 
message.compactIncrement().newIndexFiles();
+        List<String> replacedFiles =
+                message.compactIncrement().deletedIndexFiles().stream()
+                        .map(IndexFileMeta::fileName)
+                        .collect(Collectors.toList());
+        long scanSnapshot = lastSafeSnapshots.get(message);
+        if (indexBuild) {
+            message =
+                    new CommitMessageImpl(
+                            message.partition(),
+                            message.bucket(),
+                            message.totalBuckets(),
+                            new DataIncrement(
+                                    Collections.emptyList(),
+                                    Collections.emptyList(),
+                                    Collections.emptyList(),
+                                    outputs,
+                                    
message.compactIncrement().deletedIndexFiles()),
+                            CompactIncrement.emptyIncrement());
+            lastSafeSnapshots.put(message, scanSnapshot);
+        }
         assertThat(new 
DataEvolutionRowIdReassigner(table).reassign().reassigned).isTrue();
         long reassignedSnapshot = table.snapshotManager().latestSnapshotId();
         AtomicInteger attempts = new AtomicInteger();
@@ -316,7 +505,10 @@ public class CompactReassignReuseTest extends 
TableTestBase {
                                     throws Exception {
                                 boolean committed =
                                         delegate.commit(baseUuid, snapshot, 
branch, statistics);
-                                if (snapshot.commitKind() == 
Snapshot.CommitKind.COMPACT) {
+                                if (snapshot.commitKind()
+                                        == (indexBuild
+                                                ? Snapshot.CommitKind.APPEND
+                                                : 
Snapshot.CommitKind.COMPACT)) {
                                     attempts.incrementAndGet();
                                     return committed && 
!loseSuccessfulResponse;
                                 }
@@ -333,6 +525,16 @@ public class CompactReassignReuseTest extends 
TableTestBase {
         FileStoreTable committingTable =
                 FileStoreTableFactory.create(
                         table.fileIO(), table.location(), table.schema(), 
environment);
+        if (indexBuild) {
+            // A replaced index's source baseline predates this refresh's 
planning snapshot.
+            assertThat(
+                            table.fileIO()
+                                    .delete(
+                                            
table.snapshotManager().snapshotPath(scanSnapshot),
+                                            false))
+                    .isTrue();
+            table.snapshotManager().invalidateCache();
+        }
         commit(committingTable, message, false);
         assertThat(attempts).hasValue(1);
         
assertThat(table.snapshotManager().latestSnapshotId()).isEqualTo(reassignedSnapshot
 + 1);
@@ -343,7 +545,8 @@ public class CompactReassignReuseTest extends TableTestBase 
{
                         
.read(table.snapshotManager().latestSnapshot().indexManifest());
         assertThat(current.stream().map(e -> e.indexFile().fileName()))
                 .containsAll(
-                        
outputs.stream().map(IndexFileMeta::fileName).collect(Collectors.toList()));
+                        
outputs.stream().map(IndexFileMeta::fileName).collect(Collectors.toList()))
+                .doesNotContainAnyElementsOf(replacedFiles);
         Predicate predicate = new PredicateBuilder(table.rowType()).equal(1, 
1);
         try (DataEvolutionGlobalIndexScanner scanner =
                 DataEvolutionGlobalIndexScanner.create(table, null, 
predicate).get()) {
@@ -544,10 +747,15 @@ public class CompactReassignReuseTest extends 
TableTestBase {
         checkRows(table, 0, 1, 2, 3);
     }
 
-    @Test
-    void testMissingPlanFailsClosed() throws Exception {
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    void testMissingPlanFailsClosed(boolean indexBuild) throws Exception {
         FileStoreTable table = prepare();
-        CommitMessageImpl message = compact(table, false);
+        CommitMessageImpl message =
+                indexBuild
+                        ? (CommitMessageImpl) buildIndex(table, "id", 
"a").get(0)
+                        : compact(table, false);
+        lastSafeSnapshots.put(message, 
table.snapshotManager().latestSnapshotId());
         new DataEvolutionRowIdReassigner(table).reassign();
         Snapshot snapshot = table.snapshotManager().latestSnapshot();
         Path plan =
@@ -561,10 +769,15 @@ public class CompactReassignReuseTest extends 
TableTestBase {
         checkRows(table, 0, 1, 2, 3);
     }
 
-    @Test
-    void testCorruptPlanFailsClosed() throws Exception {
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    void testCorruptPlanFailsClosed(boolean indexBuild) throws Exception {
         FileStoreTable table = prepare();
-        CommitMessageImpl message = compact(table, false);
+        CommitMessageImpl message =
+                indexBuild
+                        ? (CommitMessageImpl) buildIndex(table, "id", 
"a").get(0)
+                        : compact(table, false);
+        lastSafeSnapshots.put(message, 
table.snapshotManager().latestSnapshotId());
         new DataEvolutionRowIdReassigner(table).reassign();
         Snapshot snapshot = table.snapshotManager().latestSnapshot();
         Path path =
@@ -579,20 +792,56 @@ public class CompactReassignReuseTest extends 
TableTestBase {
         checkRows(table, 0, 1, 2, 3);
     }
 
-    @Test
-    void testNoBoundaryKeepsExistingConflictCheck() throws Exception {
+    @ParameterizedTest
+    @CsvSource({"false,", "true,", "false,-1", "true,-1"})
+    void testNoBoundaryKeepsExistingConflictCheck(boolean indexBuild, Long 
lastSafeSnapshot)
+            throws Exception {
         FileStoreTable table = prepare();
-        CommitMessageImpl message = compact(table, false);
+        CommitMessageImpl message =
+                indexBuild
+                        ? (CommitMessageImpl) buildIndex(table, "id", 
"a").get(0)
+                        : compact(table, false);
         new DataEvolutionRowIdReassigner(table).reassign();
         long snapshot = table.snapshotManager().latestSnapshotId();
-        try (BatchTableCommit commit = 
table.newBatchWriteBuilder().newCommit()) {
+        Map<String, String> options = new HashMap<>();
+        options.put(CoreOptions.COMMIT_STRICT_MODE_ENABLED.key(), "false");
+        if (lastSafeSnapshot != null) {
+            options.put(CoreOptions.COMMIT_LAST_SAFE_SNAPSHOT.key(), 
lastSafeSnapshot.toString());
+        }
+        try (BatchTableCommit commit = 
table.copy(options).newBatchWriteBuilder().newCommit()) {
             assertThatThrownBy(() -> 
commit.commit(Collections.singletonList(message)))
-                    .hasStackTraceContaining("conflict");
+                    .hasStackTraceContaining(
+                            indexBuild
+                                    ? "Global index row ID existence conflict"
+                                    : "Row ID existence conflict");
         }
         
assertThat(table.snapshotManager().latestSnapshotId()).isEqualTo(snapshot);
         checkRows(table, 0, 1, 2, 3);
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = {"snapshot", "manifest-list"})
+    void testIndexBuildMissingMetadataFailsClosed(String missing) throws 
Exception {
+        FileStoreTable table = prepare();
+        CommitMessageImpl message = (CommitMessageImpl) buildIndex(table, 
"id", "a").get(0);
+        long boundary = table.snapshotManager().latestSnapshotId();
+        lastSafeSnapshots.put(message, boundary);
+        write(table, "b", 4, false);
+        new DataEvolutionRowIdReassigner(table).reassign();
+        Snapshot latest = table.snapshotManager().latestSnapshot();
+        boolean snapshotMissing = missing.equals("snapshot");
+        Path path =
+                snapshotMissing
+                        ? table.snapshotManager().snapshotPath(boundary + 1)
+                        : 
table.store().pathFactory().toManifestListPath(latest.baseManifestList());
+        assertThat(table.fileIO().delete(path, false)).isTrue();
+        assertThatThrownBy(() -> commit(table, message, false))
+                .hasStackTraceContaining(
+                        snapshotMissing ? "missing snapshot" : 
latest.baseManifestList());
+        
assertThat(table.snapshotManager().latestSnapshotId()).isEqualTo(latest.id());
+        assertThat(table.snapshotManager().snapshotExists(latest.id() + 
1)).isFalse();
+    }
+
     @Test
     void testExpiredBoundaryAndLegacyOption() throws Exception {
         FileStoreTable table = prepare();
@@ -606,10 +855,15 @@ public class CompactReassignReuseTest extends 
TableTestBase {
     }
 
     @ParameterizedTest
-    @ValueSource(booleans = {false, true})
-    void testReassignDuringCommitRetries(boolean loseSuccessfulResponse) 
throws Exception {
+    @CsvSource({"false,false", "false,true", "true,false", "true,true"})
+    void testReassignDuringCommitRetries(boolean indexBuild, boolean 
loseSuccessfulResponse)
+            throws Exception {
         FileStoreTable original = prepare();
-        CommitMessageImpl message = compact(original, false);
+        CommitMessageImpl message =
+                indexBuild
+                        ? (CommitMessageImpl) buildIndex(original, "id", 
"a").get(0)
+                        : compact(original, false);
+        lastSafeSnapshots.put(message, 
original.snapshotManager().latestSnapshotId());
         AtomicInteger attempts = new AtomicInteger();
         CatalogEnvironment environment =
                 new CatalogEnvironment(null, null, null, null, null, null, 
false, false) {
@@ -625,7 +879,10 @@ public class CompactReassignReuseTest extends 
TableTestBase {
                                     String branch,
                                     List<PartitionStatistics> statistics)
                                     throws Exception {
-                                if (snapshot.commitKind() != 
Snapshot.CommitKind.COMPACT) {
+                                if (snapshot.commitKind()
+                                        != (indexBuild
+                                                ? Snapshot.CommitKind.APPEND
+                                                : 
Snapshot.CommitKind.COMPACT)) {
                                     return delegate.commit(baseUuid, snapshot, 
branch, statistics);
                                 }
                                 int attempt = attempts.getAndIncrement();
@@ -663,7 +920,22 @@ public class CompactReassignReuseTest extends 
TableTestBase {
         assertThat(attempts).hasValue(3);
         assertThat(table.snapshotManager().latestSnapshotId()).isEqualTo(9);
         checkRows(table, 0, 1, 2, 3, 4, 5);
-        
assertThat(message.compactIncrement().compactAfter().get(0).nonNullFirstRowId()).isZero();
+        if (indexBuild) {
+            IndexFileMeta built = 
message.newFilesIncrement().newIndexFiles().get(0);
+            assertThat(built.globalIndexMeta().rowRange()).isEqualTo(new 
Range(0, 1));
+            assertThat(table.store().newIndexFileHandler().scanEntries())
+                    .singleElement()
+                    .satisfies(
+                            entry -> {
+                                assertThat(entry.indexFile().fileName())
+                                        .isEqualTo(built.fileName());
+                                
assertThat(entry.indexFile().globalIndexMeta().rowRange())
+                                        .isEqualTo(new Range(9, 10));
+                            });
+        } else {
+            
assertThat(message.compactIncrement().compactAfter().get(0).nonNullFirstRowId())
+                    .isZero();
+        }
     }
 
     @Test
@@ -682,8 +954,8 @@ public class CompactReassignReuseTest extends TableTestBase 
{
                         Collections.emptyList(),
                         Collections.emptyList(),
                         Collections.singletonList(unsupported));
-        ReassignCompactChangesProvider provider =
-                new ReassignCompactChangesProvider(
+        DataEvolutionChangesProvider provider =
+                new DataEvolutionChangesProvider(
                         table.fileIO(),
                         table.store().pathFactory(),
                         table.snapshotManager(),
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
index 99fd1f6532..b85329fd90 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
@@ -1010,8 +1010,10 @@ public class FileStoreCommitTest {
 
         try (FileStoreCommitImpl commit = store.newCommit()) {
             CommitChanges refreshed =
-                    commit.compactChangesProvider(
-                                    changes, 
Collections.singleton(Pair.of(partition, 0)))
+                    commit.dataEvolutionChangesProvider(
+                                    changes,
+                                    Snapshot.CommitKind.COMPACT,
+                                    Collections.singleton(Pair.of(partition, 
0)))
                             .provide(null);
 
             
assertThat(refreshed.indexFiles).containsExactly(otherBucketDelete);

Reply via email to