This is an automated email from the ASF dual-hosted git repository.

JackieTien97 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new aa0e230a0f5 [to dev/1.3] Fix TVList iterator state across memtable 
page switches (#18652)
aa0e230a0f5 is described below

commit aa0e230a0f5342a651bd510c3a4c6ed7b3f8f703
Author: shuwenwei <[email protected]>
AuthorDate: Wed Sep 16 17:35:47 2026 +0800

    [to dev/1.3] Fix TVList iterator state across memtable page switches 
(#18652)
---
 .../iotdb/db/utils/datastructure/TVList.java       |  6 +-
 .../memtable/AlignedTVListIteratorTest.java        | 84 ++++++++++++++++++++++
 .../memtable/NonAlignedTVListIteratorTest.java     | 70 ++++++++++++++++++
 3 files changed, 158 insertions(+), 2 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TVList.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TVList.java
index 2536836069c..48260213140 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TVList.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TVList.java
@@ -931,9 +931,10 @@ public abstract class TVList implements WALEntryValue {
       int newIndex = getScanOrderIndex(indexInTVList);
       if (newIndex > index) {
         index = newIndex;
+        // If the cursor does not move, a duplicate-timestamp group prepared 
for the current
+        // position remains valid. Invalidate it only after the cursor 
actually advances.
+        probeNext = false;
       }
-
-      probeNext = false;
     }
 
     protected void prepareNext() {
@@ -1238,6 +1239,7 @@ public abstract class TVList implements WALEntryValue {
       // been applied when constructing the tsBlock
       TsBlock tsBlock = builder.build();
       addTsBlock(tsBlock);
+      probeNext = false;
       return tsBlock;
     }
 
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedTVListIteratorTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedTVListIteratorTest.java
index d5a8b49f726..04ea5958a2b 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedTVListIteratorTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedTVListIteratorTest.java
@@ -846,6 +846,90 @@ public class AlignedTVListIteratorTest {
             paginationController.getCurLimit(), 
paginationController.getCurOffset());
   }
 
+  @Test
+  public void testPageSwitchKeepsPreparedDuplicateTimestampValues() throws 
IOException {
+    AlignedTVList tvList =
+        AlignedTVList.newAlignedList(
+            Arrays.asList(TSDataType.INT64, TSDataType.BOOLEAN, 
TSDataType.BOOLEAN));
+    tvList.putAlignedValue(1, new Object[] {1L, true, false});
+    tvList.putAlignedValue(100, new Object[] {2L, null, false});
+    tvList.putAlignedValue(100, new Object[] {null, true, false});
+
+    Map<TVList, Integer> tvListMap = new LinkedHashMap<>();
+    tvListMap.put(tvList, tvList.rowCount());
+    AlignedReadOnlyMemChunk chunk =
+        new AlignedReadOnlyMemChunk(
+            fragmentInstanceContext,
+            Arrays.asList(0, 1, 2),
+            getMeasurementSchema(),
+            tvListMap,
+            Arrays.asList(
+                Collections.emptyList(), Collections.emptyList(), 
Collections.emptyList()));
+    chunk.sortTvLists();
+    chunk.initChunkMetaFromTVListsWithFakeStatistics();
+
+    MemPointIterator iterator = chunk.createMemPointIterator(Ordering.ASC, 
null);
+    List<TimeValuePair> result = new ArrayList<>();
+    // These are fake-page boundaries for one MemChunk. The middle page is 
empty, but the
+    // shared iterator still receives its time range before its next page is 
read.
+    for (TimeRange pageRange :
+        Arrays.asList(new TimeRange(1, 33), new TimeRange(34, 66), new 
TimeRange(67, 100))) {
+      iterator.setCurrentPageTimeRange(pageRange);
+      while (iterator.hasNextTimeValuePair()) {
+        result.add(iterator.nextTimeValuePair());
+      }
+    }
+
+    Assert.assertEquals(2, result.size());
+    Assert.assertEquals(1L, result.get(0).getTimestamp());
+    Assert.assertEquals(1L, result.get(0).getValues()[0]);
+    Assert.assertEquals(100L, result.get(1).getTimestamp());
+    Assert.assertEquals(2L, result.get(1).getValues()[0]);
+    Assert.assertEquals(Boolean.TRUE, result.get(1).getValues()[1]);
+    Assert.assertEquals(Boolean.FALSE, result.get(1).getValues()[2]);
+  }
+
+  @Test
+  public void testPageSwitchKeepsPreparedDuplicateTimestampValuesDescending() 
throws IOException {
+    AlignedTVList tvList =
+        AlignedTVList.newAlignedList(
+            Arrays.asList(TSDataType.INT64, TSDataType.BOOLEAN, 
TSDataType.BOOLEAN));
+    tvList.putAlignedValue(1, new Object[] {null, true, false});
+    tvList.putAlignedValue(1, new Object[] {2L, null, false});
+    tvList.putAlignedValue(100, new Object[] {1L, true, false});
+
+    Map<TVList, Integer> tvListMap = new LinkedHashMap<>();
+    tvListMap.put(tvList, tvList.rowCount());
+    AlignedReadOnlyMemChunk chunk =
+        new AlignedReadOnlyMemChunk(
+            fragmentInstanceContext,
+            Arrays.asList(0, 1, 2),
+            getMeasurementSchema(),
+            tvListMap,
+            Arrays.asList(
+                Collections.emptyList(), Collections.emptyList(), 
Collections.emptyList()));
+    chunk.sortTvLists();
+    chunk.initChunkMetaFromTVListsWithFakeStatistics();
+
+    MemPointIterator iterator = chunk.createMemPointIterator(Ordering.DESC, 
null);
+    List<TimeValuePair> result = new ArrayList<>();
+    for (TimeRange pageRange :
+        Arrays.asList(new TimeRange(67, 100), new TimeRange(34, 66), new 
TimeRange(1, 33))) {
+      iterator.setCurrentPageTimeRange(pageRange);
+      while (iterator.hasNextTimeValuePair()) {
+        result.add(iterator.nextTimeValuePair());
+      }
+    }
+
+    Assert.assertEquals(2, result.size());
+    Assert.assertEquals(100L, result.get(0).getTimestamp());
+    Assert.assertEquals(1L, result.get(0).getValues()[0]);
+    Assert.assertEquals(1L, result.get(1).getTimestamp());
+    Assert.assertEquals(2L, result.get(1).getValues()[0]);
+    Assert.assertEquals(Boolean.TRUE, result.get(1).getValues()[1]);
+    Assert.assertEquals(Boolean.FALSE, result.get(1).getValues()[2]);
+  }
+
   @Test
   public void testSkipTimeRange() throws QueryProcessException, IOException {
     List<Map<TVList, Integer>> list =
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/NonAlignedTVListIteratorTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/NonAlignedTVListIteratorTest.java
index b28979efd67..3f63812447a 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/NonAlignedTVListIteratorTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/NonAlignedTVListIteratorTest.java
@@ -26,6 +26,7 @@ import org.apache.iotdb.db.queryengine.common.PlanFragmentId;
 import 
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext;
 import 
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceStateMachine;
 import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering;
+import org.apache.iotdb.db.utils.datastructure.LongTVList;
 import org.apache.iotdb.db.utils.datastructure.MemPointIterator;
 import org.apache.iotdb.db.utils.datastructure.TVList;
 
@@ -717,4 +718,73 @@ public class NonAlignedTVListIteratorTest {
     }
     Assert.assertEquals(expectedTimestamps, resultTimestamps);
   }
+
+  @Test
+  public void testBatchToPointAfterEmptyPageKeepsLatestDuplicateValue() throws 
IOException {
+    LongTVList tvList = LongTVList.newList();
+    tvList.putLong(1, 1);
+    tvList.putLong(100, 2);
+    tvList.putLong(100, 3);
+
+    MemPointIterator iterator =
+        tvList.iterator(
+            Ordering.ASC,
+            tvList.rowCount(),
+            null,
+            Collections.emptyList(),
+            0,
+            TSEncoding.PLAIN,
+            1024);
+
+    iterator.setCurrentPageTimeRange(new TimeRange(1, 33));
+    int firstPageRows = 0;
+    while (iterator.hasNextBatch()) {
+      firstPageRows += iterator.nextBatch().getPositionCount();
+    }
+    Assert.assertEquals(1, firstPageRows);
+
+    iterator.setCurrentPageTimeRange(new TimeRange(34, 66));
+    Assert.assertFalse(iterator.hasNextBatch());
+
+    iterator.setCurrentPageTimeRange(new TimeRange(67, 100));
+    List<Long> result = new ArrayList<>();
+    while (iterator.hasNextTimeValuePair()) {
+      result.add(iterator.nextTimeValuePair().getValue().getLong());
+    }
+    Assert.assertEquals(Collections.singletonList(3L), result);
+  }
+
+  @Test
+  public void testBatchToPointAfterEmptyPageDescendingSkipsDeletedPoint() 
throws IOException {
+    LongTVList tvList = LongTVList.newList();
+    tvList.putLong(10, 10);
+    tvList.putLong(100, 100);
+
+    MemPointIterator iterator =
+        tvList.iterator(
+            Ordering.DESC,
+            tvList.rowCount(),
+            null,
+            Collections.singletonList(new TimeRange(10, 10)),
+            0,
+            TSEncoding.PLAIN,
+            1024);
+
+    iterator.setCurrentPageTimeRange(new TimeRange(67, 100));
+    int firstPageRows = 0;
+    while (iterator.hasNextBatch()) {
+      firstPageRows += iterator.nextBatch().getPositionCount();
+    }
+    Assert.assertEquals(1, firstPageRows);
+
+    iterator.setCurrentPageTimeRange(new TimeRange(34, 66));
+    Assert.assertFalse(iterator.hasNextBatch());
+
+    iterator.setCurrentPageTimeRange(new TimeRange(1, 33));
+    List<Long> result = new ArrayList<>();
+    while (iterator.hasNextTimeValuePair()) {
+      result.add(iterator.nextTimeValuePair().getValue().getLong());
+    }
+    Assert.assertTrue(result.isEmpty());
+  }
 }

Reply via email to