This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 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 4cbbb99935 [core] Add serializer/deserialize for GlobalIndexResult
(#6810)
4cbbb99935 is described below
commit 4cbbb99935adf21f41133f20233e39eef4031a17
Author: YeJunHao <[email protected]>
AuthorDate: Fri Dec 12 20:20:42 2025 +0800
[core] Add serializer/deserialize for GlobalIndexResult (#6810)
---
.../globalindex/GlobalIndexResultSerializer.java | 119 ++++++++++++++++++++
.../paimon/globalindex/TopkGlobalIndexResult.java | 56 ++++++++++
.../apache/paimon/utils/RoaringNavigableMap64.java | 16 ++-
.../globalindex/GlobalIndexSerDeUtilsTest.java | 121 +++++++++++++++++++++
.../paimon/utils/RoaringNavigableMap64Test.java | 111 +++++++++++++++++++
.../paimon/globalindex/DataEvolutionBatchScan.java | 19 +++-
.../paimon/table/DataEvolutionTableTest.java | 10 +-
7 files changed, 441 insertions(+), 11 deletions(-)
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexResultSerializer.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexResultSerializer.java
new file mode 100644
index 0000000000..5253b59522
--- /dev/null
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/GlobalIndexResultSerializer.java
@@ -0,0 +1,119 @@
+/*
+ * 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.data.serializer.Serializer;
+import org.apache.paimon.io.DataInputDeserializer;
+import org.apache.paimon.io.DataInputView;
+import org.apache.paimon.io.DataOutputSerializer;
+import org.apache.paimon.io.DataOutputView;
+import org.apache.paimon.utils.RoaringNavigableMap64;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.paimon.utils.Preconditions.checkArgument;
+
+/** GlobalIndexResultSerializer to serialize and deserialize
GlobalIndexResult. */
+public class GlobalIndexResultSerializer implements
Serializer<GlobalIndexResult> {
+
+ private static final int VERSION = 1;
+
+ @Override
+ public Serializer<GlobalIndexResult> duplicate() {
+ return this;
+ }
+
+ @Override
+ public GlobalIndexResult copy(GlobalIndexResult from) {
+ try {
+ DataOutputSerializer dataOutputSerializer = new
DataOutputSerializer(1024);
+ serialize(from, dataOutputSerializer);
+
+ DataInputDeserializer dataInputDeserializer =
+ new
DataInputDeserializer(dataOutputSerializer.getCopyOfBuffer());
+ return deserialize(dataInputDeserializer);
+ } catch (IOException e) {
+ throw new RuntimeException("Copy failed", e);
+ }
+ }
+
+ @Override
+ public void serialize(GlobalIndexResult globalIndexResult, DataOutputView
dataOutput)
+ throws IOException {
+ dataOutput.writeInt(VERSION);
+
+ RoaringNavigableMap64 roaringNavigableMap64 =
globalIndexResult.results();
+ byte[] bytes = roaringNavigableMap64.serialize();
+
+ dataOutput.writeInt(bytes.length);
+ dataOutput.write(bytes);
+
+ if (globalIndexResult instanceof TopkGlobalIndexResult) {
+ TopkGlobalIndexResult topkGlobalIndexResult =
(TopkGlobalIndexResult) globalIndexResult;
+ dataOutput.writeInt(roaringNavigableMap64.getIntCardinality());
+ ScoreGetter scoreGetter = topkGlobalIndexResult.scoreGetter();
+ for (Long rowId : roaringNavigableMap64) {
+ dataOutput.writeFloat(scoreGetter.score(rowId));
+ }
+ } else {
+ dataOutput.writeInt(0);
+ }
+ }
+
+ @Override
+ public GlobalIndexResult deserialize(DataInputView dataInput) throws
IOException {
+ int version = dataInput.readInt();
+ if (version != VERSION) {
+ throw new IllegalStateException("Invalid version: " + version);
+ }
+
+ int size = dataInput.readInt();
+ byte[] bytes = new byte[size];
+ dataInput.readFully(bytes);
+
+ RoaringNavigableMap64 roaringNavigableMap64 = new
RoaringNavigableMap64();
+ roaringNavigableMap64.deserialize(bytes);
+ int scoreSize = dataInput.readInt();
+
+ if (scoreSize == 0) {
+ return GlobalIndexResult.create(() -> roaringNavigableMap64);
+ }
+ checkArgument(
+ scoreSize == roaringNavigableMap64.getIntCardinality(),
+ "Error size of score: "
+ + scoreSize
+ + ", expected: "
+ + roaringNavigableMap64.getIntCardinality());
+
+ float[] scores = new float[scoreSize];
+ for (int i = 0; i < scoreSize; i++) {
+ scores[i] = dataInput.readFloat();
+ }
+
+ Map<Long, Float> scoreMap = new HashMap<>();
+ int i = 0;
+ for (Long rowId : roaringNavigableMap64) {
+ scoreMap.put(rowId, scores[i++]);
+ }
+
+ return TopkGlobalIndexResult.create(() -> roaringNavigableMap64,
scoreMap::get);
+ }
+}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/globalindex/TopkGlobalIndexResult.java
b/paimon-common/src/main/java/org/apache/paimon/globalindex/TopkGlobalIndexResult.java
index 12e15260f4..eaac93fa75 100644
---
a/paimon-common/src/main/java/org/apache/paimon/globalindex/TopkGlobalIndexResult.java
+++
b/paimon-common/src/main/java/org/apache/paimon/globalindex/TopkGlobalIndexResult.java
@@ -18,8 +18,64 @@
package org.apache.paimon.globalindex;
+import org.apache.paimon.utils.LazyField;
+import org.apache.paimon.utils.RoaringNavigableMap64;
+
+import java.util.function.Supplier;
+
/** Top-k global index result for vector index. */
public interface TopkGlobalIndexResult extends GlobalIndexResult {
ScoreGetter scoreGetter();
+
+ default GlobalIndexResult and(GlobalIndexResult other) {
+ throw new UnsupportedOperationException("Please realize this by
specified global index");
+ }
+
+ @Override
+ default GlobalIndexResult or(GlobalIndexResult other) {
+ if (!(other instanceof TopkGlobalIndexResult)) {
+ return GlobalIndexResult.super.or(other);
+ }
+ RoaringNavigableMap64 thisRowIds = results();
+ ScoreGetter thisScoreGetter = scoreGetter();
+
+ RoaringNavigableMap64 otherRowIds = other.results();
+ ScoreGetter otherScoreGetter = ((TopkGlobalIndexResult)
other).scoreGetter();
+
+ final RoaringNavigableMap64 resultOr =
RoaringNavigableMap64.or(thisRowIds, otherRowIds);
+ return new TopkGlobalIndexResult() {
+ @Override
+ public ScoreGetter scoreGetter() {
+ return rowId -> {
+ if (thisRowIds.contains(rowId)) {
+ return thisScoreGetter.score(rowId);
+ }
+ return otherScoreGetter.score(rowId);
+ };
+ }
+
+ @Override
+ public RoaringNavigableMap64 results() {
+ return resultOr;
+ }
+ };
+ }
+
+ /** Returns a new {@link TopkGlobalIndexResult} from supplier. */
+ static TopkGlobalIndexResult create(
+ Supplier<RoaringNavigableMap64> supplier, ScoreGetter scoreGetter)
{
+ LazyField<RoaringNavigableMap64> lazyField = new LazyField<>(supplier);
+ return new TopkGlobalIndexResult() {
+ @Override
+ public ScoreGetter scoreGetter() {
+ return scoreGetter;
+ }
+
+ @Override
+ public RoaringNavigableMap64 results() {
+ return lazyField.get();
+ }
+ };
+ }
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/utils/RoaringNavigableMap64.java
b/paimon-common/src/main/java/org/apache/paimon/utils/RoaringNavigableMap64.java
index 6bb52af3d3..e61486ecc4 100644
---
a/paimon-common/src/main/java/org/apache/paimon/utils/RoaringNavigableMap64.java
+++
b/paimon-common/src/main/java/org/apache/paimon/utils/RoaringNavigableMap64.java
@@ -33,7 +33,7 @@ import java.util.List;
import java.util.Objects;
/** A compressed bitmap for 64-bit integer aggregated by tree. */
-public class RoaringNavigableMap64 {
+public class RoaringNavigableMap64 implements Iterable<Long> {
private final Roaring64NavigableMap roaring64NavigableMap;
@@ -46,11 +46,11 @@ public class RoaringNavigableMap64 {
}
public void addRange(Range range) {
- if (range.from == range.to) {
- roaring64NavigableMap.add(range.from);
- } else {
- roaring64NavigableMap.addRange(range.from, range.to);
- }
+ roaring64NavigableMap.addRange(range.from, range.to + 1);
+ }
+
+ public boolean contains(long x) {
+ return roaring64NavigableMap.contains(x);
}
public void add(long x) {
@@ -77,6 +77,10 @@ public class RoaringNavigableMap64 {
return roaring64NavigableMap.getLongCardinality();
}
+ public int getIntCardinality() {
+ return roaring64NavigableMap.getIntCardinality();
+ }
+
public Iterator<Long> iterator() {
return roaring64NavigableMap.iterator();
}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexSerDeUtilsTest.java
b/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexSerDeUtilsTest.java
new file mode 100644
index 0000000000..a5aaa74e87
--- /dev/null
+++
b/paimon-common/src/test/java/org/apache/paimon/globalindex/GlobalIndexSerDeUtilsTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.io.DataInputDeserializer;
+import org.apache.paimon.io.DataOutputSerializer;
+import org.apache.paimon.utils.RoaringNavigableMap64;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link GlobalIndexResultSerializer}. */
+public class GlobalIndexSerDeUtilsTest {
+
+ @Test
+ public void testSerializeAndDeserializeGlobalIndexResult() throws
IOException {
+ RoaringNavigableMap64 bitmap = RoaringNavigableMap64.bitmapOf(1, 5,
10, 100, 1000);
+ GlobalIndexResult original = GlobalIndexResult.create(() -> bitmap);
+
+ byte[] serialized = serialize(original);
+ GlobalIndexResult deserialized = deserialize(serialized);
+
+ assertThat(deserialized).isNotInstanceOf(TopkGlobalIndexResult.class);
+ assertThat(deserialized.results()).isEqualTo(bitmap);
+ }
+
+ @Test
+ public void testSerializeAndDeserializeEmptyGlobalIndexResult() throws
IOException {
+ GlobalIndexResult original = GlobalIndexResult.createEmpty();
+
+ byte[] serialized = serialize(original);
+ GlobalIndexResult deserialized = deserialize(serialized);
+
+ assertThat(deserialized).isNotInstanceOf(TopkGlobalIndexResult.class);
+ assertThat(deserialized.results().isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testSerializeAndDeserializeTopkGlobalIndexResult() throws
IOException {
+ RoaringNavigableMap64 bitmap = RoaringNavigableMap64.bitmapOf(1, 5,
10, 100);
+ Map<Long, Float> scoreMap = new HashMap<>();
+ scoreMap.put(1L, 0.9f);
+ scoreMap.put(5L, 0.8f);
+ scoreMap.put(10L, 0.7f);
+ scoreMap.put(100L, 0.6f);
+
+ TopkGlobalIndexResult original = TopkGlobalIndexResult.create(() ->
bitmap, scoreMap::get);
+
+ byte[] serialized = serialize(original);
+ GlobalIndexResult deserialized = deserialize(serialized);
+
+ assertThat(deserialized).isInstanceOf(TopkGlobalIndexResult.class);
+ assertThat(deserialized.results()).isEqualTo(bitmap);
+
+ TopkGlobalIndexResult topkResult = (TopkGlobalIndexResult)
deserialized;
+ ScoreGetter scoreGetter = topkResult.scoreGetter();
+ assertThat(scoreGetter.score(1L)).isEqualTo(0.9f);
+ assertThat(scoreGetter.score(5L)).isEqualTo(0.8f);
+ assertThat(scoreGetter.score(10L)).isEqualTo(0.7f);
+ assertThat(scoreGetter.score(100L)).isEqualTo(0.6f);
+ }
+
+ @Test
+ public void testSerializeAndDeserializeTopkWithLargeRowIds() throws
IOException {
+ RoaringNavigableMap64 bitmap =
+ RoaringNavigableMap64.bitmapOf(
+ Integer.MAX_VALUE + 1L, Integer.MAX_VALUE + 100L,
Long.MAX_VALUE - 1);
+ Map<Long, Float> scoreMap = new HashMap<>();
+ scoreMap.put(Integer.MAX_VALUE + 1L, 0.5f);
+ scoreMap.put(Integer.MAX_VALUE + 100L, 0.3f);
+ scoreMap.put(Long.MAX_VALUE - 1, 0.1f);
+
+ TopkGlobalIndexResult original = TopkGlobalIndexResult.create(() ->
bitmap, scoreMap::get);
+
+ byte[] serialized = serialize(original);
+ GlobalIndexResult deserialized = deserialize(serialized);
+
+ assertThat(deserialized).isInstanceOf(TopkGlobalIndexResult.class);
+ assertThat(deserialized.results()).isEqualTo(bitmap);
+
+ TopkGlobalIndexResult topkResult = (TopkGlobalIndexResult)
deserialized;
+ ScoreGetter scoreGetter = topkResult.scoreGetter();
+ assertThat(scoreGetter.score(Integer.MAX_VALUE + 1L)).isEqualTo(0.5f);
+ assertThat(scoreGetter.score(Integer.MAX_VALUE +
100L)).isEqualTo(0.3f);
+ assertThat(scoreGetter.score(Long.MAX_VALUE - 1)).isEqualTo(0.1f);
+ }
+
+ private byte[] serialize(GlobalIndexResult result) throws IOException {
+ GlobalIndexResultSerializer globalIndexResultSerializer = new
GlobalIndexResultSerializer();
+ DataOutputSerializer dataOutputSerializer = new
DataOutputSerializer(1024);
+ globalIndexResultSerializer.serialize(result, dataOutputSerializer);
+ return dataOutputSerializer.getCopyOfBuffer();
+ }
+
+ private GlobalIndexResult deserialize(byte[] data) throws IOException {
+ GlobalIndexResultSerializer globalIndexResultSerializer = new
GlobalIndexResultSerializer();
+ DataInputDeserializer dataInputDeserializer = new
DataInputDeserializer(data);
+ return globalIndexResultSerializer.deserialize(dataInputDeserializer);
+ }
+}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/utils/RoaringNavigableMap64Test.java
b/paimon-common/src/test/java/org/apache/paimon/utils/RoaringNavigableMap64Test.java
new file mode 100644
index 0000000000..ded850fb7d
--- /dev/null
+++
b/paimon-common/src/test/java/org/apache/paimon/utils/RoaringNavigableMap64Test.java
@@ -0,0 +1,111 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link RoaringNavigableMap64}. */
+public class RoaringNavigableMap64Test {
+
+ @Test
+ public void testAddRangeBasic() {
+ RoaringNavigableMap64 bitmap = new RoaringNavigableMap64();
+ bitmap.addRange(new Range(5, 10));
+
+ // Verify the range [5, 10] is added (inclusive on both ends)
+ assertThat(bitmap.getLongCardinality()).isEqualTo(6);
+ assertThat(bitmap.contains(4)).isFalse();
+ assertThat(bitmap.contains(5)).isTrue();
+ assertThat(bitmap.contains(7)).isTrue();
+ assertThat(bitmap.contains(10)).isTrue();
+ assertThat(bitmap.contains(11)).isFalse();
+ }
+
+ @Test
+ public void testAddRangeSingleElement() {
+ RoaringNavigableMap64 bitmap = new RoaringNavigableMap64();
+ bitmap.addRange(new Range(100, 100));
+
+ // A range where from == to should add exactly one element
+ assertThat(bitmap.getLongCardinality()).isEqualTo(1);
+ assertThat(bitmap.contains(99)).isFalse();
+ assertThat(bitmap.contains(100)).isTrue();
+ assertThat(bitmap.contains(101)).isFalse();
+ }
+
+ @Test
+ public void testAddRangeMultipleNonOverlapping() {
+ RoaringNavigableMap64 bitmap = new RoaringNavigableMap64();
+ bitmap.addRange(new Range(0, 5));
+ bitmap.addRange(new Range(10, 15));
+ bitmap.addRange(new Range(20, 25));
+
+ // Verify cardinality: 6 + 6 + 6 = 18
+ assertThat(bitmap.getLongCardinality()).isEqualTo(18);
+
+ // Verify gaps are not filled
+ assertThat(bitmap.contains(6)).isFalse();
+ assertThat(bitmap.contains(9)).isFalse();
+ assertThat(bitmap.contains(16)).isFalse();
+ assertThat(bitmap.contains(19)).isFalse();
+
+ // Verify ranges contain expected values
+ assertThat(bitmap.contains(0)).isTrue();
+ assertThat(bitmap.contains(5)).isTrue();
+ assertThat(bitmap.contains(10)).isTrue();
+ assertThat(bitmap.contains(15)).isTrue();
+ assertThat(bitmap.contains(20)).isTrue();
+ assertThat(bitmap.contains(25)).isTrue();
+
+ // Verify toRangeList reconstructs the ranges correctly
+ List<Range> ranges = bitmap.toRangeList();
+ assertThat(ranges).hasSize(3);
+ assertThat(ranges.get(0)).isEqualTo(new Range(0, 5));
+ assertThat(ranges.get(1)).isEqualTo(new Range(10, 15));
+ assertThat(ranges.get(2)).isEqualTo(new Range(20, 25));
+ }
+
+ @Test
+ public void testAddRangeLargeValues() {
+ RoaringNavigableMap64 bitmap = new RoaringNavigableMap64();
+ // Test with values beyond Integer.MAX_VALUE
+ long start = Integer.MAX_VALUE + 100L;
+ long end = Integer.MAX_VALUE + 200L;
+ bitmap.addRange(new Range(start, end));
+
+ assertThat(bitmap.getLongCardinality()).isEqualTo(101);
+ assertThat(bitmap.contains(start - 1)).isFalse();
+ assertThat(bitmap.contains(start)).isTrue();
+ assertThat(bitmap.contains(start + 50)).isTrue();
+ assertThat(bitmap.contains(end)).isTrue();
+ assertThat(bitmap.contains(end + 1)).isFalse();
+
+ // Verify iteration order
+ List<Long> values = new ArrayList<>();
+ bitmap.iterator().forEachRemaining(values::add);
+ assertThat(values).hasSize(101);
+ assertThat(values.get(0)).isEqualTo(start);
+ assertThat(values.get(100)).isEqualTo(end);
+ }
+}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionBatchScan.java
b/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionBatchScan.java
index b8eed4e5b5..9d03f49379 100644
---
a/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionBatchScan.java
+++
b/paimon-core/src/main/java/org/apache/paimon/globalindex/DataEvolutionBatchScan.java
@@ -55,6 +55,7 @@ public class DataEvolutionBatchScan implements DataTableScan {
private Predicate filter;
private List<Range> pushedRowRanges;
+ private GlobalIndexResult globalIndexResult;
public DataEvolutionBatchScan(FileStoreTable wrapped, DataTableBatchScan
batchScan) {
this.table = wrapped;
@@ -148,6 +149,18 @@ public class DataEvolutionBatchScan implements
DataTableScan {
@Override
public InnerTableScan withRowRanges(List<Range> rowRanges) {
this.pushedRowRanges = rowRanges;
+ if (globalIndexResult != null) {
+ throw new IllegalStateException("");
+ }
+ return this;
+ }
+
+ // To enable other system computing index result by their own.
+ public InnerTableScan withGlobalIndexResult(GlobalIndexResult
globalIndexResult) {
+ this.globalIndexResult = globalIndexResult;
+ if (pushedRowRanges != null) {
+ throw new IllegalStateException("");
+ }
return this;
}
@@ -160,6 +173,7 @@ public class DataEvolutionBatchScan implements
DataTableScan {
public Plan plan() {
List<Range> rowRanges = this.pushedRowRanges;
ScoreGetter scoreGetter = null;
+
if (rowRanges == null) {
Optional<GlobalIndexResult> indexResult = evalGlobalIndex();
if (indexResult.isPresent()) {
@@ -180,6 +194,9 @@ public class DataEvolutionBatchScan implements
DataTableScan {
}
private Optional<GlobalIndexResult> evalGlobalIndex() {
+ if (this.globalIndexResult != null) {
+ return Optional.of(globalIndexResult);
+ }
if (filter == null) {
return Optional.empty();
}
@@ -237,7 +254,7 @@ public class DataEvolutionBatchScan implements
DataTableScan {
float[] scores = null;
if (scoreGetter != null) {
- int size = expected.stream().mapToInt(r -> (int) (r.to -
r.from + 1)).sum();
+ int size = expected.stream().mapToInt(r -> (int)
(r.count())).sum();
scores = new float[size];
int index = 0;
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/DataEvolutionTableTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/DataEvolutionTableTest.java
index 8845e22de9..3bbe513a62 100644
---
a/paimon-core/src/test/java/org/apache/paimon/table/DataEvolutionTableTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/table/DataEvolutionTableTest.java
@@ -24,6 +24,7 @@ import org.apache.paimon.data.BinaryString;
import org.apache.paimon.data.GenericRow;
import org.apache.paimon.data.InternalRow;
import org.apache.paimon.fs.FileIO;
+import org.apache.paimon.globalindex.DataEvolutionBatchScan;
import org.apache.paimon.globalindex.GlobalIndexFileReadWrite;
import org.apache.paimon.globalindex.GlobalIndexResult;
import org.apache.paimon.globalindex.GlobalIndexScanBuilder;
@@ -852,12 +853,13 @@ public class DataEvolutionTableTest extends TableTestBase
{
.containsExactlyInAnyOrder(
new Range(200L, 200L), new Range(300L, 300L), new
Range(400L, 400L));
- ReadBuilder readBuilder =
table.newReadBuilder().withRowRanges(rowIds.toRangeList());
+ DataEvolutionBatchScan scan = (DataEvolutionBatchScan) table.newScan();
+ RoaringNavigableMap64 finalRowIds = rowIds;
+ scan.withGlobalIndexResult(GlobalIndexResult.create(() ->
finalRowIds));
List<String> readF1 = new ArrayList<>();
- readBuilder
- .newRead()
- .createReader(readBuilder.newScan().plan())
+ table.newRead()
+ .createReader(scan.plan())
.forEachRemaining(
row -> {
readF1.add(row.getString(1).toString());