This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch add_flat_device
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/add_flat_device by this push:
new 87eb52e66b8 tmp
87eb52e66b8 is described below
commit 87eb52e66b886275ae760f418efbaba48017ea7e
Author: Tian Jiang <[email protected]>
AuthorDate: Wed Oct 14 17:36:07 2026 +0800
tmp
---
...ileInsertionEventTableParserTabletIterator.java | 27 ++-
.../tsfile/parser/util/ModsOperationUtil.java | 53 ++++-
.../compaction/repair/RepairDataFileScanUtil.java | 5 +-
.../memtable/AlignedWritableMemChunk.java | 24 ++-
.../dataregion/memtable/TsFileProcessor.java | 33 ++-
.../dataregion/modification/TagPredicate.java | 8 +-
.../datastructure/FlatDeviceAlignedTVList.java | 27 ++-
.../db/utils/datastructure/FlatDeviceEncoder.java | 24 ++-
.../table/FlatDeviceTableParserDeletionTest.java | 226 +++++++++++++++++++++
.../repair/RepairDataFileScanUtilTest.java | 51 +++++
.../dataregion/memtable/TsFileProcessorTest.java | 30 +++
.../utils/datastructure/FlatDeviceEncoderTest.java | 42 +++-
.../schema/table/TsFileTableSchemaUtil.java | 15 +-
.../schema/table/TsFileTableSchemaUtilTest.java | 47 +++++
14 files changed, 567 insertions(+), 45 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/table/TsFileInsertionEventTableParserTabletIterator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/table/TsFileInsertionEventTableParserTabletIterator.java
index e7aa8387080..d641a9d617b 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/table/TsFileInsertionEventTableParserTabletIterator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/table/TsFileInsertionEventTableParserTabletIterator.java
@@ -480,7 +480,8 @@ public class TsFileInsertionEventTableParserTabletIterator
implements Iterator<T
this.chunkReader = new TableChunkReader(timeChunk, valueChunkList, null);
this.modsInfoList =
- ModsOperationUtil.initializeMeasurementMods(deviceID, measurementList,
modifications);
+ ModsOperationUtil.initializeMeasurementMods(
+ deviceID, measurementList, modifications, isFlatDeviceTable);
}
private boolean areAllFieldsDeletedByMods(
@@ -522,15 +523,29 @@ public class
TsFileInsertionEventTableParserTabletIterator implements Iterator<T
Objects.nonNull(data.getVector()) ? data.getVector() : new
TsPrimitiveType[0];
boolean needFillTime = false;
final int valueColumnOffset = isFlatDeviceTable ? 0 : deviceIdSize;
- boolean hasNonDeletedField = dataTypeList.size() == valueColumnOffset;
+ boolean hasNonDeletedField =
+ isFlatDeviceTable ? fieldSchemaList.isEmpty() : dataTypeList.size() ==
valueColumnOffset;
+ if (isFlatDeviceTable
+ && fieldSchemaList.isEmpty()
+ && !modsInfoList.isEmpty()
+ && ModsOperationUtil.isDelete(data.currentTime(), modsInfoList.get(0),
primitiveTypes)) {
+ return false;
+ }
for (int i = valueColumnOffset, size = dataTypeList.size(); i < size; i++)
{
final TsPrimitiveType primitiveType =
i - valueColumnOffset < primitiveTypes.length
? primitiveTypes[i - valueColumnOffset]
: null;
- final boolean isDeleted = ModsOperationUtil.isDelete(data.currentTime(),
modsInfoList.get(i));
- if (!isDeleted) {
+ // TAGs define identity and must survive FIELD deletion. Only surviving
FIELDs keep a row.
+ final boolean isTag = isFlatDeviceTable && i < deviceIdSize;
+ final boolean isDeleted =
+ !isTag
+ && (isFlatDeviceTable
+ ? ModsOperationUtil.isDelete(
+ data.currentTime(), modsInfoList.get(i), primitiveTypes)
+ : ModsOperationUtil.isDelete(data.currentTime(),
modsInfoList.get(i)));
+ if (!isTag && !isDeleted) {
hasNonDeletedField = true;
}
if (primitiveType == null || isDeleted) {
@@ -543,7 +558,9 @@ public class TsFileInsertionEventTableParserTabletIterator
implements Iterator<T
PipeTabletUtils.markNullValue(tablet, rowIndex, i);
continue;
}
- needFillTime = true;
+ if (!isTag || fieldSchemaList.isEmpty()) {
+ needFillTime = true;
+ }
switch (dataTypeList.get(i)) {
case BOOLEAN:
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/util/ModsOperationUtil.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/util/ModsOperationUtil.java
index e2b65b5415c..bbcdc25e7a1 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/util/ModsOperationUtil.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/util/ModsOperationUtil.java
@@ -21,18 +21,23 @@ package
org.apache.iotdb.db.pipe.event.common.tsfile.parser.util;
import org.apache.iotdb.commons.path.PatternTreeMap;
import org.apache.iotdb.db.i18n.DataNodePipeMessages;
+import
org.apache.iotdb.db.storageengine.dataregion.modification.FlatDeviceDeletionEntry;
import org.apache.iotdb.db.storageengine.dataregion.modification.ModEntry;
import
org.apache.iotdb.db.storageengine.dataregion.modification.ModificationFile;
+import
org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.TagPredicateType;
import org.apache.iotdb.db.utils.ModificationUtils;
import org.apache.iotdb.db.utils.datastructure.PatternTreeMapFactory;
import org.apache.iotdb.pipe.api.exception.PipeException;
import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.utils.TsPrimitiveType;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -103,6 +108,8 @@ public class ModsOperationUtil {
modification ->
modification.getTimeRange().contains(startTime, endTime)
&& modification.affects(deviceID)
+ && (!(modification instanceof FlatDeviceDeletionEntry
deletion)
+ || deletion.getPredicate().getTagPredicateType() ==
TagPredicateType.NOP)
&& modification.affects(measurementID));
} else {
// For tree model: check if any modification covers the time range
@@ -124,7 +131,18 @@ public class ModsOperationUtil {
List<String> measurements,
PatternTreeMap<ModEntry, PatternTreeMapFactory.ModsSerializer>
modifications) {
+ return initializeMeasurementMods(deviceID, measurements, modifications,
false);
+ }
+
+ public static List<ModsInfo> initializeMeasurementMods(
+ IDeviceID deviceID,
+ List<String> measurements,
+ PatternTreeMap<ModEntry, PatternTreeMapFactory.ModsSerializer>
modifications,
+ boolean isFlatDevice) {
+
List<ModsInfo> modsInfos = new ArrayList<>(measurements.size());
+ Map<FlatDeviceDeletionEntry, int[]> tagColumnMappings =
+ isFlatDevice ? new IdentityHashMap<>() : Collections.emptyMap();
for (final String measurement : measurements) {
final List<ModEntry> mods = modifications.getOverlapped(deviceID,
measurement);
@@ -134,7 +152,6 @@ public class ModsOperationUtil {
continue;
}
- // Sort by time range for efficient lookup
// Different filtering logic for tree model and table model
final List<ModEntry> filteredMods;
if (deviceID.isTableModel()) {
@@ -149,13 +166,42 @@ public class ModsOperationUtil {
// For tree model: no additional filtering needed
filteredMods = mods;
}
- // Store sorted mods and start index
- modsInfos.add(new ModsInfo(ModificationUtils.sortAndMerge(filteredMods),
0));
+ if (isFlatDevice) {
+ // Different TAG predicates can have overlapping ranges. Keep each
predicate and its
+ // column-name mapping rather than treating the ranges as one
monotonic time cursor.
+ ModsInfo info = new ModsInfo(filteredMods, 0);
+ info.tagColumnIndices = tagColumnMappings;
+ for (ModEntry mod : filteredMods) {
+ if (mod instanceof FlatDeviceDeletionEntry deletion) {
+ tagColumnMappings.computeIfAbsent(
+ deletion,
+ entry ->
+
entry.getTagColumnNames().stream().mapToInt(measurements::indexOf).toArray());
+ }
+ }
+ modsInfos.add(info);
+ } else {
+ modsInfos.add(new
ModsInfo(ModificationUtils.sortAndMerge(filteredMods), 0));
+ }
}
return modsInfos;
}
+ /** Flat rows are TAG-first, so timestamps may restart when the logical
device changes. */
+ public static boolean isDelete(long time, ModsInfo modsInfo,
TsPrimitiveType[] values) {
+ for (ModEntry mod : modsInfo.getMods()) {
+ if (mod.getTimeRange().contains(time)
+ && (!(mod instanceof FlatDeviceDeletionEntry deletion)
+ || deletion
+ .getPredicate()
+ .matches(values, modsInfo.tagColumnIndices.get(deletion)))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Check if data at the specified time point is deleted
*
@@ -272,6 +318,7 @@ public class ModsOperationUtil {
public static class ModsInfo {
private List<ModEntry> mods;
private int currentIndex;
+ private Map<FlatDeviceDeletionEntry, int[]> tagColumnIndices =
Collections.emptyMap();
public ModsInfo(List<ModEntry> mods, int currentIndex) {
this.mods = Objects.requireNonNull(mods);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/compaction/repair/RepairDataFileScanUtil.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/compaction/repair/RepairDataFileScanUtil.java
index 309aed2ca27..cfc8b9fc03b 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/compaction/repair/RepairDataFileScanUtil.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/compaction/repair/RepairDataFileScanUtil.java
@@ -70,6 +70,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -305,7 +306,9 @@ public class RepairDataFileScanUtil {
for (int i = 0; i < previousValues.length; i++) {
int compare =
Objects.compare(
- previousValues[i].getBinary(), currentValues[i].getBinary(),
Binary::compareTo);
+ previousValues[i] == null ? null : previousValues[i].getBinary(),
+ currentValues[i] == null ? null : currentValues[i].getBinary(),
+ Comparator.nullsFirst(Binary::compareTo));
if (compare != 0) {
return compare;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedWritableMemChunk.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedWritableMemChunk.java
index 50c6698973c..ad5957df04b 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedWritableMemChunk.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedWritableMemChunk.java
@@ -39,6 +39,7 @@ import org.apache.iotdb.db.utils.datastructure.AlignedTVList;
import org.apache.iotdb.db.utils.datastructure.AlignedTVListFactory;
import org.apache.iotdb.db.utils.datastructure.BatchEncodeInfo;
import org.apache.iotdb.db.utils.datastructure.FlatDeviceAlignedTVList;
+import
org.apache.iotdb.db.utils.datastructure.FlatDeviceAlignedTVList.EncodingStatistics;
import org.apache.iotdb.db.utils.datastructure.MemPointIterator;
import org.apache.iotdb.db.utils.datastructure.MemPointIteratorFactory;
import org.apache.iotdb.db.utils.datastructure.TVList;
@@ -75,6 +76,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static org.apache.iotdb.db.utils.ModificationUtils.isPointDeleted;
public class AlignedWritableMemChunk extends AbstractWritableMemChunk {
+ private EncodingStatistics flatDeviceEncodingStatistics;
private Map<String, Integer> measurementIndexMap;
private final Map<String, TsTableColumnCategory> measurementCategoryMap;
@@ -766,6 +768,10 @@ public class AlignedWritableMemChunk extends
AbstractWritableMemChunk {
return useColumnSortedList;
}
+ public EncodingStatistics getFlatDeviceEncodingStatistics() {
+ return flatDeviceEncodingStatistics;
+ }
+
public int deleteFlatDevice(FlatDeviceDeletionEntry deletion) {
List<String> tagColumnNames = deletion.getTagColumnNames();
// Logical TAG ordinals do not necessarily match physical positions:
failed/omitted TAGs
@@ -1345,14 +1351,15 @@ public class AlignedWritableMemChunk extends
AbstractWritableMemChunk {
}
lists.add((FlatDeviceAlignedTVList) workingListForFlush);
List<IMeasurementSchema> activeSchemas = getActiveSchemaList();
- FlatDeviceAlignedTVList.encode(
- lists,
- activeSchemas,
- buildColumnIndexList(activeSchemas),
- encryptParameter,
- ioTaskQueue,
- encodeInfo.maxNumberOfPointsInChunk,
- encodeInfo.maxNumberOfPointsInPage);
+ flatDeviceEncodingStatistics =
+ FlatDeviceAlignedTVList.encode(
+ lists,
+ activeSchemas,
+ buildColumnIndexList(activeSchemas),
+ encryptParameter,
+ ioTaskQueue,
+ encodeInfo.maxNumberOfPointsInChunk,
+ encodeInfo.maxNumberOfPointsInPage);
encodeInfo.reset();
return;
}
@@ -1421,6 +1428,7 @@ public class AlignedWritableMemChunk extends
AbstractWritableMemChunk {
@Override
public void release() {
+ flatDeviceEncodingStatistics = null;
maybeReleaseTvList(list);
for (AlignedTVList alignedTvList : sortedList) {
maybeReleaseTvList(alignedTvList);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
index 808e4a87131..8c01f490df1 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
@@ -419,16 +419,7 @@ public class TsFileProcessor {
private void updateResourceTime(
IDeviceID deviceID, long startTime, long endTime, boolean flatDevice) {
- updateResourceTime(deviceID, startTime, endTime, flatDevice, 1);
- }
-
- private void updateResourceTime(
- IDeviceID deviceID, long startTime, long endTime, boolean flatDevice,
long rowCount) {
if (flatDevice) {
- flatDeviceLogicalDevices
- .computeIfAbsent(deviceID.getTableName(), key -> new HashSet<>())
- .add(deviceID.toString());
- flatDeviceRowCounts.merge(deviceID.getTableName(), rowCount, Long::sum);
tsFileResource.updateTableStartTime(deviceID.getTableName(), startTime);
tsFileResource.updateTableEndTime(deviceID.getTableName(), endTime);
} else {
@@ -789,8 +780,7 @@ public class TsFileProcessor {
deviceEndOffset.left,
insertTabletNode.getTimes()[deviceStartOffset],
insertTabletNode.getTimes()[deviceEndOffset.right - 1],
- insertTabletNode.isFlatDevice(),
- deviceEndOffset.right - deviceStartOffset);
+ insertTabletNode.isFlatDevice());
deviceStartOffset = deviceEndOffset.right;
}
recordFlatDeviceWriteCost(
@@ -2010,6 +2000,7 @@ public class TsFileProcessor {
dataRegionName,
dataRegionInfo.getDataRegion().getDataRegionIdString());
flushTask.syncFlushMemTable();
+ recordFlatDeviceEncodingStatistics(memTableToFlush);
memTableFlushPointCount = memTableToFlush.getTotalPointsNum();
} catch (Throwable e) {
if (writer == null) {
@@ -2215,6 +2206,26 @@ public class TsFileProcessor {
}
}
+ private void recordFlatDeviceEncodingStatistics(IMemTable memTable) {
+ // Publish only after encoding and IO both succeed, so a failed flush is
never counted.
+ memTable
+ .getMemTableMap()
+ .forEach(
+ (device, group) -> {
+ if (group instanceof AlignedWritableMemChunkGroup alignedGroup) {
+ var statistics =
+
alignedGroup.getAlignedMemChunk().getFlatDeviceEncodingStatistics();
+ if (statistics != null) {
+ String table = device.getTableName();
+ Set<String> devices =
+ flatDeviceLogicalDevices.computeIfAbsent(table, key ->
new HashSet<>());
+ statistics.getDevices().forEach(id ->
devices.add(id.toString()));
+ flatDeviceRowCounts.merge(table, statistics.getRowCount(),
Long::sum);
+ }
+ }
+ });
+ }
+
/** end file and write some meta */
private void endFile() throws IOException, TsFileProcessorException {
if (logger.isDebugEnabled()) {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TagPredicate.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TagPredicate.java
index 6c2331f6290..49667a19fbb 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TagPredicate.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TagPredicate.java
@@ -30,6 +30,7 @@ import org.apache.tsfile.utils.Binary;
import org.apache.tsfile.utils.RamUsageEstimator;
import org.apache.tsfile.utils.ReadWriteForEncodingUtils;
import org.apache.tsfile.utils.ReadWriteIOUtils;
+import org.apache.tsfile.utils.TsPrimitiveType;
import java.io.EOFException;
import java.io.IOException;
@@ -108,7 +109,12 @@ public abstract class TagPredicate implements
StreamSerializable, BufferSerializ
}
int columnIndex = tagColumnIndices[tagIndex];
Object value = columnIndex >= 0 && columnIndex < values.length ?
values[columnIndex] : null;
- return value == null ? null : value.toString();
+ if (value instanceof TsPrimitiveType primitive) {
+ value = primitive.getBinary();
+ }
+ return value instanceof Binary binary
+ ? binary.getStringValue(TSFileConfig.STRING_CHARSET)
+ : value == null ? null : value.toString();
}
private static List<String> tagValuesKey(Object[] values, int[]
tagColumnIndices) {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceAlignedTVList.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceAlignedTVList.java
index 186c42fc56e..21cbc50ba8f 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceAlignedTVList.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceAlignedTVList.java
@@ -1026,7 +1026,7 @@ public class FlatDeviceAlignedTVList extends
AlignedTVList {
}
/** Encodes lists in device order, merging only row IDs within the current
device. */
- public static void encode(
+ public static EncodingStatistics encode(
List<FlatDeviceAlignedTVList> lists,
List<IMeasurementSchema> schemas,
List<Integer> columns,
@@ -1034,7 +1034,30 @@ public class FlatDeviceAlignedTVList extends
AlignedTVList {
BlockingQueue<Object> output,
long chunkLimit,
int pageLimit) {
- FlatDeviceEncoder.encode(lists, schemas, columns, encryption, output,
chunkLimit, pageLimit);
+ return FlatDeviceEncoder.encode(
+ lists, schemas, columns, encryption, output, chunkLimit, pageLimit);
+ }
+
+ /** Counts physical output rows after duplicate resolution and time
deletion. */
+ public static final class EncodingStatistics {
+ private long rowCount;
+ private final Set<IDeviceID> devices = new HashSet<>();
+
+ void recordRow() {
+ rowCount++;
+ }
+
+ void recordDevice(IDeviceID device) {
+ devices.add(device);
+ }
+
+ public long getRowCount() {
+ return rowCount;
+ }
+
+ public Set<IDeviceID> getDevices() {
+ return Collections.unmodifiableSet(devices);
+ }
}
AlignedTVList fieldStorage() {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceEncoder.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceEncoder.java
index b82c9bee802..52ac16a3223 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceEncoder.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceEncoder.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.utils.datastructure;
import
org.apache.iotdb.db.storageengine.dataregion.tsfile.FlatDeviceAlignedChunkWriter;
import
org.apache.iotdb.db.utils.datastructure.FlatDeviceAlignedTVList.DeviceInfo;
+import
org.apache.iotdb.db.utils.datastructure.FlatDeviceAlignedTVList.EncodingStatistics;
import org.apache.tsfile.encrypt.EncryptParameter;
import org.apache.tsfile.utils.Binary;
@@ -36,7 +37,7 @@ import java.util.concurrent.BlockingQueue;
final class FlatDeviceEncoder {
private FlatDeviceEncoder() {}
- static void encode(
+ static EncodingStatistics encode(
List<FlatDeviceAlignedTVList> lists,
List<IMeasurementSchema> schemas,
List<Integer> columnIndices,
@@ -45,10 +46,10 @@ final class FlatDeviceEncoder {
long chunkLimit,
int pageLimit) {
if (lists.size() == 1) {
- encodeSingleList(
+ return encodeSingleList(
lists.get(0), schemas, columnIndices, encryption, output,
chunkLimit, pageLimit);
- return;
}
+ EncodingStatistics statistics = new EncodingStatistics();
PriorityQueue<Cursor> devices =
new PriorityQueue<>(
(a, b) -> FlatDeviceAlignedTVList.compareDevices(a.device.id,
b.device.id));
@@ -74,6 +75,7 @@ final class FlatDeviceEncoder {
while (!devices.isEmpty()) {
// All rows in this group share TAGs; retain them even when a cursor
advances devices.
DeviceInfo currentDevice = devices.peek().device;
+ statistics.recordDevice(currentDevice.id);
do {
Cursor cursor = devices.remove();
rows.add(cursor);
@@ -131,6 +133,7 @@ final class FlatDeviceEncoder {
nil);
}
writer.write(time);
+ statistics.recordRow();
chunkRows++;
// The chunk writer may already have sealed on its byte-size threshold.
if (writer.getTimeChunkWriter().getPageWriter().getPointNumber() >=
pageLimit) {
@@ -138,7 +141,7 @@ final class FlatDeviceEncoder {
}
if (chunkRows >= chunkLimit) {
if (!enqueue(writer, output)) {
- return;
+ return statistics;
}
writer = new FlatDeviceAlignedChunkWriter(schemas, encryption);
chunkRows = 0;
@@ -148,9 +151,10 @@ final class FlatDeviceEncoder {
if (chunkRows > 0) {
enqueue(writer, output);
}
+ return statistics;
}
- private static void encodeSingleList(
+ private static EncodingStatistics encodeSingleList(
FlatDeviceAlignedTVList list,
List<IMeasurementSchema> schemas,
List<Integer> columnIndices,
@@ -159,9 +163,11 @@ final class FlatDeviceEncoder {
long chunkLimit,
int pageLimit) {
list.sort();
+ EncodingStatistics statistics = new EncodingStatistics();
FlatDeviceAlignedChunkWriter writer = new
FlatDeviceAlignedChunkWriter(schemas, encryption);
long chunkRows = 0;
for (DeviceInfo device : list.getDeviceInfoMap().values()) {
+ boolean recordedDevice = false;
int position = 0;
while (position < device.rowCount) {
int start = position++;
@@ -206,13 +212,18 @@ final class FlatDeviceEncoder {
nil);
}
writer.write(time);
+ statistics.recordRow();
+ if (!recordedDevice) {
+ statistics.recordDevice(device.id);
+ recordedDevice = true;
+ }
chunkRows++;
if (writer.getTimeChunkWriter().getPageWriter().getPointNumber() >=
pageLimit) {
writer.sealCurrentPage();
}
if (chunkRows >= chunkLimit) {
if (!enqueue(writer, output)) {
- return;
+ return statistics;
}
writer = new FlatDeviceAlignedChunkWriter(schemas, encryption);
chunkRows = 0;
@@ -222,6 +233,7 @@ final class FlatDeviceEncoder {
if (chunkRows > 0) {
enqueue(writer, output);
}
+ return statistics;
}
private static void writeValue(
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/table/FlatDeviceTableParserDeletionTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/table/FlatDeviceTableParserDeletionTest.java
new file mode 100644
index 00000000000..fd7a3881242
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/common/tsfile/parser/table/FlatDeviceTableParserDeletionTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.iotdb.db.pipe.event.common.tsfile.parser.table;
+
+import org.apache.iotdb.commons.schema.table.TsFileTableSchemaUtil;
+import
org.apache.iotdb.db.pipe.event.common.tsfile.parser.TsFileInsertionEventParserMemoryBlock;
+import
org.apache.iotdb.db.storageengine.dataregion.modification.DeletionPredicate;
+import
org.apache.iotdb.db.storageengine.dataregion.modification.FlatDeviceDeletionEntry;
+import org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate;
+import
org.apache.iotdb.db.storageengine.dataregion.tsfile.FlatDeviceAlignedChunkWriter;
+import org.apache.iotdb.db.utils.datastructure.PatternTreeMapFactory;
+
+import org.apache.tsfile.common.conf.TSFileConfig;
+import org.apache.tsfile.encrypt.EncryptUtils;
+import org.apache.tsfile.enums.ColumnCategory;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.TableSchema;
+import org.apache.tsfile.read.TsFileSequenceReader;
+import org.apache.tsfile.read.common.TimeRange;
+import org.apache.tsfile.utils.Binary;
+import org.apache.tsfile.write.record.Tablet;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.apache.tsfile.write.writer.TsFileIOWriter;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FlatDeviceTableParserDeletionTest {
+ @Rule public TemporaryFolder folder = new TemporaryFolder();
+
+ private static final TsFileInsertionEventParserMemoryBlock MEMORY =
+ new TsFileInsertionEventParserMemoryBlock() {
+ @Override
+ public long getMemoryUsageInBytes() {
+ return 1L << 30;
+ }
+
+ @Override
+ public void forceResize(long newSizeInBytes) {}
+
+ @Override
+ public void close() {}
+ };
+
+ @Test
+ public void testTagScopedDeletionCannotSkipWholeChunk() throws Exception {
+ FlatDeviceDeletionEntry deletion =
+ deletion(new TagPredicate.SegmentExactMatch("a", 1), List.of());
+ List<Tablet> tablets = parse(new String[] {"a", "b"}, new long[] {1, 2},
deletion);
+ Assert.assertEquals(List.of(2L), times(tablets));
+ Assert.assertEquals(binary("b"), ((Binary[])
tablets.get(0).getValues()[0])[0]);
+ }
+
+ @Test
+ public void testDeletionSurvivesTimeResetAcrossLogicalDevices() throws
Exception {
+ List<Tablet> tablets =
+ parse(
+ new String[] {"a", "b", "c"},
+ new long[] {3, 1, 2},
+ deletion(new TagPredicate.NOP(), List.of()));
+ Assert.assertEquals(List.of(3L), times(tablets));
+ }
+
+ @Test
+ public void testScopedFieldDeletionKeepsOtherFieldsAndTags() throws
Exception {
+ List<Tablet> tablets =
+ parse(
+ new String[] {"a", "b"},
+ new long[] {1, 2},
+ deletion(new TagPredicate.SegmentExactMatch("a", 1),
List.of("s0")));
+ Assert.assertEquals(List.of(1L, 2L), times(tablets));
+ Tablet tablet = tablets.get(0);
+ Assert.assertEquals(binary("a"), ((Binary[]) tablet.getValues()[0])[0]);
+ Assert.assertTrue(tablet.getBitMaps()[2].isMarked(0));
+ Assert.assertEquals(101L, ((long[]) tablet.getValues()[3])[0]);
+ Assert.assertFalse(tablet.getBitMaps()[2].isMarked(1));
+ }
+
+ @Test
+ public void testTagMappingUsesDeletionColumnNames() throws Exception {
+ FlatDeviceDeletionEntry deletion =
+ deletion(
+ new TagPredicate.FullExactMatch(
+ IDeviceID.Factory.DEFAULT_FACTORY.create(new String[] {"t",
"x", "a"})),
+ List.of());
+ deletion.setTagColumnNames(List.of("tag1", "tag0"));
+ Assert.assertEquals(
+ List.of(2L), times(parse(new String[] {"a", "b"}, new long[] {1, 2},
deletion)));
+ }
+
+ @Test
+ public void testNullAndMissingTagsMatchNullPredicate() throws Exception {
+ FlatDeviceDeletionEntry deletion =
+ deletion(new TagPredicate.SegmentExactMatch(null, 1), List.of());
+ Assert.assertEquals(
+ List.of(2L), times(parse(new String[] {null, "b"}, new long[] {1, 2},
deletion)));
+ deletion.setTagColumnNames(List.of("new_tag", "tag0"));
+ Assert.assertTrue(times(parse(new String[] {"a", "b"}, new long[] {1, 2},
deletion)).isEmpty());
+ }
+
+ @Test
+ public void testOverlappingRangesRetainIndependentTagPredicates() throws
Exception {
+ FlatDeviceDeletionEntry first = deletion(new
TagPredicate.SegmentExactMatch("a", 1), List.of());
+ FlatDeviceDeletionEntry second =
+ new FlatDeviceDeletionEntry(
+ new DeletionPredicate("t", new TagPredicate.SegmentExactMatch("b",
1)),
+ new TimeRange(1, 1));
+ second.setTagColumnNames(List.of("tag0", "tag1"));
+ Assert.assertEquals(
+ List.of(2L),
+ times(parse(new String[] {"a", "b", "b"}, new long[] {1, 1, 2}, first,
second)));
+ }
+
+ private FlatDeviceDeletionEntry deletion(TagPredicate predicate,
List<String> fields) {
+ FlatDeviceDeletionEntry deletion =
+ new FlatDeviceDeletionEntry(
+ new DeletionPredicate("t", predicate, fields), new TimeRange(1,
2));
+ deletion.setTagColumnNames(List.of("tag0", "tag1"));
+ return deletion;
+ }
+
+ private List<Tablet> parse(String[] tags, long[] times,
FlatDeviceDeletionEntry... deletions)
+ throws Exception {
+ File file = folder.newFile();
+ List<IMeasurementSchema> schemas =
+ List.of(
+ new MeasurementSchema("tag0", TSDataType.STRING),
+ new MeasurementSchema("tag1", TSDataType.STRING),
+ new MeasurementSchema("s0", TSDataType.INT64),
+ new MeasurementSchema("s1", TSDataType.INT64));
+ try (TsFileIOWriter writer = new TsFileIOWriter(file)) {
+ writer
+ .getSchema()
+ .registerTableSchema(
+ new TableSchema(
+ "t",
+ schemas,
+ List.of(
+ ColumnCategory.TAG,
+ ColumnCategory.TAG,
+ ColumnCategory.FIELD,
+ ColumnCategory.FIELD)));
+ TsFileTableSchemaUtil.addFlatDeviceProperty(writer, "t", true);
+ writer.startChunkGroup(IDeviceID.Factory.DEFAULT_FACTORY.create(new
String[] {"t"}));
+ FlatDeviceAlignedChunkWriter chunk =
+ new FlatDeviceAlignedChunkWriter(schemas,
EncryptUtils.getEncryptParameter());
+ for (int rowIndex = 0; rowIndex < times.length; rowIndex++) {
+ long time = times[rowIndex];
+ chunk.write(
+ time,
+ tags[rowIndex] == null ? Binary.EMPTY_VALUE :
binary(tags[rowIndex]),
+ tags[rowIndex] == null);
+ chunk.write(time, binary("x"), false);
+ chunk.write(time, time, false);
+ chunk.write(time, 100L + time, false);
+ chunk.write(time);
+ }
+ chunk.writeToFileWriter(writer);
+ writer.endChunkGroup();
+ writer.endFile();
+ }
+ var mods = PatternTreeMapFactory.getModsPatternTreeMap();
+ for (FlatDeviceDeletionEntry deletion : deletions) {
+ mods.append(deletion.keyOfPatternTree(), deletion);
+ }
+ List<Tablet> tablets = new ArrayList<>();
+ try (TsFileSequenceReader reader = new
TsFileSequenceReader(file.getPath())) {
+ TsFileInsertionEventTableParserTabletIterator parser =
+ new TsFileInsertionEventTableParserTabletIterator(
+ reader,
+ entry -> true,
+ MEMORY,
+ MEMORY,
+ MEMORY,
+ MEMORY,
+ MEMORY,
+ mods,
+ Long.MIN_VALUE,
+ Long.MAX_VALUE);
+ while (parser.hasNext()) {
+ Tablet tablet = parser.next();
+ if (tablet.getRowSize() > 0) {
+ tablets.add(tablet);
+ }
+ }
+ }
+ return tablets;
+ }
+
+ private List<Long> times(List<Tablet> tablets) {
+ List<Long> result = new ArrayList<>();
+ for (Tablet tablet : tablets) {
+ for (int rowIndex = 0; rowIndex < tablet.getRowSize(); rowIndex++) {
+ result.add(tablet.getTimestamp(rowIndex));
+ }
+ }
+ return result;
+ }
+
+ private static Binary binary(String value) {
+ return new Binary(value, TSFileConfig.STRING_CHARSET);
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/compaction/repair/RepairDataFileScanUtilTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/compaction/repair/RepairDataFileScanUtilTest.java
index 90ae850afa1..433b99dd087 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/compaction/repair/RepairDataFileScanUtilTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/compaction/repair/RepairDataFileScanUtilTest.java
@@ -20,24 +20,35 @@
package org.apache.iotdb.db.storageengine.dataregion.compaction.repair;
import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.schema.table.TsFileTableSchemaUtil;
import org.apache.iotdb.db.exception.StorageEngineException;
import
org.apache.iotdb.db.storageengine.dataregion.compaction.AbstractCompactionTest;
import
org.apache.iotdb.db.storageengine.dataregion.compaction.utils.CompactionTestFileWriter;
+import
org.apache.iotdb.db.storageengine.dataregion.tsfile.FlatDeviceAlignedChunkWriter;
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
import
org.apache.iotdb.db.storageengine.dataregion.tsfile.timeindex.ArrayDeviceTimeIndex;
+import org.apache.tsfile.encrypt.EncryptUtils;
+import org.apache.tsfile.enums.ColumnCategory;
+import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.exception.write.WriteProcessException;
import org.apache.tsfile.file.metadata.ChunkMetadata;
import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.TableSchema;
import org.apache.tsfile.file.metadata.enums.CompressionType;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
import org.apache.tsfile.read.common.TimeRange;
+import org.apache.tsfile.utils.Binary;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.apache.tsfile.write.writer.TsFileIOWriter;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
@@ -83,6 +94,46 @@ public class RepairDataFileScanUtilTest extends
AbstractCompactionTest {
Assert.assertFalse(scanUtil.hasUnsortedDataOrWrongStatistics());
}
+ @Test
+ public void testFlatDeviceNullTagOrder() throws IOException {
+ for (boolean unsorted : List.of(false, true)) {
+ TsFileResource resource = createEmptyFileAndResource(true);
+ List<IMeasurementSchema> schemas =
+ List.of(
+ new MeasurementSchema("tag", TSDataType.STRING),
+ new MeasurementSchema("s", TSDataType.INT64));
+ try (TsFileIOWriter writer = new TsFileIOWriter(resource.getTsFile())) {
+ writer
+ .getSchema()
+ .registerTableSchema(
+ new TableSchema("t", schemas, List.of(ColumnCategory.TAG,
ColumnCategory.FIELD)));
+ TsFileTableSchemaUtil.addFlatDeviceProperty(writer, "t", true);
+ writer.startChunkGroup(IDeviceID.Factory.DEFAULT_FACTORY.create(new
String[] {"t"}));
+ FlatDeviceAlignedChunkWriter chunk =
+ new FlatDeviceAlignedChunkWriter(schemas,
EncryptUtils.getEncryptParameter());
+ // Null TAGs come first and may repeat at increasing times. A later
non-null TAG may
+ // restart time; placing a null TAG after it must be unsorted, never a
broken file.
+ String[] tags = unsorted ? new String[] {"a", null} : new String[]
{null, null, "a"};
+ for (int i = 0; i < tags.length; i++) {
+ long time = tags[i] == null ? i + 1 : 1;
+ chunk.write(
+ time,
+ tags[i] == null ? Binary.EMPTY_VALUE : new Binary(tags[i],
StandardCharsets.UTF_8),
+ tags[i] == null);
+ chunk.write(time, time, false);
+ chunk.write(time);
+ }
+ chunk.writeToFileWriter(writer);
+ writer.endChunkGroup();
+ writer.endFile();
+ }
+ RepairDataFileScanUtil scan = new RepairDataFileScanUtil(resource);
+ scan.scanTsFile(false);
+ Assert.assertFalse(scan.isBrokenFile());
+ Assert.assertEquals(unsorted, scan.hasUnsortedDataOrWrongStatistics());
+ }
+ }
+
@Test
public void testWrongChunkStatisticsWithNonAlignedSeries() throws
IOException {
TsFileResource resource = createEmptyFileAndResource(true);
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
index 598a67d83da..16ffa8761ad 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
@@ -27,6 +27,7 @@ import org.apache.iotdb.commons.path.AlignedPath;
import org.apache.iotdb.commons.path.NonAlignedFullPath;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.commons.schema.table.TsFileTableSchemaUtil;
import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
import org.apache.iotdb.db.conf.IoTDBConfig;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -88,6 +89,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1571,11 +1573,39 @@ public class TsFileProcessorTest {
assertEquals(Set.of(physicalDevice),
processor.getWorkMemTable().getMemTableMap().keySet());
assertTrue(resource.isSatisfied(physicalDevice, TimeFilterApi.eq(70),
sequence, false));
+ processor.asyncFlush();
+ processor.getCloseFuture().get(30, TimeUnit.SECONDS);
+ assertTrue(processor.getFlushingMemTable().isEmpty());
+
// An all-null logical ID equals the table-name ID. It must extend the
table range,
// never narrow it to the null-TAG device alone.
processor.insert(flatDeviceRow(90, null, schemas), new long[5]);
processor.insert(flatDeviceRow(100, "d2", schemas), new long[5]);
+ // Repeated keys must increase write metrics but not the final TsFile row
count.
+ for (int i = 0; i < 5; i++) {
+ processor.insert(flatDeviceRow(10, "d1", schemas), new long[5]);
+ }
+ // This standalone processor reuses the future slot for flush and close
requests.
+ processor.setCloseFuture(null);
processor.asyncClose().get(30, TimeUnit.SECONDS);
+ try (TsFileSequenceReader reader = new TsFileSequenceReader(filePath)) {
+ Map<String, byte[]> properties = reader.getTsFileProperties();
+ assertEquals(
+ // Eight rows in the first flush, then three physical rows in the
second. The five
+ // repeated writes at (d1,10) resolve to one row in that second
flush.
+ 11,
+ ByteBuffer.wrap(
+ properties.get(
+
TsFileTableSchemaUtil.getFlatDevicePointCountPropertyKey("flat_table")))
+ .getLong());
+ assertEquals(
+ 3,
+ ByteBuffer.wrap(
+ properties.get(
+
TsFileTableSchemaUtil.getFlatDeviceLogicalDeviceCountPropertyKey(
+ "flat_table")))
+ .getLong());
+ }
assertTrue(resource.isClosed());
assertEquals(Set.of(physicalDevice), resource.getDevices());
assertResourceTimes(resource, physicalDevice, 10, 100);
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceEncoderTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceEncoderTest.java
index c4bdc712eb0..7a6f3d4d4f2 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceEncoderTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/FlatDeviceEncoderTest.java
@@ -98,6 +98,46 @@ public class FlatDeviceEncoderTest {
assertRow(rows.get(4), 3, "c", 30L, 300L);
}
+ @Test
+ public void testStatisticsCountEncodedRowsAfterDuplicateMergeAndDeletion()
throws Exception {
+ List<TSDataType> types = List.of(TSDataType.STRING, TSDataType.INT64,
TSDataType.INT64);
+ // Exercise both the single-list fast path and the merge across historical
lists.
+ for (boolean multipleLists : List.of(false, true)) {
+ FlatDeviceAlignedTVList old = list(types, List.of(0));
+ FlatDeviceAlignedTVList current = multipleLists ? list(types,
List.of(0)) : old;
+ old.putAlignedValue(1, new Object[] {binary("a"), 10L, null});
+ current.putAlignedValue(1, new Object[] {binary("a"), null, 20L});
+ current.putAlignedValue(1, new Object[] {binary("a"), 11L, null});
+ current.putAlignedValue(2, new Object[] {null, null, null});
+ current.putAlignedValue(3, new Object[] {binary("deleted"), 30L, 30L});
+ current.deleteMatchingRows(3, 3, row -> true, null);
+ BlockingQueue<Object> queue = new LinkedBlockingQueue<>();
+ List<IMeasurementSchema> schemas =
+ IntStream.range(0, types.size())
+ .mapToObj(i -> (IMeasurementSchema) new MeasurementSchema("s" +
i, types.get(i)))
+ .collect(Collectors.toList());
+ FlatDeviceAlignedTVList.EncodingStatistics statistics =
+ FlatDeviceAlignedTVList.encode(
+ multipleLists ? List.of(old, current) : List.of(old),
+ schemas,
+ List.of(0, 1, 2),
+ EncryptDBUtils.getDefaultFirstEncryptParam(),
+ queue,
+ 1,
+ 1);
+ List<Row> rows = read(queue);
+ Assert.assertEquals(2, rows.size());
+ Assert.assertEquals(rows.size(), statistics.getRowCount());
+ Assert.assertEquals(2, statistics.getDevices().size());
+ Assert.assertFalse(
+ statistics
+ .getDevices()
+ .contains(
+ IDeviceID.Factory.DEFAULT_FACTORY.create(new String[]
{"table", "deleted"})));
+ assertRow(rows.get(1), 1, "a", 11L, 20L);
+ }
+ }
+
@Test
public void testSingleListLatestNonNullDeletionAndPageChunkBoundaries()
throws Exception {
List<TSDataType> types = List.of(TSDataType.STRING, TSDataType.INT64,
TSDataType.INT64);
@@ -292,7 +332,7 @@ public class FlatDeviceEncoderTest {
}
List<Row> rows = new ArrayList<>();
try (TsFileSequenceReader reader = new
TsFileSequenceReader(file.getPath())) {
- for (AbstractAlignedChunkMetadata metadata :
reader.getAlignedChunkMetadata(device, true)) {
+ for (AbstractAlignedChunkMetadata metadata :
reader.getAlignedChunkMetadata(device, false)) {
Chunk time = reader.readMemChunk((ChunkMetadata)
metadata.getTimeChunkMetadata());
List<Chunk> values = new ArrayList<>();
for (IChunkMetadata value : metadata.getValueChunkMetadataList()) {
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtil.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtil.java
index 17c2acb0168..2c3a250ab71 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtil.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtil.java
@@ -46,7 +46,7 @@ import static
org.apache.iotdb.commons.schema.table.TsTable.TIME_COLUMN_NAME;
public class TsFileTableSchemaUtil {
private static final String FLAT_DEVICE_TS_FILE_PROPERTY_PREFIX =
- TsTable.FLAT_DEVICE_PROPERTY + ".";
+ "iotdb." + TsTable.FLAT_DEVICE_PROPERTY + ".";
private static final byte[] FLAT_DEVICE_PROPERTY_BYTES =
TsTable.FLAT_DEVICE_PROPERTY.getBytes(StandardCharsets.UTF_8);
private static final byte[] TRUE_BYTES = {1};
@@ -267,25 +267,26 @@ public class TsFileTableSchemaUtil {
}
public static String getFlatDevicePropertyKey(final String tableName) {
- return FLAT_DEVICE_TS_FILE_PROPERTY_PREFIX + tableName;
+ // Disjoint prefixes prevent a table named point_count_t from shadowing
t's statistics.
+ return FLAT_DEVICE_TS_FILE_PROPERTY_PREFIX + "layout." + tableName;
}
public static String getFlatDeviceLogicalDeviceCountPropertyKey(final String
tableName) {
- return FLAT_DEVICE_TS_FILE_PROPERTY_PREFIX + "logical_device_count_" +
tableName;
+ return FLAT_DEVICE_TS_FILE_PROPERTY_PREFIX + "logical_device_count." +
tableName;
}
public static String getFlatDevicePointCountPropertyKey(final String
tableName) {
- return FLAT_DEVICE_TS_FILE_PROPERTY_PREFIX + "point_count_" + tableName;
+ return FLAT_DEVICE_TS_FILE_PROPERTY_PREFIX + "row_count." + tableName;
}
public static void addFlatDeviceStatistics(
final TsFileIOWriter writer,
final String tableName,
final long logicalDeviceCount,
- final long pointCount) {
+ final long rowCount) {
writer.addTsFileProperty(
getFlatDeviceLogicalDeviceCountPropertyKey(tableName),
longBytes(logicalDeviceCount));
- writer.addTsFileProperty(getFlatDevicePointCountPropertyKey(tableName),
longBytes(pointCount));
+ writer.addTsFileProperty(getFlatDevicePointCountPropertyKey(tableName),
longBytes(rowCount));
}
private static byte[] longBytes(long value) {
@@ -298,7 +299,7 @@ public class TsFileTableSchemaUtil {
if (Objects.isNull(tsFileProperties)) {
return false;
}
- final byte[] value =
tsFileProperties.get(getFlatDevicePropertyKey(tableName));
+ byte[] value = tsFileProperties.get(getFlatDevicePropertyKey(tableName));
return value != null && value.length == 1 && value[0] == 1;
}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtilTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtilTest.java
index 07a83a4838d..237bae5bd76 100644
---
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtilTest.java
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/TsFileTableSchemaUtilTest.java
@@ -82,6 +82,53 @@ public class TsFileTableSchemaUtilTest {
Assert.assertFalse(TsFileTableSchemaUtil.isFlatDevice(new HashMap<>(),
"table"));
}
+ @Test
+ public void testFlatDeviceStatisticsDoNotShadowTableMarkers() throws
IOException {
+ File file = temporaryFolder.newFile("collision-properties.tsfile");
+ List<String> tables =
+ List.of("t", "point_count_t", "logical_device_count_t", "layout.t",
"row_count.t");
+ try (TsFileIOWriter writer = new TsFileIOWriter(file)) {
+ for (String table : tables) {
+ TsFileTableSchemaUtil.addFlatDeviceProperty(writer, table, true);
+ TsFileTableSchemaUtil.addFlatDeviceStatistics(writer, table, 2, 3);
+ }
+ writer.endFile();
+ }
+ try (TsFileSequenceReader reader = new
TsFileSequenceReader(file.getAbsolutePath())) {
+ Map<String, byte[]> properties = reader.getTsFileProperties();
+ Assert.assertEquals(
+ tables.size() * 3,
+ properties.keySet().stream().filter(key ->
key.startsWith("iotdb.flat_device.")).count());
+ for (String table : tables) {
+ Assert.assertTrue(TsFileTableSchemaUtil.isFlatDevice(properties,
table));
+ Assert.assertEquals(
+ 2,
+ ByteBuffer.wrap(
+ properties.get(
+
TsFileTableSchemaUtil.getFlatDeviceLogicalDeviceCountPropertyKey(table)))
+ .getLong());
+ Assert.assertEquals(
+ 3,
+ ByteBuffer.wrap(
+
properties.get(TsFileTableSchemaUtil.getFlatDevicePointCountPropertyKey(table)))
+ .getLong());
+ }
+ }
+ }
+
+ @Test
+ public void testLegacyFlatDeviceMarker() {
+ Assert.assertTrue(
+ TsFileTableSchemaUtil.isFlatDevice(Map.of("flat_device.t", new byte[]
{1}), "t"));
+ Assert.assertFalse(
+ TsFileTableSchemaUtil.isFlatDevice(
+ Map.of("flat_device.t",
ByteBuffer.allocate(Long.BYTES).putLong(1).array()), "t"));
+ // An old table called layout.t must not be mistaken for t's new layout
property.
+ Assert.assertFalse(
+ TsFileTableSchemaUtil.isFlatDevice(
+ Map.of("flat_device.t", new byte[] {0}, "flat_device.layout.t",
new byte[] {1}), "t"));
+ }
+
@Test
public void testInvalidFlatDevicePropertyDefaultsToFalse() {
final Map<String, byte[]> properties = new HashMap<>();