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 c2673f63d3 [core] Support row ID rewriting in scalar global indexes 
(#10678)
c2673f63d3 is described below

commit c2673f63d3a22fe21bbea4d0fb1aef140f4e9cfe
Author: Jingsong Lee <[email protected]>
AuthorDate: Tue Oct 20 12:30:16 2026 +0800

    [core] Support row ID rewriting in scalar global indexes (#10678)
---
 .../apache/paimon/globalindex/GlobalIndexer.java   |  21 ++
 .../globalindex/bitmap/BitmapGlobalIndexer.java    | 111 +++++++++++
 .../globalindex/btree/BTreeGlobalIndexer.java      |  82 ++++++++
 .../globalindex/GlobalIndexRowIdRewriteTest.java   | 213 +++++++++++++++++++++
 4 files changed, 427 insertions(+)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexer.java 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexer.java
index 9792602393..beb08f07c0 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexer.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexer.java
@@ -29,6 +29,7 @@ import javax.annotation.Nullable;
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
+import java.util.function.LongUnaryOperator;
 
 /** Abstract base class for global indexers. */
 public interface GlobalIndexer {
@@ -49,6 +50,26 @@ public interface GlobalIndexer {
             @Nullable List<Range> rowRanges,
             ExecutorService executor);
 
+    /**
+     * Rewrites stored index keys with mapped, relative row IDs without 
reading source data.
+     *
+     * <p>The input files share the relative row-ID domain {@code [0, 
sourceRowCount)}. The mapping
+     * returns a row ID in {@code [0, targetRowCount)}, or {@code -1} to 
exclude a source row.
+     * Output files share the target domain and may need to be read together, 
just like the input
+     * files.
+     */
+    default List<ResultEntry> rewriteRowIds(
+            GlobalIndexFileReader reader,
+            List<GlobalIndexIOMeta> files,
+            long sourceRowCount,
+            long targetRowCount,
+            LongUnaryOperator mapping,
+            GlobalIndexFileWriter writer)
+            throws IOException {
+        throw new UnsupportedOperationException(
+                "This global index does not support rewriting stored row 
IDs.");
+    }
+
     static GlobalIndexer create(String type, List<DataField> indexFields, 
Options options) {
         return GlobalIndexerFactoryUtils.load(type).create(indexFields, 
options);
     }
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/BitmapGlobalIndexer.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/BitmapGlobalIndexer.java
index c63ded522f..a5711abcfc 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/BitmapGlobalIndexer.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/bitmap/BitmapGlobalIndexer.java
@@ -20,23 +20,33 @@ package org.apache.paimon.globalindex.bitmap;
 
 import org.apache.paimon.compression.BlockCompressionFactory;
 import org.apache.paimon.compression.CompressOptions;
+import org.apache.paimon.fs.PositionOutputStream;
+import org.apache.paimon.fs.SeekableInputStream;
 import org.apache.paimon.globalindex.GlobalIndexIOMeta;
 import org.apache.paimon.globalindex.GlobalIndexKeyExtractor;
 import org.apache.paimon.globalindex.GlobalIndexReader;
 import org.apache.paimon.globalindex.GlobalIndexer;
 import org.apache.paimon.globalindex.KeySerializer;
+import org.apache.paimon.globalindex.ResultEntry;
 import org.apache.paimon.globalindex.SortedGlobalIndexer;
+import org.apache.paimon.globalindex.SortedIndexFileMeta;
 import org.apache.paimon.globalindex.io.GlobalIndexFileReader;
 import org.apache.paimon.globalindex.io.GlobalIndexFileWriter;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.types.DataField;
+import org.apache.paimon.utils.IOUtils;
 import org.apache.paimon.utils.Range;
+import org.apache.paimon.utils.RoaringNavigableMap64;
 
 import javax.annotation.Nullable;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
+import java.util.function.LongUnaryOperator;
+
+import static org.apache.paimon.utils.Preconditions.checkArgument;
 
 /** The {@link GlobalIndexer} for bitmap index. */
 public class BitmapGlobalIndexer implements SortedGlobalIndexer {
@@ -86,4 +96,105 @@ public class BitmapGlobalIndexer implements 
SortedGlobalIndexer {
         return new LazyFilteredBitmapReader(
                 fileReader, files, keySerializer, fallbackScanMaxSize, 
totalRowCount, executor);
     }
+
+    @Override
+    public List<ResultEntry> rewriteRowIds(
+            GlobalIndexFileReader fileReader,
+            List<GlobalIndexIOMeta> files,
+            long sourceRowCount,
+            long targetRowCount,
+            LongUnaryOperator mapping,
+            GlobalIndexFileWriter fileWriter)
+            throws IOException {
+        checkArgument(sourceRowCount >= 0 && targetRowCount >= 0, "Invalid row 
count.");
+        List<ResultEntry> results = new ArrayList<>();
+        if (targetRowCount == 0) {
+            return results;
+        }
+        for (GlobalIndexIOMeta file : files) {
+            try (SeekableInputStream input = fileReader.getInputStream(file)) {
+                BitmapGlobalIndexFormat.SeekableReader reader =
+                        (offset, length) -> {
+                            input.seek(offset);
+                            byte[] bytes = new byte[length];
+                            IOUtils.readFully(input, bytes);
+                            return bytes;
+                        };
+                BitmapGlobalIndexFormat.Footer footer =
+                        BitmapGlobalIndexFormat.readFooter(reader, 
file.fileSize());
+                RoaringNavigableMap64 nullRows =
+                        mapRowIds(
+                                BitmapGlobalIndexFormat.readBitmap(reader, 
footer.nullRowsBlock),
+                                sourceRowCount,
+                                targetRowCount,
+                                mapping);
+                RoaringNavigableMap64 nonNullRows =
+                        mapRowIds(
+                                BitmapGlobalIndexFormat.readBitmap(reader, 
footer.nonNullRowsBlock),
+                                sourceRowCount,
+                                targetRowCount,
+                                mapping);
+                if (nullRows.isEmpty() && nonNullRows.isEmpty()) {
+                    continue;
+                }
+                String fileName = 
fileWriter.newFileName(BitmapGlobalIndexerFactory.IDENTIFIER);
+                byte[] firstKey = null;
+                byte[] lastKey = null;
+                try (PositionOutputStream output = 
fileWriter.newOutputStream(fileName)) {
+                    BitmapGlobalIndexFormat.StreamingWriter writer =
+                            new BitmapGlobalIndexFormat.StreamingWriter(
+                                    output, dictionaryBlockSize, 
compressionFactory);
+                    for (BitmapGlobalIndexFormat.DictionaryBlockMeta block :
+                            BitmapGlobalIndexFormat.readIndexBlock(reader, 
footer.indexBlock)) {
+                        for (BitmapGlobalIndexFormat.DictionaryEntry entry :
+                                
BitmapGlobalIndexFormat.readDictionaryBlock(reader, block)
+                                        .entries()) {
+                            RoaringNavigableMap64 rowIds =
+                                    mapRowIds(
+                                            BitmapGlobalIndexFormat.readBitmap(
+                                                    reader, 
entry.bitmapBlock()),
+                                            sourceRowCount,
+                                            targetRowCount,
+                                            mapping);
+                            if (!rowIds.isEmpty()) {
+                                // Dictionary order is unchanged, so encoded 
keys need no decoding.
+                                writer.write(entry.key(), rowIds);
+                                if (firstKey == null) {
+                                    firstKey = entry.key().bytes();
+                                }
+                                lastKey = entry.key().bytes();
+                            }
+                        }
+                    }
+                    writer.finish(nullRows, nonNullRows);
+                }
+                results.add(
+                        new ResultEntry(
+                                fileName,
+                                nullRows.getLongCardinality() + 
nonNullRows.getLongCardinality(),
+                                new SortedIndexFileMeta(firstKey, lastKey, 
!nullRows.isEmpty())
+                                        .serialize()));
+            }
+        }
+        return results;
+    }
+
+    private static RoaringNavigableMap64 mapRowIds(
+            RoaringNavigableMap64 rows,
+            long sourceRowCount,
+            long targetRowCount,
+            LongUnaryOperator mapping) {
+        RoaringNavigableMap64 result = new RoaringNavigableMap64();
+        for (long source : rows) {
+            checkArgument(
+                    source >= 0 && source < sourceRowCount, "Invalid source 
row ID: %s", source);
+            long target = mapping.applyAsLong(source);
+            checkArgument(
+                    target >= -1 && target < targetRowCount, "Invalid target 
row ID: %s", target);
+            if (target >= 0) {
+                result.add(target);
+            }
+        }
+        return result;
+    }
 }
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexer.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexer.java
index e8db64056d..8733bc2e10 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexer.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexer.java
@@ -26,6 +26,7 @@ import org.apache.paimon.globalindex.GlobalIndexKeyExtractor;
 import org.apache.paimon.globalindex.GlobalIndexReader;
 import org.apache.paimon.globalindex.GlobalIndexer;
 import org.apache.paimon.globalindex.KeySerializer;
+import org.apache.paimon.globalindex.ResultEntry;
 import org.apache.paimon.globalindex.SortedGlobalIndexer;
 import org.apache.paimon.globalindex.io.GlobalIndexFileReader;
 import org.apache.paimon.globalindex.io.GlobalIndexFileWriter;
@@ -37,6 +38,7 @@ import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.BloomFilter;
 import org.apache.paimon.utils.LazyField;
 import org.apache.paimon.utils.Range;
+import org.apache.paimon.utils.RoaringNavigableMap64;
 
 import javax.annotation.Nullable;
 
@@ -44,6 +46,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
+import java.util.function.LongUnaryOperator;
 
 import static org.apache.paimon.utils.Preconditions.checkArgument;
 
@@ -152,4 +155,83 @@ public class BTreeGlobalIndexer implements 
SortedGlobalIndexer {
                 rowRanges,
                 executor);
     }
+
+    @Override
+    public List<ResultEntry> rewriteRowIds(
+            GlobalIndexFileReader fileReader,
+            List<GlobalIndexIOMeta> files,
+            long sourceRowCount,
+            long targetRowCount,
+            LongUnaryOperator mapping,
+            GlobalIndexFileWriter fileWriter)
+            throws IOException {
+        checkArgument(sourceRowCount >= 0 && targetRowCount >= 0, "Invalid row 
count.");
+        List<ResultEntry> results = new ArrayList<>();
+        if (targetRowCount == 0) {
+            return results;
+        }
+        for (GlobalIndexIOMeta file : files) {
+            try (BTreeIndexReader reader =
+                    new BTreeIndexReader(
+                            keySerializer, fileReader, file, 
cacheManager.get(), null)) {
+                RoaringNavigableMap64 nullRows = new RoaringNavigableMap64();
+                reader.scanNullRowIds(
+                        rowId ->
+                                addMappedRowId(
+                                        nullRows, rowId, sourceRowCount, 
targetRowCount, mapping));
+                BTreeIndexWriter writer = null;
+                try {
+                    if (!nullRows.isEmpty()) {
+                        writer = createWriter(fileWriter);
+                        for (long rowId : nullRows) {
+                            writer.write(null, rowId);
+                        }
+                    }
+                    BTreeIndexReader.EntryIterator entries = 
reader.entryIterator();
+                    while (entries.hasNext()) {
+                        BTreeIndexReader.KeyRowIds entry = entries.next();
+                        RoaringNavigableMap64 rowIds = new 
RoaringNavigableMap64();
+                        for (long rowId : entry.rowIds()) {
+                            addMappedRowId(rowIds, rowId, sourceRowCount, 
targetRowCount, mapping);
+                        }
+                        if (!rowIds.isEmpty()) {
+                            if (writer == null) {
+                                writer = createWriter(fileWriter);
+                            }
+                            // A permutation can reverse a posting list; v2 
requires sorted,
+                            // unique IDs.
+                            for (long rowId : rowIds) {
+                                writer.write(entry.key(), rowId);
+                            }
+                        }
+                    }
+                    if (writer != null) {
+                        results.addAll(writer.finish());
+                    }
+                } finally {
+                    if (writer != null) {
+                        writer.close();
+                    }
+                }
+            }
+        }
+        return results;
+    }
+
+    private static void addMappedRowId(
+            RoaringNavigableMap64 result,
+            long sourceRowId,
+            long sourceRowCount,
+            long targetRowCount,
+            LongUnaryOperator mapping) {
+        checkArgument(
+                sourceRowId >= 0 && sourceRowId < sourceRowCount,
+                "Invalid source row ID: %s",
+                sourceRowId);
+        long target = mapping.applyAsLong(sourceRowId);
+        checkArgument(target >= -1 && target < targetRowCount, "Invalid target 
row ID: %s", target);
+        if (target >= 0) {
+            result.add(target);
+        }
+    }
 }
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexRowIdRewriteTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexRowIdRewriteTest.java
new file mode 100644
index 0000000000..84cf6b9963
--- /dev/null
+++ 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexRowIdRewriteTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.globalindex;
+
+import org.apache.paimon.fs.FileIO;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.fs.PositionOutputStream;
+import org.apache.paimon.fs.local.LocalFileIO;
+import org.apache.paimon.globalindex.bitmap.BitmapGlobalIndexer;
+import org.apache.paimon.globalindex.btree.BTreeGlobalIndexer;
+import org.apache.paimon.globalindex.btree.BTreeIndexOptions;
+import org.apache.paimon.globalindex.io.GlobalIndexFileReader;
+import org.apache.paimon.globalindex.io.GlobalIndexFileWriter;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.predicate.FieldRef;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.IntType;
+import org.apache.paimon.utils.Pair;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static 
org.apache.paimon.shade.guava30.com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests scalar index payload rewrites through query results. */
+class GlobalIndexRowIdRewriteTest {
+
+    private static final DataField FIELD = new DataField(0, "value", new 
IntType());
+    private static final FieldRef REF = new FieldRef(0, "value", new 
IntType());
+
+    @TempDir java.nio.file.Path tempPath;
+
+    private FileIO fileIO;
+    private Path basePath;
+    private GlobalIndexFileReader fileReader;
+    private GlobalIndexFileWriter fileWriter;
+    private AtomicInteger outputFiles;
+
+    @BeforeEach
+    void setUp() {
+        fileIO = LocalFileIO.create();
+        basePath = new Path(tempPath.toUri());
+        outputFiles = new AtomicInteger();
+        fileReader = meta -> fileIO.newInputStream(meta.filePath());
+        fileWriter =
+                new GlobalIndexFileWriter() {
+                    @Override
+                    public String newFileName(String prefix) {
+                        return prefix + "-" + UUID.randomUUID();
+                    }
+
+                    @Override
+                    public PositionOutputStream newOutputStream(String 
fileName)
+                            throws IOException {
+                        outputFiles.incrementAndGet();
+                        return fileIO.newOutputStream(new Path(basePath, 
fileName), false);
+                    }
+                };
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"btree-v1", "btree-v2", "bitmap"})
+    void testPermutationAndRemovalAcrossPayloadFiles(String type) throws 
Exception {
+        GlobalIndexer indexer = indexer(type);
+        List<GlobalIndexIOMeta> source = new ArrayList<>();
+        source.addAll(write(indexer, 0, 2, null, 1, 2));
+        source.addAll(write(indexer, 4, -3, 4, null, 2));
+        long[] mapping = {3, 1, -1, 0, 4, -1, 2, 5};
+        List<ResultEntry> rewritten =
+                indexer.rewriteRowIds(
+                        fileReader, source, 8, 6, id -> mapping[(int) id], 
fileWriter);
+        assertThat(rewritten).hasSize(2);
+        
assertThat(rewritten.stream().mapToLong(ResultEntry::rowCount).sum()).isEqualTo(6);
+        try (GlobalIndexReader reader = reader(indexer, rewritten, 6)) {
+            assertRows(reader.visitEqual(REF, 2).join(), 0, 3, 5);
+            assertRows(reader.visitEqual(REF, -3).join(), 4);
+            assertRows(reader.visitEqual(REF, 1).join());
+            assertRows(reader.visitEqual(REF, 4).join());
+            assertRows(reader.visitIsNull(REF).join(), 1, 2);
+            assertRows(reader.visitIsNotNull(REF).join(), 0, 3, 4, 5);
+            assertRows(reader.visitNotEqual(REF, 2).join(), 4);
+            assertRows(reader.visitLessThan(REF, 0).join(), 4);
+        }
+        // A rewrite must leave the old payload usable by readers pinned to 
its snapshot.
+        try (GlobalIndexReader reader =
+                indexer.createReader(fileReader, source, 8, null, 
newDirectExecutorService())) {
+            assertRows(reader.visitEqual(REF, 2).join(), 0, 3, 7);
+            assertRows(reader.visitIsNull(REF).join(), 1, 6);
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"btree-v1", "btree-v2", "bitmap"})
+    void testMappedPostingIdsAreSortedAndUnique(String type) throws Exception {
+        GlobalIndexer indexer = indexer(type);
+        List<GlobalIndexIOMeta> source = write(indexer, 0, 7, 7, null, 7);
+        long[] mapping = {2, 1, 0, 1};
+        List<ResultEntry> rewritten =
+                indexer.rewriteRowIds(
+                        fileReader, source, 4, 3, id -> mapping[(int) id], 
fileWriter);
+        assertThat(rewritten).hasSize(1);
+        assertThat(rewritten.get(0).rowCount()).isEqualTo(3);
+        try (GlobalIndexReader reader = reader(indexer, rewritten, 3)) {
+            assertRows(reader.visitEqual(REF, 7).join(), 1, 2);
+            assertRows(reader.visitIsNull(REF).join(), 0);
+            assertRows(reader.visitNotEqual(REF, 8).join(), 1, 2);
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"btree-v1", "btree-v2", "bitmap"})
+    void testNullOnlyAndFullyExcludedPayloads(String type) throws Exception {
+        GlobalIndexer indexer = indexer(type);
+        List<GlobalIndexIOMeta> source = write(indexer, 0, null, null);
+        List<ResultEntry> rewritten =
+                indexer.rewriteRowIds(fileReader, source, 2, 2, id -> 1 - id, 
fileWriter);
+        try (GlobalIndexReader reader = reader(indexer, rewritten, 2)) {
+            assertRows(reader.visitIsNull(REF).join(), 0, 1);
+            assertRows(reader.visitIsNotNull(REF).join());
+        }
+        int before = outputFiles.get();
+        assertThat(indexer.rewriteRowIds(fileReader, source, 2, 1, id -> -1, 
fileWriter)).isEmpty();
+        assertThat(indexer.rewriteRowIds(fileReader, source, 2, 0, id -> -1, 
fileWriter)).isEmpty();
+        assertThat(outputFiles).hasValue(before);
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"btree-v1", "btree-v2", "bitmap"})
+    void testRejectsMappingOutsideTargetDomain(String type) throws Exception {
+        GlobalIndexer indexer = indexer(type);
+        List<GlobalIndexIOMeta> source = write(indexer, 0, 7);
+        assertThatThrownBy(
+                        () -> indexer.rewriteRowIds(fileReader, source, 1, 1, 
id -> 1, fileWriter))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid target row ID");
+    }
+
+    private GlobalIndexer indexer(String type) {
+        Options options = new Options();
+        if (type.equals("bitmap")) {
+            return new BitmapGlobalIndexer(FIELD, options);
+        }
+        options.set(BTreeIndexOptions.BTREE_INDEX_FILE_VERSION, 
type.equals("btree-v1") ? 1 : 2);
+        return new BTreeGlobalIndexer(Collections.singletonList(FIELD), 
options);
+    }
+
+    private List<GlobalIndexIOMeta> write(GlobalIndexer indexer, long start, 
Integer... values)
+            throws IOException {
+        List<Pair<Integer, Long>> rows = new ArrayList<>();
+        for (int i = 0; i < values.length; i++) {
+            rows.add(Pair.of(values[i], start + i));
+        }
+        Comparator<Integer> comparator = 
Comparator.nullsFirst(Integer::compareTo);
+        rows.sort((left, right) -> comparator.compare(left.getKey(), 
right.getKey()));
+        GlobalIndexSingleColumnWriter writer =
+                (GlobalIndexSingleColumnWriter) 
indexer.createWriter(fileWriter);
+        for (Pair<Integer, Long> row : rows) {
+            writer.write(row.getKey(), row.getValue());
+        }
+        return toMetas(writer.finish());
+    }
+
+    private GlobalIndexReader reader(
+            GlobalIndexer indexer, List<ResultEntry> entries, long rowCount) 
throws IOException {
+        return indexer.createReader(
+                fileReader, toMetas(entries), rowCount, null, 
newDirectExecutorService());
+    }
+
+    private List<GlobalIndexIOMeta> toMetas(List<ResultEntry> entries) throws 
IOException {
+        List<GlobalIndexIOMeta> result = new ArrayList<>();
+        for (ResultEntry entry : entries) {
+            Path path = new Path(basePath, entry.fileName());
+            result.add(
+                    new GlobalIndexIOMeta(
+                            path, fileIO.getFileSize(path), entry.rowCount(), 
entry.meta()));
+        }
+        return result;
+    }
+
+    private void assertRows(Optional<GlobalIndexResult> result, long... 
expected) {
+        assertThat(result).isPresent();
+        
assertThat(result.get().results().toArray(Integer.MAX_VALUE)).containsExactly(expected);
+    }
+}

Reply via email to