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

shuwenwei pushed a commit to branch 
fix/aligned-tvlist-page-switch-duplicate-values
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit 1a15a210e121799f2da8759ff8588c54bb0c5711
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 506f82d9e10..d59da2575b0 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
@@ -966,9 +966,10 @@ public abstract class TVList implements WALEntryValue {
           
this.getQueryContext().getQueryStatistics().addFilteredRowsOfRowLevel(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 c58eeb1bf60..ea2b43c8f8d 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
@@ -868,6 +868,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