You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by sh...@apache.org on 2026/09/16 08:25:42 UTC
(iotdb) 01/02: Fix aligned TVList page switch dropping duplicate values
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 <s1...@gmail.com>
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 =