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 6177366503 [core] Fix BlockIterator may not move forward after calling
`next()` (#6738)
6177366503 is described below
commit 6177366503585d765d56147d9e9a08364515d6c0
Author: Faiz <[email protected]>
AuthorDate: Thu Dec 4 21:24:35 2025 +0800
[core] Fix BlockIterator may not move forward after calling `next()` (#6738)
---
.../apache/paimon/lookup/sort/BlockIterator.java | 4 +-
.../org/apache/paimon/lookup/sort/BlockReader.java | 8 +-
.../paimon/lookup/sort/BlockIteratorTest.java | 108 +++++++++++++++++++++
3 files changed, 115 insertions(+), 5 deletions(-)
diff --git
a/paimon-common/src/main/java/org/apache/paimon/lookup/sort/BlockIterator.java
b/paimon-common/src/main/java/org/apache/paimon/lookup/sort/BlockIterator.java
index c5647dc50b..0ca04e91d7 100644
---
a/paimon-common/src/main/java/org/apache/paimon/lookup/sort/BlockIterator.java
+++
b/paimon-common/src/main/java/org/apache/paimon/lookup/sort/BlockIterator.java
@@ -88,6 +88,7 @@ public abstract class BlockIterator implements
Iterator<Map.Entry<MemorySlice, M
polled = midEntry;
right = mid - 1;
} else {
+ polled = null;
left = mid + 1;
}
}
@@ -95,7 +96,8 @@ public abstract class BlockIterator implements
Iterator<Map.Entry<MemorySlice, M
return false;
}
- public abstract void seekTo(int record);
+ /** Seek to the specified record position of current block. */
+ public abstract void seekTo(int recordPosition);
private BlockEntry readEntry() {
requireNonNull(data, "data is null");
diff --git
a/paimon-common/src/main/java/org/apache/paimon/lookup/sort/BlockReader.java
b/paimon-common/src/main/java/org/apache/paimon/lookup/sort/BlockReader.java
index 544a3d42b6..a5bbef74ec 100644
--- a/paimon-common/src/main/java/org/apache/paimon/lookup/sort/BlockReader.java
+++ b/paimon-common/src/main/java/org/apache/paimon/lookup/sort/BlockReader.java
@@ -64,8 +64,8 @@ public class BlockReader {
}
@Override
- public void seekTo(int record) {
- data.setPosition(record * recordSize);
+ public void seekTo(int recordPosition) {
+ data.setPosition(recordPosition * recordSize);
}
}
@@ -80,8 +80,8 @@ public class BlockReader {
}
@Override
- public void seekTo(int record) {
- data.setPosition(index.readInt(record * 4));
+ public void seekTo(int recordPosition) {
+ data.setPosition(index.readInt(recordPosition * 4));
}
}
}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/lookup/sort/BlockIteratorTest.java
b/paimon-common/src/test/java/org/apache/paimon/lookup/sort/BlockIteratorTest.java
new file mode 100644
index 0000000000..71c75ff95a
--- /dev/null
+++
b/paimon-common/src/test/java/org/apache/paimon/lookup/sort/BlockIteratorTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.lookup.sort;
+
+import org.apache.paimon.memory.MemorySlice;
+import org.apache.paimon.memory.MemorySliceOutput;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Map;
+
+/** Test for {@link BlockIterator}. */
+public class BlockIteratorTest {
+ private static final int ROW_NUM = 10_000;
+ private static final Comparator<MemorySlice> COMPARATOR =
+ Comparator.comparingInt(slice -> slice.readInt(0));
+
+ @Test
+ public void testAlignedIterator() throws IOException {
+ innerTest(true);
+ }
+
+ @Test
+ public void testUnalignedIterator() throws IOException {
+ innerTest(false);
+ }
+
+ public void innerTest(boolean aligned) throws IOException {
+ MemorySlice data = writeBlock(aligned);
+ BlockIterator iterator = new BlockReader(data, COMPARATOR).iterator();
+
+ // 1. test for normal cases:
+ final int step = 3;
+ MemorySliceOutput keyOut = new MemorySliceOutput(4);
+ MemorySliceOutput valueOut = new MemorySliceOutput(4);
+ for (int i = 0; i < ROW_NUM * 2; i += step) {
+ keyOut.reset();
+ keyOut.writeInt(i);
+ iterator.seekTo(keyOut.toSlice());
+ int expected = (i + 1) / 2;
+ while (iterator.hasNext()) {
+ Map.Entry<MemorySlice, MemorySlice> entry = iterator.next();
+ MemorySlice key = entry.getKey();
+ MemorySlice value = entry.getValue();
+ Assertions.assertEquals(expected * 2, key.readInt(0));
+ Assertions.assertArrayEquals(
+ constructValue(valueOut, aligned, expected),
value.copyBytes());
+ expected++;
+ }
+ }
+
+ // 2. test seek to boundaries
+
+ // 2.1 seek to some key smaller than the first key
+ keyOut.reset();
+ keyOut.writeInt(-1);
+ iterator.seekTo(keyOut.toSlice());
+ Assertions.assertTrue(iterator.hasNext());
+ Map.Entry<MemorySlice, MemorySlice> entry = iterator.next();
+ Assertions.assertEquals(0, entry.getKey().readInt(0));
+
+ // 2.2 seek to some key greater than the last key
+ keyOut.reset();
+ keyOut.writeInt(ROW_NUM * 2 + 2);
+ iterator.seekTo(keyOut.toSlice());
+ Assertions.assertFalse(iterator.hasNext());
+ }
+
+ private MemorySlice writeBlock(boolean aligned) throws IOException {
+ BlockWriter writer = new BlockWriter(ROW_NUM * 14);
+ MemorySliceOutput keyOut = new MemorySliceOutput(4);
+ MemorySliceOutput valueOut = new MemorySliceOutput(4);
+ for (int i = 0; i < ROW_NUM; i++) {
+ keyOut.reset();
+ keyOut.writeInt(i * 2);
+ writer.add(keyOut.toSlice().getHeapMemory(),
constructValue(valueOut, aligned, i));
+ }
+ return writer.finish();
+ }
+
+ private byte[] constructValue(MemorySliceOutput valueOut, boolean aligned,
int i) {
+ valueOut.reset();
+ valueOut.writeInt(i * 2);
+ if (aligned && i % 2 == 0) {
+ valueOut.writeInt(i * 2);
+ }
+ return valueOut.toSlice().copyBytes();
+ }
+}