diff --git a/paimon-common/src/main/java/org/apache/paimon/utils/RowRangeIndex.java b/paimon-common/src/main/java/org/apache/paimon/utils/RowRangeIndex.java index 7143d7d5fdc2..e5264555b2a5 100644 --- a/paimon-common/src/main/java/org/apache/paimon/utils/RowRangeIndex.java +++ b/paimon-common/src/main/java/org/apache/paimon/utils/RowRangeIndex.java @@ -42,6 +42,16 @@ private RowRangeIndex(List ranges) { } } + /** Builds an index from a bitmap, skipping sorting and merging for already ordered ranges. */ + public static RowRangeIndex fromBitmap(RoaringNavigableMap64 bitmap) { + List ranges = bitmap.toRangeList(); + // Bitmap order is unsigned; mixed signs still require signed sorting and merging. + if (ranges.size() > 1 && ranges.get(0).from > ranges.get(ranges.size() - 1).from) { + return create(ranges); + } + return new RowRangeIndex(ranges); + } + public static RowRangeIndex create(List ranges) { return create(ranges, true); } diff --git a/paimon-common/src/test/java/org/apache/paimon/utils/RowRangeIndexTest.java b/paimon-common/src/test/java/org/apache/paimon/utils/RowRangeIndexTest.java index 7c4a9b155013..f1be84e0249d 100644 --- a/paimon-common/src/test/java/org/apache/paimon/utils/RowRangeIndexTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/utils/RowRangeIndexTest.java @@ -21,12 +21,52 @@ import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.stream.LongStream; import static org.assertj.core.api.Assertions.assertThat; /** Tests for {@link RowRangeIndex}. */ class RowRangeIndexTest { + @Test + void testFromBitmapPreservesRangeQueriesAndOwnership() { + for (long[] values : + new long[][] { + {}, + {0}, + {0, 1, 2, 9, 11, 12}, + {Integer.MAX_VALUE, (1L << 32) - 1, 1L << 32, Long.MAX_VALUE}, + {-2, -1, 0, 1, Long.MAX_VALUE}, + {Long.MIN_VALUE, -2, -1}, + LongStream.range(0, 10000).toArray() + }) { + RoaringNavigableMap64 bitmap = RoaringNavigableMap64.bitmapOf(values); + RowRangeIndex expected = RowRangeIndex.create(bitmap.toRangeList()); + RowRangeIndex actual = RowRangeIndex.fromBitmap(bitmap); + assertThat(actual.ranges()).isEqualTo(expected.ranges()); + long[] bounds = { + Long.MIN_VALUE, -2, -1, 0, 1, 3, 8, 9, 10, 11, 13, 9999, 1L << 32, Long.MAX_VALUE + }; + for (long start : bounds) { + for (long end : bounds) { + if (start <= end) { + Range range = new Range(start, end); + assertThat(actual.intersects(start, end)) + .isEqualTo(expected.intersects(start, end)); + assertThat(actual.intersectedRanges(start, end)) + .isEqualTo(expected.intersectedRanges(start, end)); + assertThat(actual.contains(range)).isEqualTo(expected.contains(range)); + assertThat(actual.containsExactly(range)) + .isEqualTo(expected.containsExactly(range)); + } + } + } + bitmap.add(20000); + assertThat(actual.ranges()).isEqualTo(expected.ranges()); + assertThat(actual.intersects(20000, 20000)).isFalse(); + } + } + @Test void testContains() { RowRangeIndex index = 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 7a16a2e7accb..6f12335f3ccd 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 @@ -286,7 +286,7 @@ public Plan plan() { } if (indexResult.isPresent()) { GlobalIndexResult result = indexResult.get(); - rowRangeIndex = RowRangeIndex.create(result.results().toRangeList()); + rowRangeIndex = RowRangeIndex.fromBitmap(result.results()); if (result instanceof ScoredGlobalIndexResult) { scoreGetter = ((ScoredGlobalIndexResult) result).scoreGetter(); }