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

shuwenwei pushed a commit to branch cp/18647-dev1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit 839e0ca3587863cee77674d31d5dc8aa9edebbb9
Author: shuwenwei <[email protected]>
AuthorDate: Wed Sep 16 12:00:45 2026 +0800

    Fix aligned TVList page switch dropping duplicate values
---
 .../iotdb/db/utils/datastructure/TVList.java       |  5 +-
 .../memtable/AlignedTVListIteratorTest.java        | 86 ++++++++++++++++++++++
 2 files changed, 89 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..e0d8e5b6de3 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() {
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..840e9d31156 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,92 @@ 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,
+            Collections.emptyList(),
+            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,
+            Collections.emptyList(),
+            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 =

Reply via email to