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 fb3484df6e [core][flink] Publish primary-key vector indexes on write 
(#8563)
fb3484df6e is described below

commit fb3484df6e0053a94ed281f43e2dad09d04cf704
Author: Jingsong Lee <[email protected]>
AuthorDate: Sun Jul 12 07:37:36 2026 +0800

    [core][flink] Publish primary-key vector indexes on write (#8563)
    
    Activates primary-key vector index maintenance in the write path on top
    of #8549 and #8562. Complete Level 1 compact files become ANN sources,
    and bucket-local ANN additions and removals are published with the same
    compact increment as their source data transition.
---
 .../benchmark/WriteRestoreScanBenchmark.java       |   1 +
 .../java/org/apache/paimon/KeyValueFileStore.java  |  16 +++
 .../org/apache/paimon/index/IndexFileHandler.java  |  17 +++
 .../compact/LookupMergeTreeCompactRewriter.java    |   6 +
 .../paimon/operation/AbstractFileStoreWrite.java   |  50 +++++++-
 .../paimon/operation/BaseAppendFileStoreWrite.java |  10 +-
 .../apache/paimon/operation/FileStoreWrite.java    |   7 +-
 .../paimon/operation/FileSystemWriteRestore.java   |  15 ++-
 .../paimon/operation/KeyValueFileStoreWrite.java   |   3 +
 .../paimon/operation/MemoryFileStoreWrite.java     |   3 +
 .../org/apache/paimon/operation/RestoreFiles.java  |  12 +-
 .../org/apache/paimon/operation/WriteRestore.java  |   3 +-
 .../postpone/PostponeBucketFileStoreWrite.java     |   2 +-
 .../LookupMergeTreeCompactRewriterTest.java        |  90 +++++++++++++++
 .../operation/FileSystemWriteRestoreTest.java      |  66 +++++++++++
 .../operation/PrimaryKeyVectorIndexWriteTest.java  | 126 +++++++++++++++++++++
 .../sink/coordinator/CoordinatedWriteRestore.java  |   9 +-
 .../sink/coordinator/ScanCoordinationRequest.java  |   9 +-
 .../sink/coordinator/ScanCoordinationResponse.java |  26 ++++-
 .../sink/coordinator/TableWriteCoordinator.java    |  17 ++-
 .../coordinator/TableWriteCoordinatorTest.java     |  55 ++++++++-
 .../flink/source/TestChangelogDataReadWrite.java   |   1 +
 22 files changed, 524 insertions(+), 20 deletions(-)

diff --git 
a/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/WriteRestoreScanBenchmark.java
 
b/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/WriteRestoreScanBenchmark.java
index 9edc73ed6d..a7dfcdeb0f 100644
--- 
a/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/WriteRestoreScanBenchmark.java
+++ 
b/paimon-benchmark/paimon-micro-benchmarks/src/test/java/org/apache/paimon/benchmark/WriteRestoreScanBenchmark.java
@@ -400,6 +400,7 @@ public class WriteRestoreScanBenchmark extends 
TableBenchmark {
                                                     entry.partition(),
                                                     entry.bucket(),
                                                     false,
+                                                    false,
                                                     false)));
         }
         for (Future<?> f : futures) {
diff --git a/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java 
b/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
index a1ae650b43..d7e195418b 100644
--- a/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
+++ b/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
@@ -24,6 +24,7 @@ import org.apache.paimon.deletionvectors.BucketedDvMaintainer;
 import org.apache.paimon.format.FileFormatDiscover;
 import org.apache.paimon.fs.FileIO;
 import org.apache.paimon.index.DynamicBucketIndexMaintainer;
+import org.apache.paimon.index.pkvector.BucketedVectorIndexMaintainer;
 import org.apache.paimon.io.KeyValueFileReaderFactory;
 import org.apache.paimon.mergetree.compact.MergeFunctionFactory;
 import org.apache.paimon.operation.AbstractFileStoreWrite;
@@ -38,6 +39,7 @@ import org.apache.paimon.schema.SchemaManager;
 import org.apache.paimon.schema.TableSchema;
 import org.apache.paimon.table.BucketMode;
 import org.apache.paimon.table.CatalogEnvironment;
+import org.apache.paimon.types.DataField;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.KeyComparatorSupplier;
 import org.apache.paimon.utils.UserDefinedSeqComparator;
@@ -169,6 +171,19 @@ public class KeyValueFileStore extends 
AbstractFileStore<KeyValue> {
         if (options.deletionVectorsEnabled()) {
             dvMaintainerFactory = 
BucketedDvMaintainer.factory(newIndexFileHandler());
         }
+        BucketedVectorIndexMaintainer.Factory vectorIndexMaintainerFactory = 
null;
+        if (options.primaryKeyVectorIndexEnabled()) {
+            String vectorColumn = options.primaryKeyVectorIndexColumn();
+            DataField vectorField = schema.nameToFieldMap().get(vectorColumn);
+            vectorIndexMaintainerFactory =
+                    new BucketedVectorIndexMaintainer.Factory(
+                                    newIndexFileHandler(),
+                                    newReaderFactoryBuilder(),
+                                    vectorField,
+                                    
options.primaryKeyVectorDistanceMetric(vectorColumn),
+                                    
options.primaryKeyVectorIndexType(vectorColumn))
+                            
.withIndexOptions(options.primaryKeyVectorIndexOptions(vectorColumn));
+        }
         return new KeyValueFileStoreWrite(
                 fileIO,
                 schemaManager,
@@ -187,6 +202,7 @@ public class KeyValueFileStore extends 
AbstractFileStore<KeyValue> {
                 newScan(),
                 indexFactory,
                 dvMaintainerFactory,
+                vectorIndexMaintainerFactory,
                 options,
                 keyValueFieldsExtractor,
                 tableName);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/index/IndexFileHandler.java 
b/paimon-core/src/main/java/org/apache/paimon/index/IndexFileHandler.java
index 92909a618d..799aa3ef66 100644
--- a/paimon-core/src/main/java/org/apache/paimon/index/IndexFileHandler.java
+++ b/paimon-core/src/main/java/org/apache/paimon/index/IndexFileHandler.java
@@ -145,6 +145,23 @@ public class IndexFileHandler {
         return result;
     }
 
+    public List<IndexFileMeta> scanSourceIndexes(
+            Snapshot snapshot, BinaryRow partition, int bucket) {
+        List<IndexFileMeta> result = new ArrayList<>();
+        for (IndexManifestEntry entry :
+                scan(
+                        snapshot,
+                        candidate ->
+                                candidate.partition().equals(partition)
+                                        && candidate.bucket() == bucket
+                                        && 
candidate.indexFile().globalIndexMeta() != null
+                                        && 
candidate.indexFile().globalIndexMeta().sourceMeta()
+                                                != null)) {
+            result.add(entry.indexFile());
+        }
+        return result;
+    }
+
     public Map<Pair<BinaryRow, Integer>, List<IndexFileMeta>> scan(
             long snapshot, String indexType, Set<BinaryRow> partitions) {
         return scan(snapshotManager.snapshot(snapshot), indexType, partitions);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/LookupMergeTreeCompactRewriter.java
 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/LookupMergeTreeCompactRewriter.java
index f566aca1c8..6976d214b3 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/LookupMergeTreeCompactRewriter.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/LookupMergeTreeCompactRewriter.java
@@ -58,6 +58,7 @@ public class LookupMergeTreeCompactRewriter<T> extends 
ChangelogMergeTreeRewrite
     private final LookupLevels<T> lookupLevels;
     private final MergeFunctionWrapperFactory<T> wrapperFactory;
     private final boolean noSequenceField;
+    private final boolean forceRewriteLevelZeroForVectorIndex;
     @Nullable private final BucketedDvMaintainer dvMaintainer;
     private final IntFunction<String> level2FileFormat;
 
@@ -93,6 +94,7 @@ public class LookupMergeTreeCompactRewriter<T> extends 
ChangelogMergeTreeRewrite
         this.lookupLevels = lookupLevels;
         this.wrapperFactory = wrapperFactory;
         this.noSequenceField = options.sequenceField().isEmpty();
+        this.forceRewriteLevelZeroForVectorIndex = 
options.primaryKeyVectorIndexEnabled();
         String fileFormat = options.fileFormatString();
         Map<Integer, String> fileFormatPerLevel = options.fileFormatPerLevel();
         this.level2FileFormat = level -> 
fileFormatPerLevel.getOrDefault(level, fileFormat);
@@ -135,6 +137,10 @@ public class LookupMergeTreeCompactRewriter<T> extends 
ChangelogMergeTreeRewrite
             return NO_CHANGELOG_NO_REWRITE;
         }
 
+        if (forceRewriteLevelZeroForVectorIndex) {
+            return CHANGELOG_WITH_REWRITE;
+        }
+
         // forcing rewriting when upgrading from level 0 to level x with 
different file formats
         if 
(!level2FileFormat.apply(file.level()).equals(level2FileFormat.apply(outputLevel)))
 {
             return CHANGELOG_WITH_REWRITE;
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreWrite.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreWrite.java
index fb1db4e841..1014ddd118 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreWrite.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/AbstractFileStoreWrite.java
@@ -29,6 +29,7 @@ import org.apache.paimon.deletionvectors.BucketedDvMaintainer;
 import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.index.DynamicBucketIndexMaintainer;
 import org.apache.paimon.index.IndexFileHandler;
+import org.apache.paimon.index.pkvector.BucketedVectorIndexMaintainer;
 import org.apache.paimon.io.CompactIncrement;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.io.DataIncrement;
@@ -83,6 +84,7 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
     private final int writerNumberMax;
     @Nullable private final DynamicBucketIndexMaintainer.Factory 
dbMaintainerFactory;
     @Nullable private final BucketedDvMaintainer.Factory dvMaintainerFactory;
+    @Nullable private final BucketedVectorIndexMaintainer.Factory 
vectorIndexMaintainerFactory;
     private final int numBuckets;
     private final RowType partitionType;
 
@@ -109,6 +111,7 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
             FileStoreScan scan,
             @Nullable DynamicBucketIndexMaintainer.Factory dbMaintainerFactory,
             @Nullable BucketedDvMaintainer.Factory dvMaintainerFactory,
+            @Nullable BucketedVectorIndexMaintainer.Factory 
vectorIndexMaintainerFactory,
             String tableName,
             CoreOptions options,
             RowType partitionType) {
@@ -117,11 +120,14 @@ public abstract class AbstractFileStoreWrite<T> 
implements FileStoreWrite<T> {
             indexFileHandler = dbMaintainerFactory.indexFileHandler();
         } else if (dvMaintainerFactory != null) {
             indexFileHandler = dvMaintainerFactory.indexFileHandler();
+        } else if (vectorIndexMaintainerFactory != null) {
+            indexFileHandler = vectorIndexMaintainerFactory.indexFileHandler();
         }
         this.snapshotManager = snapshotManager;
         this.restore = new FileSystemWriteRestore(options, snapshotManager, 
scan, indexFileHandler);
         this.dbMaintainerFactory = dbMaintainerFactory;
         this.dvMaintainerFactory = dvMaintainerFactory;
+        this.vectorIndexMaintainerFactory = vectorIndexMaintainerFactory;
         this.numBuckets = options.bucket();
         this.partitionType = partitionType;
         this.writers = new HashMap<>();
@@ -242,6 +248,33 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
                             .newIndexFiles()
                             
.addAll(writerContainer.dynamicBucketMaintainer.prepareCommit());
                 }
+                if (writerContainer.vectorIndexMaintainer != null) {
+                    BucketedVectorIndexMaintainer.VectorIndexCommit 
vectorCommit =
+                            
writerContainer.vectorIndexMaintainer.prepareCommit(
+                                    newFilesIncrement, compactIncrement);
+                    vectorCommit
+                            .appendIncrement()
+                            .ifPresent(
+                                    vectorIncrement -> {
+                                        newFilesIncrement
+                                                .newIndexFiles()
+                                                
.addAll(vectorIncrement.newIndexFiles());
+                                        newFilesIncrement
+                                                .deletedIndexFiles()
+                                                
.addAll(vectorIncrement.deletedIndexFiles());
+                                    });
+                    vectorCommit
+                            .compactIncrement()
+                            .ifPresent(
+                                    vectorIncrement -> {
+                                        compactIncrement
+                                                .newIndexFiles()
+                                                
.addAll(vectorIncrement.newIndexFiles());
+                                        compactIncrement
+                                                .deletedIndexFiles()
+                                                
.addAll(vectorIncrement.deletedIndexFiles());
+                                    });
+                }
                 CompactDeletionFile compactDeletionFile = 
increment.compactDeletionFile();
                 if (compactDeletionFile != null) {
                     compactDeletionFile
@@ -375,6 +408,7 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
                                 writerContainer.writer.maxSequenceNumber(),
                                 writerContainer.dynamicBucketMaintainer,
                                 writerContainer.deletionVectorsMaintainer,
+                                writerContainer.vectorIndexMaintainer,
                                 increment));
             }
         }
@@ -407,6 +441,7 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
                             state.totalBuckets,
                             state.indexMaintainer,
                             state.deletionVectorsMaintainer,
+                            state.vectorIndexMaintainer,
                             state.baseSnapshotId);
             writerContainer.lastModifiedCommitIdentifier = 
state.lastModifiedCommitIdentifier;
             writers.computeIfAbsent(state.partition, k -> new HashMap<>())
@@ -473,6 +508,14 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
                         ? null
                         : dvMaintainerFactory.create(
                                 partition, bucket, 
restored.deleteVectorsIndex());
+        BucketedVectorIndexMaintainer vectorIndexMaintainer =
+                vectorIndexMaintainerFactory == null
+                        ? null
+                        : vectorIndexMaintainerFactory.create(
+                                partition,
+                                bucket,
+                                restored.dataFiles(),
+                                restored.vectorIndexPayloads());
 
         List<DataFileMeta> restoreFiles = restored.dataFiles();
         if (restoreFiles == null) {
@@ -497,6 +540,7 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
                 firstNonNull(restored.totalBuckets(), numBuckets),
                 indexMaintainer,
                 dvMaintainer,
+                vectorIndexMaintainer,
                 previousSnapshot == null ? null : previousSnapshot.id());
     }
 
@@ -558,7 +602,8 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
                             partition,
                             bucket,
                             dbMaintainerFactory != null,
-                            dvMaintainerFactory != null);
+                            dvMaintainerFactory != null,
+                            vectorIndexMaintainerFactory != null);
         } catch (RuntimeException e) {
             throw new RuntimeException(
                     String.format(
@@ -621,6 +666,7 @@ public abstract class AbstractFileStoreWrite<T> implements 
FileStoreWrite<T> {
         public final int totalBuckets;
         @Nullable public final DynamicBucketIndexMaintainer 
dynamicBucketMaintainer;
         @Nullable public final BucketedDvMaintainer deletionVectorsMaintainer;
+        @Nullable public final BucketedVectorIndexMaintainer 
vectorIndexMaintainer;
         protected final long baseSnapshotId;
         protected long lastModifiedCommitIdentifier;
 
@@ -629,11 +675,13 @@ public abstract class AbstractFileStoreWrite<T> 
implements FileStoreWrite<T> {
                 int totalBuckets,
                 @Nullable DynamicBucketIndexMaintainer dynamicBucketMaintainer,
                 @Nullable BucketedDvMaintainer deletionVectorsMaintainer,
+                @Nullable BucketedVectorIndexMaintainer vectorIndexMaintainer,
                 Long baseSnapshotId) {
             this.writer = writer;
             this.totalBuckets = totalBuckets;
             this.dynamicBucketMaintainer = dynamicBucketMaintainer;
             this.deletionVectorsMaintainer = deletionVectorsMaintainer;
+            this.vectorIndexMaintainer = vectorIndexMaintainer;
             this.baseSnapshotId =
                     baseSnapshotId == null ? Snapshot.FIRST_SNAPSHOT_ID - 1 : 
baseSnapshotId;
             this.lastModifiedCommitIdentifier = Long.MIN_VALUE;
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
index 574143192e..0e4eeb9e4e 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
@@ -96,7 +96,15 @@ public abstract class BaseAppendFileStoreWrite extends 
MemoryFileStoreWrite<Inte
             CoreOptions options,
             @Nullable BucketedDvMaintainer.Factory dvMaintainerFactory,
             String tableName) {
-        super(snapshotManager, scan, options, partitionType, null, 
dvMaintainerFactory, tableName);
+        super(
+                snapshotManager,
+                scan,
+                options,
+                partitionType,
+                null,
+                dvMaintainerFactory,
+                null,
+                tableName);
         this.fileIO = fileIO;
         this.readForCompact = readForCompact;
         this.schemaId = schemaId;
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreWrite.java 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreWrite.java
index 24cb9c1287..5875840053 100644
--- a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreWrite.java
+++ b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreWrite.java
@@ -24,6 +24,7 @@ import org.apache.paimon.data.BlobConsumer;
 import org.apache.paimon.deletionvectors.BucketedDvMaintainer;
 import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.index.DynamicBucketIndexMaintainer;
+import org.apache.paimon.index.pkvector.BucketedVectorIndexMaintainer;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.memory.MemoryPoolFactory;
 import org.apache.paimon.metrics.MetricRegistry;
@@ -150,6 +151,7 @@ public interface FileStoreWrite<T> extends 
Restorable<List<FileStoreWrite.State<
         protected final long maxSequenceNumber;
         @Nullable protected final DynamicBucketIndexMaintainer indexMaintainer;
         @Nullable protected final BucketedDvMaintainer 
deletionVectorsMaintainer;
+        @Nullable protected final BucketedVectorIndexMaintainer 
vectorIndexMaintainer;
         protected final CommitIncrement commitIncrement;
 
         protected State(
@@ -162,6 +164,7 @@ public interface FileStoreWrite<T> extends 
Restorable<List<FileStoreWrite.State<
                 long maxSequenceNumber,
                 @Nullable DynamicBucketIndexMaintainer indexMaintainer,
                 @Nullable BucketedDvMaintainer deletionVectorsMaintainer,
+                @Nullable BucketedVectorIndexMaintainer vectorIndexMaintainer,
                 CommitIncrement commitIncrement) {
             this.partition = partition;
             this.bucket = bucket;
@@ -172,13 +175,14 @@ public interface FileStoreWrite<T> extends 
Restorable<List<FileStoreWrite.State<
             this.maxSequenceNumber = maxSequenceNumber;
             this.indexMaintainer = indexMaintainer;
             this.deletionVectorsMaintainer = deletionVectorsMaintainer;
+            this.vectorIndexMaintainer = vectorIndexMaintainer;
             this.commitIncrement = commitIncrement;
         }
 
         @Override
         public String toString() {
             return String.format(
-                    "{%s, %d, %d, %d, %d, %s, %d, %s, %s, %s}",
+                    "{%s, %d, %d, %d, %d, %s, %d, %s, %s, %s, %s}",
                     partition,
                     bucket,
                     totalBuckets,
@@ -188,6 +192,7 @@ public interface FileStoreWrite<T> extends 
Restorable<List<FileStoreWrite.State<
                     maxSequenceNumber,
                     indexMaintainer,
                     deletionVectorsMaintainer,
+                    vectorIndexMaintainer,
                     commitIncrement);
         }
     }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileSystemWriteRestore.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileSystemWriteRestore.java
index e7faf2a245..46e174cc21 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/FileSystemWriteRestore.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/FileSystemWriteRestore.java
@@ -67,7 +67,8 @@ public class FileSystemWriteRestore implements WriteRestore {
             BinaryRow partition,
             int bucket,
             boolean scanDynamicBucketIndex,
-            boolean scanDeleteVectorsIndex) {
+            boolean scanDeleteVectorsIndex,
+            boolean scanVectorIndexPayloads) {
         // NOTE: don't use snapshotManager.latestSnapshot() here,
         // because we don't want to flood the catalog with high concurrency
         Snapshot snapshot = snapshotManager.latestSnapshotFromFileSystem();
@@ -92,7 +93,17 @@ public class FileSystemWriteRestore implements WriteRestore {
                     indexFileHandler.scan(snapshot, DELETION_VECTORS_INDEX, 
partition, bucket);
         }
 
+        List<IndexFileMeta> vectorIndexPayloads = null;
+        if (scanVectorIndexPayloads) {
+            vectorIndexPayloads = indexFileHandler.scanSourceIndexes(snapshot, 
partition, bucket);
+        }
+
         return new RestoreFiles(
-                snapshot, totalBuckets, restoreFiles, dynamicBucketIndex, 
deleteVectorsIndex);
+                snapshot,
+                totalBuckets,
+                restoreFiles,
+                dynamicBucketIndex,
+                deleteVectorsIndex,
+                vectorIndexPayloads);
     }
 }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/KeyValueFileStoreWrite.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/KeyValueFileStoreWrite.java
index 1bbbfd2c30..df2faf2dc8 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/KeyValueFileStoreWrite.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/KeyValueFileStoreWrite.java
@@ -31,6 +31,7 @@ import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.format.FileFormatDiscover;
 import org.apache.paimon.fs.FileIO;
 import org.apache.paimon.index.DynamicBucketIndexMaintainer;
+import org.apache.paimon.index.pkvector.BucketedVectorIndexMaintainer;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.io.KeyValueFileReaderFactory;
 import org.apache.paimon.io.KeyValueFileWriterFactory;
@@ -96,6 +97,7 @@ public class KeyValueFileStoreWrite extends 
MemoryFileStoreWrite<KeyValue> {
             FileStoreScan scan,
             @Nullable DynamicBucketIndexMaintainer.Factory dbMaintainerFactory,
             @Nullable BucketedDvMaintainer.Factory dvMaintainerFactory,
+            @Nullable BucketedVectorIndexMaintainer.Factory 
vectorIndexMaintainerFactory,
             CoreOptions options,
             KeyValueFieldsExtractor extractor,
             String tableName) {
@@ -106,6 +108,7 @@ public class KeyValueFileStoreWrite extends 
MemoryFileStoreWrite<KeyValue> {
                 partitionType,
                 dbMaintainerFactory,
                 dvMaintainerFactory,
+                vectorIndexMaintainerFactory,
                 tableName);
         this.valueType = valueType;
         this.commitUser = commitUser;
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/MemoryFileStoreWrite.java
 
b/paimon-core/src/main/java/org/apache/paimon/operation/MemoryFileStoreWrite.java
index 06263d4ec6..2a178d83cd 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/operation/MemoryFileStoreWrite.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/operation/MemoryFileStoreWrite.java
@@ -21,6 +21,7 @@ package org.apache.paimon.operation;
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.deletionvectors.BucketedDvMaintainer;
 import org.apache.paimon.index.DynamicBucketIndexMaintainer;
+import org.apache.paimon.index.pkvector.BucketedVectorIndexMaintainer;
 import org.apache.paimon.io.cache.CacheManager;
 import org.apache.paimon.memory.HeapMemorySegmentPool;
 import org.apache.paimon.memory.MemoryOwner;
@@ -66,12 +67,14 @@ public abstract class MemoryFileStoreWrite<T> extends 
AbstractFileStoreWrite<T>
             RowType partitionType,
             @Nullable DynamicBucketIndexMaintainer.Factory dbMaintainerFactory,
             @Nullable BucketedDvMaintainer.Factory dvMaintainerFactory,
+            @Nullable BucketedVectorIndexMaintainer.Factory 
vectorIndexMaintainerFactory,
             String tableName) {
         super(
                 snapshotManager,
                 scan,
                 dbMaintainerFactory,
                 dvMaintainerFactory,
+                vectorIndexMaintainerFactory,
                 tableName,
                 options,
                 partitionType);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/RestoreFiles.java 
b/paimon-core/src/main/java/org/apache/paimon/operation/RestoreFiles.java
index 8005bda291..0887149819 100644
--- a/paimon-core/src/main/java/org/apache/paimon/operation/RestoreFiles.java
+++ b/paimon-core/src/main/java/org/apache/paimon/operation/RestoreFiles.java
@@ -34,18 +34,21 @@ public class RestoreFiles {
     private final @Nullable List<DataFileMeta> dataFiles;
     private final @Nullable IndexFileMeta dynamicBucketIndex;
     private final @Nullable List<IndexFileMeta> deleteVectorsIndex;
+    private final @Nullable List<IndexFileMeta> vectorIndexPayloads;
 
     public RestoreFiles(
             @Nullable Snapshot snapshot,
             @Nullable Integer totalBuckets,
             @Nullable List<DataFileMeta> dataFiles,
             @Nullable IndexFileMeta dynamicBucketIndex,
-            @Nullable List<IndexFileMeta> deleteVectorsIndex) {
+            @Nullable List<IndexFileMeta> deleteVectorsIndex,
+            @Nullable List<IndexFileMeta> vectorIndexPayloads) {
         this.snapshot = snapshot;
         this.totalBuckets = totalBuckets;
         this.dataFiles = dataFiles;
         this.dynamicBucketIndex = dynamicBucketIndex;
         this.deleteVectorsIndex = deleteVectorsIndex;
+        this.vectorIndexPayloads = vectorIndexPayloads;
     }
 
     @Nullable
@@ -73,7 +76,12 @@ public class RestoreFiles {
         return deleteVectorsIndex;
     }
 
+    @Nullable
+    public List<IndexFileMeta> vectorIndexPayloads() {
+        return vectorIndexPayloads;
+    }
+
     public static RestoreFiles empty() {
-        return new RestoreFiles(null, null, null, null, null);
+        return new RestoreFiles(null, null, null, null, null, null);
     }
 }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/operation/WriteRestore.java 
b/paimon-core/src/main/java/org/apache/paimon/operation/WriteRestore.java
index 5d4e335571..284d22575a 100644
--- a/paimon-core/src/main/java/org/apache/paimon/operation/WriteRestore.java
+++ b/paimon-core/src/main/java/org/apache/paimon/operation/WriteRestore.java
@@ -35,7 +35,8 @@ public interface WriteRestore {
             BinaryRow partition,
             int bucket,
             boolean scanDynamicBucketIndex,
-            boolean scanDeleteVectorsIndex);
+            boolean scanDeleteVectorsIndex,
+            boolean scanVectorIndexPayloads);
 
     @Nullable
     static Integer extractDataFiles(List<ManifestEntry> entries, 
List<DataFileMeta> dataFiles) {
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/postpone/PostponeBucketFileStoreWrite.java
 
b/paimon-core/src/main/java/org/apache/paimon/postpone/PostponeBucketFileStoreWrite.java
index 9fa8afcd67..43c51d7ee7 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/postpone/PostponeBucketFileStoreWrite.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/postpone/PostponeBucketFileStoreWrite.java
@@ -95,7 +95,7 @@ public class PostponeBucketFileStoreWrite extends 
MemoryFileStoreWrite<KeyValue>
             CoreOptions options,
             String tableName,
             @Nullable Integer writeId) {
-        super(snapshotManager, scan, options, partitionType, null, null, 
tableName);
+        super(snapshotManager, scan, options, partitionType, null, null, null, 
tableName);
         this.fileIO = fileIO;
         this.pathFactory = pathFactory;
         this.mfFactory = mfFactory;
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/LookupMergeTreeCompactRewriterTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/LookupMergeTreeCompactRewriterTest.java
new file mode 100644
index 0000000000..5c85b37df3
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/LookupMergeTreeCompactRewriterTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.mergetree.compact;
+
+import org.apache.paimon.CoreOptions;
+import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.io.FileReaderFactory;
+import org.apache.paimon.io.KeyValueFileWriterFactory;
+import org.apache.paimon.manifest.FileSource;
+import org.apache.paimon.mergetree.LookupLevels;
+import org.apache.paimon.mergetree.MergeSorter;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.stats.SimpleStats;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import java.util.Collections;
+import java.util.Comparator;
+
+import static 
org.apache.paimon.mergetree.compact.ChangelogMergeTreeRewriter.UpgradeStrategy.CHANGELOG_WITH_REWRITE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/** Tests for {@link LookupMergeTreeCompactRewriter}. */
+class LookupMergeTreeCompactRewriterTest {
+
+    @ParameterizedTest
+    @EnumSource(
+            value = CoreOptions.MergeEngine.class,
+            names = {"DEDUPLICATE", "PARTIAL_UPDATE"})
+    void testVectorIndexForcesLevelZeroRewrite(CoreOptions.MergeEngine 
mergeEngine) {
+        Options rawOptions = new Options();
+        rawOptions.set(CoreOptions.MERGE_ENGINE, mergeEngine);
+        rawOptions.set(CoreOptions.PK_VECTOR_INDEX_COLUMNS, "embedding");
+        CoreOptions options = new CoreOptions(rawOptions);
+        LookupMergeTreeCompactRewriter<Object> rewriter =
+                new LookupMergeTreeCompactRewriter<>(
+                        2,
+                        mergeEngine,
+                        mock(LookupLevels.class),
+                        mock(FileReaderFactory.class),
+                        mock(KeyValueFileWriterFactory.class),
+                        Comparator.comparingInt(row -> row.getInt(0)),
+                        null,
+                        mock(MergeFunctionFactory.class),
+                        mock(MergeSorter.class),
+                        
mock(LookupMergeTreeCompactRewriter.MergeFunctionWrapperFactory.class),
+                        false,
+                        null,
+                        options,
+                        null);
+
+        assertThat(rewriter.upgradeStrategy(2, 
levelZeroFile())).isEqualTo(CHANGELOG_WITH_REWRITE);
+    }
+
+    private static DataFileMeta levelZeroFile() {
+        return DataFileMeta.forAppend(
+                "data-1",
+                100,
+                10,
+                SimpleStats.EMPTY_STATS,
+                0,
+                0,
+                1,
+                Collections.emptyList(),
+                null,
+                FileSource.APPEND,
+                null,
+                null,
+                null,
+                null);
+    }
+}
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/operation/FileSystemWriteRestoreTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/operation/FileSystemWriteRestoreTest.java
new file mode 100644
index 0000000000..401439be91
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/operation/FileSystemWriteRestoreTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.operation;
+
+import org.apache.paimon.CoreOptions;
+import org.apache.paimon.Snapshot;
+import org.apache.paimon.index.IndexFileHandler;
+import org.apache.paimon.index.IndexFileMeta;
+import org.apache.paimon.utils.SnapshotManager;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+
+import static org.apache.paimon.data.BinaryRow.EMPTY_ROW;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link FileSystemWriteRestore}. */
+class FileSystemWriteRestoreTest {
+
+    @Test
+    void testRestoreVectorIndexPayloadsWithoutDirectory() {
+        Snapshot snapshot = mock(Snapshot.class);
+        SnapshotManager snapshotManager = mock(SnapshotManager.class);
+        
when(snapshotManager.latestSnapshotFromFileSystem()).thenReturn(snapshot);
+
+        FileStoreScan scan = mock(FileStoreScan.class);
+        FileStoreScan.Plan plan = mock(FileStoreScan.Plan.class);
+        when(scan.withSnapshot(snapshot)).thenReturn(scan);
+        when(scan.withPartitionBucket(EMPTY_ROW, 0)).thenReturn(scan);
+        when(scan.plan()).thenReturn(plan);
+        when(plan.files()).thenReturn(Collections.emptyList());
+
+        IndexFileMeta ann = new IndexFileMeta("test-vector-ann", "ann", 1, 1, 
null, null, null);
+        IndexFileHandler indexFileHandler = mock(IndexFileHandler.class);
+        when(indexFileHandler.scanSourceIndexes(snapshot, EMPTY_ROW, 0))
+                .thenReturn(Collections.singletonList(ann));
+
+        FileSystemWriteRestore restore =
+                new FileSystemWriteRestore(
+                        new CoreOptions(new HashMap<>()), snapshotManager, 
scan, indexFileHandler);
+
+        RestoreFiles restored = restore.restoreFiles(EMPTY_ROW, 0, false, 
false, true);
+
+        assertThat(restored.vectorIndexPayloads()).containsExactly(ann);
+    }
+}
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/operation/PrimaryKeyVectorIndexWriteTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/operation/PrimaryKeyVectorIndexWriteTest.java
new file mode 100644
index 0000000000..d2feef4c25
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/operation/PrimaryKeyVectorIndexWriteTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.operation;
+
+import org.apache.paimon.CoreOptions;
+import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.deletionvectors.BucketedDvMaintainer;
+import org.apache.paimon.index.IndexFileMeta;
+import org.apache.paimon.index.pkvector.BucketedVectorIndexMaintainer;
+import org.apache.paimon.io.CompactIncrement;
+import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.io.DataIncrement;
+import org.apache.paimon.table.sink.CommitMessageImpl;
+import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.CommitIncrement;
+import org.apache.paimon.utils.RecordWriter;
+import org.apache.paimon.utils.SnapshotManager;
+
+import org.junit.jupiter.api.Test;
+
+import javax.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
+
+import static org.apache.paimon.data.BinaryRow.EMPTY_ROW;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests primary-key vector index publication through {@link 
AbstractFileStoreWrite}. */
+class PrimaryKeyVectorIndexWriteTest {
+
+    @Test
+    void testPublishesVectorChangesWithCompactDataTransition() throws 
Exception {
+        DataIncrement dataIncrement = DataIncrement.emptyIncrement();
+        CompactIncrement compactIncrement = CompactIncrement.emptyIncrement();
+        RecordWriter<String> writer = mock(RecordWriter.class);
+        when(writer.prepareCommit(false))
+                .thenReturn(new CommitIncrement(dataIncrement, 
compactIncrement, null));
+
+        IndexFileMeta added = mock(IndexFileMeta.class);
+        IndexFileMeta deleted = mock(IndexFileMeta.class);
+        BucketedVectorIndexMaintainer.VectorIndexIncrement vectorIncrement =
+                mock(BucketedVectorIndexMaintainer.VectorIndexIncrement.class);
+        
when(vectorIncrement.newIndexFiles()).thenReturn(Collections.singletonList(added));
+        
when(vectorIncrement.deletedIndexFiles()).thenReturn(Collections.singletonList(deleted));
+        BucketedVectorIndexMaintainer.VectorIndexCommit vectorCommit =
+                mock(BucketedVectorIndexMaintainer.VectorIndexCommit.class);
+        when(vectorCommit.appendIncrement()).thenReturn(Optional.empty());
+        
when(vectorCommit.compactIncrement()).thenReturn(Optional.of(vectorIncrement));
+        BucketedVectorIndexMaintainer maintainer = 
mock(BucketedVectorIndexMaintainer.class);
+        when(maintainer.prepareCommit(same(dataIncrement), 
same(compactIncrement)))
+                .thenReturn(vectorCommit);
+
+        TestingFileStoreWrite write = new TestingFileStoreWrite();
+        write.install(writer, maintainer);
+
+        CommitMessageImpl message = (CommitMessageImpl) 
write.prepareCommit(false, 1).get(0);
+
+        
assertThat(message.compactIncrement().newIndexFiles()).containsExactly(added);
+        
assertThat(message.compactIncrement().deletedIndexFiles()).containsExactly(deleted);
+        verify(maintainer).prepareCommit(same(dataIncrement), 
same(compactIncrement));
+    }
+
+    private static class TestingFileStoreWrite extends 
AbstractFileStoreWrite<String> {
+
+        private TestingFileStoreWrite() {
+            super(
+                    mock(SnapshotManager.class),
+                    mock(FileStoreScan.class),
+                    null,
+                    null,
+                    null,
+                    "test-table",
+                    new CoreOptions(new HashMap<>()),
+                    RowType.of());
+        }
+
+        private void install(
+                RecordWriter<String> writer, BucketedVectorIndexMaintainer 
vectorMaintainer) {
+            writers.computeIfAbsent(EMPTY_ROW, ignored -> new HashMap<>())
+                    .put(0, new WriterContainer<>(writer, 1, null, null, 
vectorMaintainer, null));
+        }
+
+        @Override
+        protected Function<WriterContainer<String>, Boolean> 
createWriterCleanChecker() {
+            return writer -> false;
+        }
+
+        @Override
+        protected RecordWriter<String> createWriter(
+                BinaryRow partition,
+                int bucket,
+                List<DataFileMeta> restoreFiles,
+                long restoredMaxSeqNumber,
+                @Nullable CommitIncrement restoreIncrement,
+                ExecutorService compactExecutor,
+                @Nullable BucketedDvMaintainer deletionVectorsMaintainer,
+                boolean ignorePreviousFiles) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/CoordinatedWriteRestore.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/CoordinatedWriteRestore.java
index 31438cfbde..a9b3c3c9a2 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/CoordinatedWriteRestore.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/CoordinatedWriteRestore.java
@@ -71,13 +71,15 @@ public class CoordinatedWriteRestore implements 
WriteRestore {
             BinaryRow partition,
             int bucket,
             boolean scanDynamicBucketIndex,
-            boolean scanDeleteVectorsIndex) {
+            boolean scanDeleteVectorsIndex,
+            boolean scanVectorIndexPayloads) {
         ScanCoordinationRequest coordinationRequest =
                 new ScanCoordinationRequest(
                         serializeBinaryRow(partition),
                         bucket,
                         scanDynamicBucketIndex,
-                        scanDeleteVectorsIndex);
+                        scanDeleteVectorsIndex,
+                        scanVectorIndexPayloads);
         try {
             byte[] requestContent = serializeObject(coordinationRequest);
             Integer nextPageToken = null;
@@ -106,7 +108,8 @@ public class CoordinatedWriteRestore implements 
WriteRestore {
                     response.totalBuckets(),
                     response.extractDataFiles(),
                     response.extractDynamicBucketIndex(),
-                    response.extractDeletionVectorsIndex());
+                    response.extractDeletionVectorsIndex(),
+                    response.extractVectorIndexPayloads());
         } catch (IOException
                 | ExecutionException
                 | InterruptedException
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/ScanCoordinationRequest.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/ScanCoordinationRequest.java
index 81eb686753..b37c00a0ff 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/ScanCoordinationRequest.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/ScanCoordinationRequest.java
@@ -29,16 +29,19 @@ public class ScanCoordinationRequest implements 
CoordinationRequest {
     private final int bucket;
     private final boolean scanDynamicBucketIndex;
     private final boolean scanDeleteVectorsIndex;
+    private final boolean scanVectorIndexPayloads;
 
     public ScanCoordinationRequest(
             byte[] partition,
             int bucket,
             boolean scanDynamicBucketIndex,
-            boolean scanDeleteVectorsIndex) {
+            boolean scanDeleteVectorsIndex,
+            boolean scanVectorIndexPayloads) {
         this.partition = partition;
         this.bucket = bucket;
         this.scanDynamicBucketIndex = scanDynamicBucketIndex;
         this.scanDeleteVectorsIndex = scanDeleteVectorsIndex;
+        this.scanVectorIndexPayloads = scanVectorIndexPayloads;
     }
 
     public byte[] partition() {
@@ -56,4 +59,8 @@ public class ScanCoordinationRequest implements 
CoordinationRequest {
     public boolean scanDeleteVectorsIndex() {
         return scanDeleteVectorsIndex;
     }
+
+    public boolean scanVectorIndexPayloads() {
+        return scanVectorIndexPayloads;
+    }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/ScanCoordinationResponse.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/ScanCoordinationResponse.java
index 46e411e919..7fbc9150ee 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/ScanCoordinationResponse.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/ScanCoordinationResponse.java
@@ -42,13 +42,15 @@ public class ScanCoordinationResponse implements 
CoordinationResponse {
     @Nullable private final List<byte[]> dataFiles;
     @Nullable private final byte[] dynamicBucketIndex;
     @Nullable private final List<byte[]> deleteVectorsIndex;
+    @Nullable private final List<byte[]> vectorIndexPayloads;
 
     public ScanCoordinationResponse(
             @Nullable Snapshot snapshot,
             @Nullable Integer totalBuckets,
             @Nullable List<DataFileMeta> dataFiles,
             @Nullable IndexFileMeta dynamicBucketIndex,
-            @Nullable List<IndexFileMeta> deleteVectorsIndex)
+            @Nullable List<IndexFileMeta> deleteVectorsIndex,
+            @Nullable List<IndexFileMeta> vectorIndexPayloads)
             throws IOException {
         this.snapshot = snapshot;
         this.totalBuckets = totalBuckets;
@@ -78,6 +80,15 @@ public class ScanCoordinationResponse implements 
CoordinationResponse {
         } else {
             this.deleteVectorsIndex = null;
         }
+
+        if (vectorIndexPayloads != null) {
+            this.vectorIndexPayloads = new 
ArrayList<>(vectorIndexPayloads.size());
+            for (IndexFileMeta indexFile : vectorIndexPayloads) {
+                
this.vectorIndexPayloads.add(indexSerializer.serializeToBytes(indexFile));
+            }
+        } else {
+            this.vectorIndexPayloads = null;
+        }
     }
 
     @Nullable
@@ -123,4 +134,17 @@ public class ScanCoordinationResponse implements 
CoordinationResponse {
         }
         return metas;
     }
+
+    @Nullable
+    public List<IndexFileMeta> extractVectorIndexPayloads() throws IOException 
{
+        if (vectorIndexPayloads == null) {
+            return null;
+        }
+        IndexFileMetaSerializer serializer = new IndexFileMetaSerializer();
+        List<IndexFileMeta> metas = new 
ArrayList<>(vectorIndexPayloads.size());
+        for (byte[] file : vectorIndexPayloads) {
+            metas.add(serializer.deserializeFromBytes(file));
+        }
+        return metas;
+    }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/TableWriteCoordinator.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/TableWriteCoordinator.java
index 1cff11eeff..ac7d5475ee 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/TableWriteCoordinator.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/coordinator/TableWriteCoordinator.java
@@ -118,7 +118,8 @@ public class TableWriteCoordinator {
             throws IOException {
         if (snapshot == null) {
             return new PagedCoordinationResponse(
-                    serializeObject(new ScanCoordinationResponse(null, null, 
null, null, null)),
+                    serializeObject(
+                            new ScanCoordinationResponse(null, null, null, 
null, null, null)),
                     null);
         }
 
@@ -161,7 +162,7 @@ public class TableWriteCoordinator {
     public synchronized ScanCoordinationResponse scan(ScanCoordinationRequest 
request)
             throws IOException {
         if (snapshot == null) {
-            return new ScanCoordinationResponse(null, null, null, null, null);
+            return new ScanCoordinationResponse(null, null, null, null, null, 
null);
         }
 
         BinaryRow partition = deserializeBinaryRow(request.partition());
@@ -183,8 +184,18 @@ public class TableWriteCoordinator {
                     indexFileHandler.scan(snapshot, DELETION_VECTORS_INDEX, 
partition, bucket);
         }
 
+        List<IndexFileMeta> vectorIndexPayloads = null;
+        if (request.scanVectorIndexPayloads()) {
+            vectorIndexPayloads = indexFileHandler.scanSourceIndexes(snapshot, 
partition, bucket);
+        }
+
         return new ScanCoordinationResponse(
-                snapshot, totalBuckets, restoreFiles, dynamicBucketIndex, 
deleteVectorsIndex);
+                snapshot,
+                totalBuckets,
+                restoreFiles,
+                dynamicBucketIndex,
+                deleteVectorsIndex,
+                vectorIndexPayloads);
     }
 
     public synchronized long latestCommittedIdentifier(String user) {
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/coordinator/TableWriteCoordinatorTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/coordinator/TableWriteCoordinatorTest.java
index 16367204b6..7909580876 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/coordinator/TableWriteCoordinatorTest.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/coordinator/TableWriteCoordinatorTest.java
@@ -24,12 +24,20 @@ import org.apache.paimon.catalog.Identifier;
 import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.flink.FlinkConnectorOptions;
 import org.apache.paimon.fs.Path;
+import org.apache.paimon.index.GlobalIndexMeta;
+import org.apache.paimon.index.IndexFileMeta;
+import org.apache.paimon.index.pkvector.PkVectorSourceFile;
+import org.apache.paimon.index.pkvector.PkVectorSourceMeta;
+import org.apache.paimon.io.CompactIncrement;
+import org.apache.paimon.io.DataIncrement;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.options.MemorySize;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.schema.Schema;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.TableTestBase;
+import org.apache.paimon.table.sink.CommitMessageImpl;
+import org.apache.paimon.table.sink.TableCommitImpl;
 import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.utils.SegmentsCache;
 
@@ -39,6 +47,7 @@ import org.junit.jupiter.params.provider.ValueSource;
 
 import java.lang.reflect.Field;
 import java.time.Duration;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -75,12 +84,52 @@ class TableWriteCoordinatorTest extends TableTestBase {
 
         // scan should scan snapshot 2
         ScanCoordinationRequest request =
-                new ScanCoordinationRequest(serializeBinaryRow(EMPTY_ROW), 0, 
false, false);
+                new ScanCoordinationRequest(serializeBinaryRow(EMPTY_ROW), 0, 
false, false, false);
         ScanCoordinationResponse scan = coordinator.scan(request);
         assertThat(scan.snapshot().id()).isEqualTo(latest.id());
         assertThat(scan.extractDataFiles().size()).isEqualTo(initSnapshot ? 2 
: 1);
     }
 
+    @Test
+    public void testScanVectorIndexPayloads() throws Exception {
+        Identifier identifier = new Identifier("db", "table");
+        Schema schema = Schema.newBuilder().column("f0", 
DataTypes.INT()).build();
+        catalog.createDatabase("db", false);
+        catalog.createTable(identifier, schema, false);
+        FileStoreTable table = getTable(identifier);
+        write(table, GenericRow.of(1));
+
+        PkVectorSourceMeta sourceMeta =
+                new PkVectorSourceMeta(
+                        Collections.singletonList(new 
PkVectorSourceFile("data", 1)));
+        IndexFileMeta ann =
+                new IndexFileMeta(
+                        "test-vector-ann",
+                        "ann",
+                        1,
+                        1,
+                        new GlobalIndexMeta(0, 0, 0, null, new byte[0], 
sourceMeta.serialize()),
+                        null);
+        try (TableCommitImpl commit = table.newCommit("vector-index")) {
+            commit.commit(
+                    100,
+                    Collections.singletonList(
+                            new CommitMessageImpl(
+                                    EMPTY_ROW,
+                                    0,
+                                    1,
+                                    
DataIncrement.indexIncrement(Collections.singletonList(ann)),
+                                    CompactIncrement.emptyIncrement())));
+        }
+
+        TableWriteCoordinator coordinator = new TableWriteCoordinator(table);
+        ScanCoordinationRequest request =
+                new ScanCoordinationRequest(serializeBinaryRow(EMPTY_ROW), 0, 
false, false, true);
+        ScanCoordinationResponse scan = coordinator.scan(request);
+
+        assertThat(scan.extractVectorIndexPayloads()).containsExactly(ann);
+    }
+
     @Test
     public void testPrefetchManifestsWarmsCache() throws Exception {
         Identifier identifier = new Identifier("db", "table");
@@ -120,7 +169,7 @@ class TableWriteCoordinatorTest extends TableTestBase {
 
         // scan results remain correct after warming
         ScanCoordinationRequest request =
-                new ScanCoordinationRequest(serializeBinaryRow(EMPTY_ROW), 0, 
false, false);
+                new ScanCoordinationRequest(serializeBinaryRow(EMPTY_ROW), 0, 
false, false, false);
         ScanCoordinationResponse scan = coordinator.scan(request);
         
assertThat(scan.snapshot().id()).isEqualTo(table.latestSnapshot().get().id());
         assertThat(scan.extractDataFiles().size()).isEqualTo(2);
@@ -170,7 +219,7 @@ class TableWriteCoordinatorTest extends TableTestBase {
         // manifest, proving the bucket filter is active (and leaving the 
stale bucket state on the
         // shared scan)
         ScanCoordinationRequest request =
-                new ScanCoordinationRequest(serializeBinaryRow(EMPTY_ROW), 0, 
false, false);
+                new ScanCoordinationRequest(serializeBinaryRow(EMPTY_ROW), 0, 
false, false, false);
         coordinator.scan(request);
         long filteredCacheBytes = table.getManifestCache().totalCacheBytes();
         assertThat(filteredCacheBytes).isGreaterThan(0);
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/TestChangelogDataReadWrite.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/TestChangelogDataReadWrite.java
index 8114ac17eb..21edabbb42 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/TestChangelogDataReadWrite.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/TestChangelogDataReadWrite.java
@@ -195,6 +195,7 @@ public class TestChangelogDataReadWrite {
                         null, // not used, we only create an empty writer
                         null,
                         null,
+                        null,
                         options,
                         EXTRACTOR,
                         tablePath.getName());

Reply via email to