This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new a498d7d2a46 Fix schema mutation validation, accounting and deletion
performance (#18861)
a498d7d2a46 is described below
commit a498d7d2a4608993c5c7d0c11f954e77b0554900
Author: Caideyipi <[email protected]>
AuthorDate: Wed Oct 14 14:34:34 2026 +0800
Fix schema mutation validation, accounting and deletion performance (#18861)
* Fix schema mutation validation, accounting and deletion performance
Validate aligned batch aliases, keep blacklist statistics idempotent, avoid
repeated child scans during deletion, and correct and reuse device attribute
storage. Add regression coverage for retries, snapshots, nested devices and all
schema engine modes.
* Fix aligned memchunk tests for current API
---
.../schema/source/DeviceBlackListConstructor.java | 14 ++-
.../attribute/DeviceAttributeStore.java | 75 ++++++++----
.../mtree/impl/mem/MTreeBelowSGMemoryImpl.java | 23 +---
.../impl/mem/mnode/impl/BasicInternalMNode.java | 60 +++++++---
.../mtree/impl/pbtree/MTreeBelowSGCachedImpl.java | 1 +
.../schemaregion/utils/MetaFormatUtils.java | 23 ++++
.../schemaRegion/SchemaRegionAliasAndTagTest.java | 62 ++++++++++
.../source/DeviceBlackListConstructorTest.java | 120 +++++++++++++++++++
.../attribute/DeviceAttributeStoreTest.java | 129 +++++++++++++++++++++
.../mtree/impl/mem/MTreeBelowSGMemoryImplTest.java | 129 +++++++++++++++++++++
10 files changed, 575 insertions(+), 61 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/schema/source/DeviceBlackListConstructor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/schema/source/DeviceBlackListConstructor.java
index 3391f3e4080..b8f315b3a0c 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/schema/source/DeviceBlackListConstructor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/schema/source/DeviceBlackListConstructor.java
@@ -64,9 +64,8 @@ public class DeviceBlackListConstructor extends DeviceUpdater
{
public void handleDeviceNode(final IDeviceMNode<IMemMNode> node) throws
MetadataException {
if (withoutFilter()) {
- node.preDeactivateSelfOrTemplate();
+ preDeleteDevice(node);
preDeletedNum++;
- regionStatistics.decreaseTableDevice(tableName, 1);
return;
}
nodes.add(node);
@@ -76,14 +75,21 @@ public class DeviceBlackListConstructor extends
DeviceUpdater {
@Override
protected void update() {
preDeletedNum += indexes.size();
- regionStatistics.decreaseTableDevice(tableName, indexes.size());
for (final Integer index : indexes) {
- nodes.get(index).preDeactivateSelfOrTemplate();
+ preDeleteDevice(nodes.get(index));
}
nodes.clear();
super.clear();
}
+ private void preDeleteDevice(final IDeviceMNode<IMemMNode> node) {
+ // Retried requests still count matches, but statistics only change on the
first transition.
+ if (!node.isPreDeactivateSelfOrTemplate()) {
+ node.preDeactivateSelfOrTemplate();
+ regionStatistics.decreaseTableDevice(tableName, 1);
+ }
+ }
+
public long getPreDeletedNum() {
return preDeletedNum;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/attribute/DeviceAttributeStore.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/attribute/DeviceAttributeStore.java
index 2c71005fab2..8b9e65b5e2d 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/attribute/DeviceAttributeStore.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/attribute/DeviceAttributeStore.java
@@ -43,6 +43,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -57,6 +58,9 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
private static final long MAP_SIZE =
RamUsageEstimator.shallowSizeOfInstance(HashMap.class);
private final List<Map<String, Binary>> deviceAttributeList = new
ArrayList<>();
+ // Reuse holes without moving live pointers or changing the snapshot format.
+ private BitSet freeAttributePointers = new BitSet(0);
+ private int nextFreeAttributePointer;
private final MemSchemaRegionStatistics regionStatistics;
@@ -67,8 +71,10 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
// It's OK not to calculate the region statistics here. The caller shall
ensure that the
// statistics is cleared at the outside.
@Override
- public void clear() {
+ public synchronized void clear() {
deviceAttributeList.clear();
+ freeAttributePointers = new BitSet(0);
+ nextFreeAttributePointer = 0;
}
@Override
@@ -113,7 +119,7 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
}
@Override
- public void loadFromSnapshot(final File snapshotDir) throws IOException {
+ public synchronized void loadFromSnapshot(final File snapshotDir) throws
IOException {
final File snapshot =
SystemFileFactory.INSTANCE.getFile(snapshotDir,
SchemaConstant.DEVICE_ATTRIBUTE_SNAPSHOT);
if (!snapshot.exists()) {
@@ -133,7 +139,7 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
public synchronized int createAttribute(
final List<String> nameList, final Object[] valueList, final String
tableName) {
// todo implement storage for device of diverse data types
- long memUsage = MAP_SIZE + RamUsageEstimator.NUM_BYTES_OBJECT_REF;
+ long memUsage = MAP_SIZE;
final Map<String, Binary> attributeMap = new HashMap<>();
for (int i = 0; i < nameList.size(); i++) {
if (valueList.length <= i) {
@@ -147,19 +153,29 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
memUsage += MemUsageUtil.computeKVMemUsageInMap(nameList.get(i), value);
addTableAttributeMemory(tableName, value.ramBytesUsed());
}
- deviceAttributeList.add(attributeMap);
+ int pointer = freeAttributePointers.nextSetBit(nextFreeAttributePointer);
+ if (pointer < 0) {
+ pointer = deviceAttributeList.size();
+ deviceAttributeList.add(attributeMap);
+ memUsage += RamUsageEstimator.NUM_BYTES_OBJECT_REF;
+ } else {
+ deviceAttributeList.set(pointer, attributeMap);
+ freeAttributePointers.clear(pointer);
+ nextFreeAttributePointer = pointer + 1;
+ }
requestMemory(memUsage);
- return deviceAttributeList.size() - 1;
+ return pointer;
}
@Override
- public Map<String, Binary> alterAttribute(
+ public synchronized Map<String, Binary> alterAttribute(
final int pointer,
final List<String> nameList,
final Object[] valueList,
final String tableName) {
// todo implement storage for device of diverse data types
long memUsageDelta = 0L;
+ long valueMemUsageDelta = 0L;
long originMemUsage;
long updatedMemUsage;
final Map<String, Binary> updateMap = new HashMap<>();
@@ -173,11 +189,12 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
continue;
}
final Binary value = (Binary) valueList[i];
+ final Binary oldValue = attributeMap.get(key);
+ valueMemUsageDelta +=
+ (value == null ? 0 : value.ramBytesUsed())
+ - (oldValue == null ? 0 : oldValue.ramBytesUsed());
- originMemUsage =
- attributeMap.containsKey(key)
- ? MemUsageUtil.computeKVMemUsageInMap(key, attributeMap.get(key))
- : 0;
+ originMemUsage = oldValue == null ? 0 :
MemUsageUtil.computeKVMemUsageInMap(key, oldValue);
if (value != null) {
if (!Objects.equals(value, attributeMap.put(key, value))) {
updateMap.put(key, value);
@@ -193,28 +210,33 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
}
if (memUsageDelta > 0) {
requestMemory(memUsageDelta);
- addTableAttributeMemory(tableName, memUsageDelta);
} else if (memUsageDelta < 0) {
releaseMemory(-memUsageDelta);
- decreaseTableAttributeMemory(tableName, -memUsageDelta);
+ }
+ if (valueMemUsageDelta > 0) {
+ addTableAttributeMemory(tableName, valueMemUsageDelta);
+ } else if (valueMemUsageDelta < 0) {
+ decreaseTableAttributeMemory(tableName, -valueMemUsageDelta);
}
return updateMap;
}
@Override
- public void removeAttribute(final int pointer, final String tableName) {
- releaseMemory(
- MAP_SIZE +
UpdateDetailContainer.sizeOfMapEntries(deviceAttributeList.get(pointer)));
+ public synchronized void removeAttribute(final int pointer, final String
tableName) {
+ final Map<String, Binary> attributeMap = deviceAttributeList.get(pointer);
+ if (attributeMap == null) {
+ return;
+ }
+ releaseMemory(MAP_SIZE +
UpdateDetailContainer.sizeOfMapEntries(attributeMap));
decreaseTableAttributeMemory(
tableName,
- deviceAttributeList.get(pointer).values().stream()
- .map(UpdateDetailContainer::sizeOf)
- .reduce(0L, Long::sum));
+
attributeMap.values().stream().map(UpdateDetailContainer::sizeOf).reduce(0L,
Long::sum));
deviceAttributeList.set(pointer, null);
+ markFreeAttributePointer(pointer);
}
@Override
- public void removeAttribute(
+ public synchronized void removeAttribute(
final int pointer, final String attributeName, final String tableName) {
final Map<String, Binary> attributeMap = deviceAttributeList.get(pointer);
if (Objects.isNull(attributeMap)) {
@@ -228,12 +250,12 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
}
@Override
- public Map<String, Binary> getAttributes(final int pointer) {
+ public synchronized Map<String, Binary> getAttributes(final int pointer) {
return deviceAttributeList.get(pointer);
}
@Override
- public Binary getAttributes(final int pointer, final String name) {
+ public synchronized Binary getAttributes(final int pointer, final String
name) {
return deviceAttributeList.get(pointer).get(name);
}
@@ -271,6 +293,9 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
for (int i = 0; i < size; i++) {
final Map<String, Binary> attributeMap = readMap(inputStream, false);
deviceAttributeList.add(attributeMap);
+ if (attributeMap == null) {
+ markFreeAttributePointer(i);
+ }
requestMemory(
RamUsageEstimator.NUM_BYTES_OBJECT_REF
+ (Objects.nonNull(attributeMap)
@@ -279,6 +304,14 @@ public class DeviceAttributeStore implements
IDeviceAttributeStore {
}
}
+ private void markFreeAttributePointer(final int pointer) {
+ final int oldCapacity = freeAttributePointers.size();
+ freeAttributePointers.set(pointer);
+ // Account for bitmap storage as well as the retained references in
deviceAttributeList.
+ requestMemory((freeAttributePointers.size() - oldCapacity) / Byte.SIZE);
+ nextFreeAttributePointer = Math.min(nextFreeAttributePointer, pointer);
+ }
+
public static Map<String, Binary> readMap(final InputStream inputStream,
final boolean concurrent)
throws IOException {
final int length = ReadWriteIOUtils.readInt(inputStream);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImpl.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImpl.java
index f920c6d0efe..a5007d8e998 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImpl.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImpl.java
@@ -54,6 +54,7 @@ import
org.apache.iotdb.db.queryengine.plan.statement.component.Ordering;
import org.apache.iotdb.db.schemaengine.metric.SchemaRegionMemMetric;
import org.apache.iotdb.db.schemaengine.rescon.MemSchemaRegionStatistics;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.IMemMNode;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.impl.BasicInternalMNode;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.info.TableDeviceInfo;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.loader.MNodeFactoryLoader;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.traverser.collector.EntityCollector;
@@ -403,6 +404,7 @@ public class MTreeBelowSGMemoryImpl {
throws MetadataException {
final List<IMeasurementMNode<IMemMNode>> measurementMNodeList = new
ArrayList<>();
MetaFormatUtils.checkSchemaMeasurementNames(measurements);
+ MetaFormatUtils.checkSchemaMeasurementAliases(devicePath, measurements,
aliasList);
final IMemMNode deviceParent = checkAndAutoCreateInternalPath(devicePath);
// synchronize check and add, we need addChild operation be atomic.
@@ -706,28 +708,13 @@ public class MTreeBelowSGMemoryImpl {
public void deleteEmptyInternalMNode(final IDeviceMNode<IMemMNode>
entityMNode) {
IMemMNode curNode = entityMNode.getAsMNode();
if (!entityMNode.isUseTemplate()) {
- boolean hasMeasurement = false;
- boolean hasNonViewMeasurement = false;
- IMemMNode child;
- try (final IMNodeIterator<IMemMNode> iterator =
store.getChildrenIterator(curNode)) {
- while (iterator.hasNext()) {
- child = iterator.next();
- if (child.isMeasurement()) {
- hasMeasurement = true;
- if (!child.getAsMeasurementMNode().isLogicalView()) {
- hasNonViewMeasurement = true;
- break;
- }
- }
- }
- }
-
- if (!hasMeasurement) {
+ final BasicInternalMNode internalNode = (BasicInternalMNode)
curNode.getBasicMNode();
+ if (internalNode.getMeasurementChildCount() == 0) {
synchronized (this) {
curNode = store.setToInternal(entityMNode);
}
refreshAncestorsHavingDeviceDescendant(curNode.getParent());
- } else if (!hasNonViewMeasurement) {
+ } else if (internalNode.getNonViewMeasurementChildCount() == 0) {
// has some measurement but they are all logical view
entityMNode.setAligned(null);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/BasicInternalMNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/BasicInternalMNode.java
index 8eb37b8bff7..a37b4234d70 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/BasicInternalMNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/BasicInternalMNode.java
@@ -47,6 +47,10 @@ public class BasicInternalMNode extends BasicMNode
implements IInternalMNode<IMe
@SuppressWarnings("squid:S3077")
private volatile IDeviceInfo<IMemMNode> deviceInfo = null;
+ // Direct children only. Snapshot loading rebuilds these counters through
addChild.
+ private int measurementChildCount;
+ private int nonViewMeasurementChildCount;
+
/** Constructor of MNode. */
public BasicInternalMNode(IMemMNode parent, String name) {
super(parent, name);
@@ -98,6 +102,9 @@ public class BasicInternalMNode extends BasicMNode
implements IInternalMNode<IMe
}
child.setParent(this);
IMemMNode existingChild = children.putIfAbsent(name, child);
+ if (existingChild == null) {
+ updateMeasurementChildCount(child, 1);
+ }
return existingChild == null ? child : existingChild;
}
@@ -114,21 +121,7 @@ public class BasicInternalMNode extends BasicMNode
implements IInternalMNode<IMe
*/
@Override
public IMemMNode addChild(IMemMNode child) {
- /* use cpu time to exchange memory
- * measurementNode's children should be null to save memory
- * add child method will only be called when writing MTree, which is not a
frequent operation
- */
- if (children == null) {
- // double check, children is volatile
- synchronized (this) {
- if (children == null) {
- children = new MemMNodeContainer();
- }
- }
- }
-
- child.setParent(this);
- children.putIfAbsent(child.getName(), child);
+ addChild(child.getName(), child);
return child;
}
@@ -136,7 +129,11 @@ public class BasicInternalMNode extends BasicMNode
implements IInternalMNode<IMe
@Override
public IMemMNode deleteChild(String name) {
if (children != null) {
- return children.remove(name);
+ final IMemMNode child = children.remove(name);
+ if (child != null) {
+ updateMeasurementChildCount(child, -1);
+ }
+ return child;
}
return null;
}
@@ -152,12 +149,39 @@ public class BasicInternalMNode extends BasicMNode
implements IInternalMNode<IMe
@Override
public void setChildren(IMNodeContainer<IMemMNode> children) {
this.children = children;
+ measurementChildCount = 0;
+ nonViewMeasurementChildCount = 0;
+ if (children != null) {
+ children.values().forEach(child -> updateMeasurementChildCount(child,
1));
+ }
+ }
+
+ private void updateMeasurementChildCount(final IMemMNode child, final int
delta) {
+ if (child.isMeasurement()) {
+ measurementChildCount += delta;
+ if (!child.getAsMeasurementMNode().isLogicalView()) {
+ nonViewMeasurementChildCount += delta;
+ }
+ }
+ }
+
+ public int getMeasurementChildCount() {
+ return measurementChildCount;
+ }
+
+ public int getNonViewMeasurementChildCount() {
+ return nonViewMeasurementChildCount;
}
- /** MNodeContainer reference and basic occupation, 8 + 80B. DeviceInfo
reference and size. */
+ /** MNodeContainer reference and basic occupation, 8 + 80B. Counters, 8B.
DeviceInfo and size. */
@Override
public int estimateSize() {
- return 8 + 80 + super.estimateSize() + 8 + (deviceInfo == null ? 0 :
deviceInfo.estimateSize());
+ return 8
+ + 80
+ + 8
+ + super.estimateSize()
+ + 8
+ + (deviceInfo == null ? 0 : deviceInfo.estimateSize());
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/MTreeBelowSGCachedImpl.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/MTreeBelowSGCachedImpl.java
index f181766731d..b3c531a97d6 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/MTreeBelowSGCachedImpl.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/MTreeBelowSGCachedImpl.java
@@ -471,6 +471,7 @@ public class MTreeBelowSGCachedImpl {
throws MetadataException {
final List<IMeasurementMNode<ICachedMNode>> measurementMNodeList = new
ArrayList<>();
MetaFormatUtils.checkSchemaMeasurementNames(measurements);
+ MetaFormatUtils.checkSchemaMeasurementAliases(devicePath, measurements,
aliasList);
final ICachedMNode deviceParent =
checkAndAutoCreateInternalPath(devicePath);
try {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
index 57ba2fbb366..c28a918e451 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/utils/MetaFormatUtils.java
@@ -22,15 +22,18 @@ import
org.apache.iotdb.commons.exception.IllegalPathException;
import org.apache.iotdb.commons.exception.MetadataException;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.db.conf.IoTDBConfig;
+import org.apache.iotdb.db.exception.metadata.AliasAlreadyExistException;
import org.apache.iotdb.db.exception.metadata.IllegalParameterOfPathException;
import org.apache.iotdb.db.i18n.DataNodeSchemaMessages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import static org.apache.iotdb.commons.conf.IoTDBConstant.LOSS;
import static org.apache.iotdb.commons.conf.IoTDBConstant.SDT;
@@ -102,6 +105,26 @@ public class MetaFormatUtils {
}
}
+ /** Validate the shared measurement/alias namespace before creating any
member of a batch. */
+ public static void checkSchemaMeasurementAliases(
+ final PartialPath devicePath, final List<String> measurements, final
List<String> aliases)
+ throws MetadataException {
+ if (aliases == null) {
+ return;
+ }
+ final Set<String> measurementNames = new HashSet<>(measurements);
+ final Set<String> aliasNames = new HashSet<>();
+ for (int i = 0; i < measurements.size(); i++) {
+ final String alias = aliases.get(i);
+ if (alias != null
+ && (!aliasNames.add(alias)
+ || (measurementNames.contains(alias) &&
!alias.equals(measurements.get(i))))) {
+ throw new AliasAlreadyExistException(
+
devicePath.concatAsMeasurementPath(measurements.get(i)).getFullPath(), alias);
+ }
+ }
+ }
+
/** check whether the database name uses illegal characters */
public static void checkDatabase(final String database) throws
IllegalPathException {
if (!IoTDBConfig.DATABASE_PATTERN.matcher(database).matches()) {
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionAliasAndTagTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionAliasAndTagTest.java
index 616a041750a..073fbc72a03 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionAliasAndTagTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionAliasAndTagTest.java
@@ -22,8 +22,10 @@ package org.apache.iotdb.db.metadata.schemaRegion;
import org.apache.iotdb.commons.exception.MetadataException;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.path.PathPatternTree;
+import org.apache.iotdb.db.exception.metadata.AliasAlreadyExistException;
import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegion;
import
org.apache.iotdb.db.schemaengine.schemaregion.read.resp.info.ITimeSeriesSchemaInfo;
+import
org.apache.iotdb.db.schemaengine.schemaregion.write.req.impl.CreateAlignedTimeSeriesPlanImpl;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.file.metadata.enums.CompressionType;
@@ -34,6 +36,7 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -58,6 +61,65 @@ public class SchemaRegionAliasAndTagTest extends
AbstractSchemaRegionTest {
schemaRegion = getSchemaRegion("root.sg", 0);
}
+ @Test
+ public void testAlignedBatchRejectsConflictingAliasesBeforeCreation() throws
Exception {
+ for (final List<String> aliases :
+ Arrays.asList(
+ Arrays.asList("dup", "dup"), Arrays.asList("s2", null),
Arrays.asList(null, "s1"))) {
+ final CreateAlignedTimeSeriesPlanImpl plan = alignedAliasPlan(aliases);
+ try {
+ schemaRegion.createAlignedTimeSeries(plan);
+ Assert.fail("Expected alias collision");
+ } catch (final AliasAlreadyExistException expected) {
+ Assert.assertTrue(
+ SchemaRegionTestUtil.showTimeseries(schemaRegion, new
PartialPath("root.sg.batch.*"))
+ .isEmpty());
+ }
+ }
+ schemaRegion.createAlignedTimeSeries(alignedAliasPlan(Arrays.asList("a1",
"a2")));
+ final CreateAlignedTimeSeriesPlanImpl merge =
alignedAliasPlan(Arrays.asList("dup", "dup"));
+ merge.setWithMerge(true);
+ try {
+ schemaRegion.createAlignedTimeSeries(merge);
+ Assert.fail("Expected alias collision during merge");
+ } catch (final AliasAlreadyExistException expected) {
+ checkAliasAndTagsAndAttributes(
+ "root.sg.batch.s1", "a1", Collections.emptyMap(),
Collections.emptyMap());
+ checkAliasAndTagsAndAttributes(
+ "root.sg.batch.s2", "a2", Collections.emptyMap(),
Collections.emptyMap());
+ }
+ }
+
+ @Test
+ public void testAlignedSelfAliasAndRemainingAliasAfterDeletion() throws
Exception {
+ schemaRegion.createAlignedTimeSeries(alignedAliasPlan(Arrays.asList("s1",
"a2")));
+ final PathPatternTree delete = new PathPatternTree();
+ delete.appendPathPattern(new PartialPath("root.sg.batch.s2"));
+ delete.constructTree();
+ schemaRegion.constructSchemaBlackList(delete);
+ schemaRegion.deleteTimeseriesInBlackList(delete);
+ checkAliasAndTagsAndAttributes(
+ "root.sg.batch.s1", "s1", Collections.emptyMap(),
Collections.emptyMap());
+ Assert.assertEquals(
+ "root.sg.batch.s1",
+ SchemaRegionTestUtil.showTimeseries(schemaRegion, new
PartialPath("root.sg.batch.s1"))
+ .get(0)
+ .getFullPath());
+ }
+
+ private CreateAlignedTimeSeriesPlanImpl alignedAliasPlan(final List<String>
aliases)
+ throws MetadataException {
+ return new CreateAlignedTimeSeriesPlanImpl(
+ new PartialPath("root.sg.batch"),
+ new ArrayList<>(Arrays.asList("s1", "s2")),
+ new ArrayList<>(Collections.nCopies(2, TSDataType.INT32)),
+ new ArrayList<>(Collections.nCopies(2, TSEncoding.PLAIN)),
+ new ArrayList<>(Collections.nCopies(2, CompressionType.SNAPPY)),
+ new ArrayList<>(aliases),
+ null,
+ null);
+ }
+
/**
* Prepare timeseries
*
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/schema/source/DeviceBlackListConstructorTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/schema/source/DeviceBlackListConstructorTest.java
new file mode 100644
index 00000000000..c9649b1da4c
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/schema/source/DeviceBlackListConstructorTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.queryengine.execution.operator.schema.source;
+
+import
org.apache.iotdb.calc.transformation.dag.column.leaf.ConstantColumnTransformer;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.schema.table.TsTable;
+import org.apache.iotdb.db.schemaengine.metric.SchemaRegionMemMetric;
+import org.apache.iotdb.db.schemaengine.rescon.MemSchemaEngineStatistics;
+import org.apache.iotdb.db.schemaengine.rescon.MemSchemaRegionStatistics;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.MTreeBelowSGMemoryImpl;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.read.common.block.column.BooleanColumn;
+import org.apache.tsfile.read.common.type.TypeFactory;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+
+public class DeviceBlackListConstructorTest {
+
+ @Test
+ public void testUnfilteredRetryAndRollback() throws Exception {
+ checkRetryAndRollback(false);
+ }
+
+ @Test
+ public void testFilteredRetryAndRollback() throws Exception {
+ checkRetryAndRollback(true);
+ }
+
+ private void checkRetryAndRollback(final boolean filtered) throws Exception {
+ final MemSchemaRegionStatistics statistics =
+ new MemSchemaRegionStatistics(0, new MemSchemaEngineStatistics());
+ final MTreeBelowSGMemoryImpl tree =
+ new MTreeBelowSGMemoryImpl(
+ PartialPath.getQualifiedDatabasePartialPath("root.db"),
+ node -> Collections.emptyMap(),
+ node -> Collections.emptyMap(),
+ statistics,
+ new SchemaRegionMemMetric(statistics, "root.db"));
+ final AtomicInteger pointer = new AtomicInteger();
+ tree.createOrUpdateTableDevice(
+ "t", new String[] {"d1"}, pointer::getAndIncrement, ignored -> {});
+ tree.createOrUpdateTableDevice(
+ "t", new String[] {"d2"}, pointer::getAndIncrement, ignored -> {});
+ assertEquals(2, statistics.getTableDevicesNumber("t"));
+
+ final PartialPath allDevices = new PartialPath("root.db.t.*");
+ // Both an overlapping pattern in one request and a new request after a
retry visit d1 again.
+ for (int retry = 0; retry < 2; retry++) {
+ final DeviceBlackListConstructor constructor =
newConstructor(statistics, filtered, true);
+ try (final DeviceBlackListConstructor ignored = constructor) {
+ tree.constructTableDeviceBlackList(new PartialPath("root.db.t.d1"),
constructor);
+ tree.constructTableDeviceBlackList(allDevices, constructor);
+ }
+ assertEquals(3, constructor.getPreDeletedNum());
+ assertEquals(0, statistics.getTableDevicesNumber("t"));
+ }
+ tree.rollbackTableDeviceBlackList(allDevices);
+ tree.rollbackTableDeviceBlackList(allDevices);
+ assertEquals(2, statistics.getTableDevicesNumber("t"));
+
+ final DeviceBlackListConstructor noMatch = newConstructor(statistics,
true, false);
+ try (final DeviceBlackListConstructor ignored = noMatch) {
+ tree.constructTableDeviceBlackList(allDevices, noMatch);
+ }
+ assertEquals(0, noMatch.getPreDeletedNum());
+ assertEquals(2, statistics.getTableDevicesNumber("t"));
+
+ final DeviceBlackListConstructor delete = newConstructor(statistics,
filtered, true);
+ try (final DeviceBlackListConstructor ignored = delete) {
+ tree.constructTableDeviceBlackList(allDevices, delete);
+ }
+ final AtomicInteger deleted = new AtomicInteger();
+ tree.deleteTableDevicesInBlackList(
+ allDevices, ignored -> deleted.incrementAndGet(), ignored -> {});
+ assertEquals(2, deleted.get());
+ assertEquals(0, statistics.getTableDevicesNumber("t"));
+ }
+
+ private DeviceBlackListConstructor newConstructor(
+ final MemSchemaRegionStatistics statistics, final boolean filtered,
final boolean matches) {
+ final ConstantColumnTransformer filter =
+ new ConstantColumnTransformer(
+ TypeFactory.getType(TSDataType.BOOLEAN),
+ new BooleanColumn(1, Optional.empty(), new boolean[] {matches}));
+ filter.addReferenceCount();
+ return new DeviceBlackListConstructor(
+ filtered ? Collections.singletonList(filter) : Collections.emptyList(),
+ filtered ? filter : null,
+ "t",
+ Collections.emptyList(),
+ (pointer, name) -> null,
+ statistics,
+ "db",
+ new TsTable("t"));
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/attribute/DeviceAttributeStoreTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/attribute/DeviceAttributeStoreTest.java
index 880f5678418..0730d30d455 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/attribute/DeviceAttributeStoreTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/attribute/DeviceAttributeStoreTest.java
@@ -19,20 +19,149 @@
package org.apache.iotdb.db.schemaengine.schemaregion.attribute;
+import org.apache.iotdb.commons.schema.MemUsageUtil;
+import org.apache.iotdb.commons.schema.SchemaConstant;
+import org.apache.iotdb.db.schemaengine.rescon.MemSchemaEngineStatistics;
+import org.apache.iotdb.db.schemaengine.rescon.MemSchemaRegionStatistics;
+
import org.apache.tsfile.utils.Binary;
import org.apache.tsfile.utils.Constants;
+import org.apache.tsfile.utils.RamUsageEstimator;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import java.io.File;
+import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class DeviceAttributeStoreTest {
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void testAttributeMemoryUsesValueSizesAcrossUpdates() {
+ final MemSchemaRegionStatistics statistics = newStatistics();
+ final DeviceAttributeStore store = new DeviceAttributeStore(statistics);
+ final Binary first = new Binary("value", StandardCharsets.UTF_8);
+ final Binary second = new Binary("a longer attribute value",
StandardCharsets.UTF_8);
+ final int pointer =
+ store.createAttribute(Collections.singletonList("a"), new Object[]
{first}, "t");
+ final long emptySlotMemory =
+ statistics.getRegionMemoryUsage() -
MemUsageUtil.computeKVMemUsageInMap("a", first);
+ assertEquals(first.ramBytesUsed(),
statistics.getTableAttributeMemory("t"));
+
+ store.alterAttribute(pointer, Collections.singletonList("a"), new Object[]
{second}, "t");
+ assertEquals(second.ramBytesUsed(),
statistics.getTableAttributeMemory("t"));
+ assertEquals(
+ emptySlotMemory + MemUsageUtil.computeKVMemUsageInMap("a", second),
+ statistics.getRegionMemoryUsage());
+
+ store.alterAttribute(pointer, Arrays.asList("a", "b"), new Object[] {null,
first}, "t");
+ assertEquals(first.ramBytesUsed(),
statistics.getTableAttributeMemory("t"));
+ assertEquals(
+ emptySlotMemory + MemUsageUtil.computeKVMemUsageInMap("b", first),
+ statistics.getRegionMemoryUsage());
+
+ store.alterAttribute(
+ pointer, Collections.singletonList("b"), new Object[]
{Constants.NONE}, "t");
+ assertEquals(first.ramBytesUsed(),
statistics.getTableAttributeMemory("t"));
+ store.alterAttribute(pointer, Collections.singletonList("b"), new Object[]
{null}, "t");
+ assertEquals(0, statistics.getTableAttributeMemory("t"));
+ assertEquals(emptySlotMemory, statistics.getRegionMemoryUsage());
+
+ store.alterAttribute(pointer, Collections.singletonList("c"), new Object[]
{second}, "t");
+ store.removeAttribute(pointer, "c", "t");
+ assertEquals(0, statistics.getTableAttributeMemory("t"));
+ assertEquals(emptySlotMemory, statistics.getRegionMemoryUsage());
+
+ store.alterAttribute(pointer, Collections.singletonList("c"), new Object[]
{first}, "t");
+ store.removeAttribute(pointer, "t");
+ assertEquals(0, statistics.getTableAttributeMemory("t"));
+ assertNull(store.getAttributes(pointer));
+ final long releasedMemory = statistics.getRegionMemoryUsage();
+ store.removeAttribute(pointer, "t");
+ assertEquals(releasedMemory, statistics.getRegionMemoryUsage());
+ }
+
+ @Test
+ public void testRepeatedCreateDeleteReusesSlotsAndBoundsSnapshot() throws
Exception {
+ final MemSchemaRegionStatistics statistics = newStatistics();
+ final DeviceAttributeStore store = new DeviceAttributeStore(statistics);
+ for (int i = 0; i < 10000; i++) {
+ final int pointer = store.createAttribute(Collections.emptyList(), new
Object[0], "t");
+ assertEquals(0, pointer);
+ store.removeAttribute(pointer, "t");
+ }
+ // One retained list reference and one bitmap word, independent of the
number of deletions.
+ assertEquals(
+ RamUsageEstimator.NUM_BYTES_OBJECT_REF + Long.BYTES,
statistics.getRegionMemoryUsage());
+ final File snapshotDir = temporaryFolder.newFolder();
+ assertTrue(store.createSnapshot(snapshotDir));
+ assertEquals(2L * Integer.BYTES, snapshotFile(snapshotDir).length());
+
+ final MemSchemaRegionStatistics recoveredStatistics = newStatistics();
+ final DeviceAttributeStore recovered = new
DeviceAttributeStore(recoveredStatistics);
+ recovered.loadFromSnapshot(snapshotDir);
+ assertEquals(statistics.getRegionMemoryUsage(),
recoveredStatistics.getRegionMemoryUsage());
+ assertEquals(0, recovered.createAttribute(Collections.emptyList(), new
Object[0], "other"));
+ assertEquals(0, recoveredStatistics.getTableAttributeMemory("other"));
+ }
+
+ @Test
+ public void testSnapshotRebuildsFreeSlotsAndPreservesLivePointers() throws
Exception {
+ final DeviceAttributeStore store = new DeviceAttributeStore(null);
+ final Binary value = new Binary("live", StandardCharsets.UTF_8);
+ for (int i = 0; i < 130; i++) {
+ assertEquals(
+ i, store.createAttribute(Collections.singletonList("a"), new
Object[] {value}, "t"));
+ }
+ store.removeAttribute(1, "t");
+ store.removeAttribute(64, "t");
+ store.removeAttribute(129, "t");
+ final File snapshotDir = temporaryFolder.newFolder();
+ assertTrue(store.createSnapshot(snapshotDir));
+ try (final InputStream input =
Files.newInputStream(snapshotFile(snapshotDir).toPath())) {
+ assertEquals(130, ReadWriteIOUtils.readInt(input));
+ }
+
+ final DeviceAttributeStore recovered = new DeviceAttributeStore(null);
+ recovered.loadFromSnapshot(snapshotDir);
+ for (int i = 0; i < 130; i++) {
+ if (i == 1 || i == 64 || i == 129) {
+ assertNull(recovered.getAttributes(i));
+ } else {
+ assertEquals(value, recovered.getAttributes(i, "a"));
+ }
+ }
+ assertEquals(1, recovered.createAttribute(Collections.emptyList(), new
Object[0], "t"));
+ assertEquals(64, recovered.createAttribute(Collections.emptyList(), new
Object[0], "t"));
+ recovered.removeAttribute(1, "t");
+ assertEquals(1, recovered.createAttribute(Collections.emptyList(), new
Object[0], "t"));
+ assertEquals(129, recovered.createAttribute(Collections.emptyList(), new
Object[0], "t"));
+ assertEquals(130, recovered.createAttribute(Collections.emptyList(), new
Object[0], "t"));
+ recovered.clear();
+ assertEquals(0, recovered.createAttribute(Collections.emptyList(), new
Object[0], "t"));
+ }
+
+ private static File snapshotFile(final File snapshotDir) {
+ return new File(snapshotDir, SchemaConstant.DEVICE_ATTRIBUTE_SNAPSHOT);
+ }
+
+ private static MemSchemaRegionStatistics newStatistics() {
+ return new MemSchemaRegionStatistics(0, new MemSchemaEngineStatistics());
+ }
+
@Test
public void testCreateAttributeIgnoresNamesWithoutValues() {
final DeviceAttributeStore store = new DeviceAttributeStore(null);
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImplTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImplTest.java
index 2b811f1e945..c498f507f9d 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImplTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImplTest.java
@@ -32,6 +32,8 @@ import
org.apache.iotdb.db.schemaengine.metric.SchemaRegionMemMetric;
import org.apache.iotdb.db.schemaengine.rescon.MemSchemaEngineStatistics;
import org.apache.iotdb.db.schemaengine.rescon.MemSchemaRegionStatistics;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.IMemMNode;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.container.MemMNodeContainer;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.impl.BasicInternalMNode;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.loader.MNodeFactoryLoader;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.traverser.collector.MeasurementCollector;
@@ -40,17 +42,144 @@ import
org.apache.tsfile.file.metadata.enums.CompressionType;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
import org.apache.tsfile.write.schema.MeasurementSchema;
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.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
public class MTreeBelowSGMemoryImplTest {
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void testWideDeviceDeletionDoesNotScanChildrenPerMeasurement() throws
Exception {
+ final MTreeBelowSGMemoryImpl mtree = newMTree();
+ final int measurementCount = 1024;
+ final List<String> measurements = new ArrayList<>();
+ for (int i = 0; i < measurementCount; i++) {
+ measurements.add("s" + i);
+ }
+ mtree.createAlignedTimeSeries(
+ new PartialPath("root.sg.d"),
+ measurements,
+ Collections.nCopies(measurementCount, TSDataType.INT32),
+ Collections.nCopies(measurementCount, TSEncoding.PLAIN),
+ Collections.nCopies(measurementCount, CompressionType.SNAPPY),
+ null,
+ false,
+ Collections.emptySet(),
+ new AtomicBoolean(true));
+ final IMemMNode device = mtree.getNodeByPath(new PartialPath("root.sg.d"));
+ final CountingChildren children = new CountingChildren();
+ children.putAll(device.getChildren());
+ device.setChildren(children);
+ final PartialPath pattern = new PartialPath("root.sg.d.*");
+ mtree.constructSchemaBlackList(pattern, new AtomicBoolean());
+ final List<PartialPath> paths = mtree.getPreDeletedTimeSeries(pattern);
+ Assert.assertEquals(measurementCount, paths.size());
+ children.scans = 0;
+ for (final PartialPath path : paths) {
+ mtree.deleteTimeSeries(path);
+ }
+ Assert.assertEquals(0, children.scans);
+ assertPathNotExist(mtree, new PartialPath("root.sg.d"));
+ Assert.assertEquals(
+ 0, mtree.getNodeByPath(new
PartialPath("root.sg")).getSubtreeMeasurementCount());
+ }
+
+ @Test
+ public void testDirectChildCountsAfterSnapshotWithNestedDevice() throws
Exception {
+ final MTreeBelowSGMemoryImpl mtree = newMTree();
+ for (final String path : Arrays.asList("root.sg.d.s1",
"root.sg.d.child.s2")) {
+ mtree.createTimeSeries(
+ new MeasurementPath(path),
+ TSDataType.INT32,
+ TSEncoding.PLAIN,
+ CompressionType.SNAPPY,
+ null,
+ null,
+ false,
+ null);
+ }
+ final File snapshotDir = temporaryFolder.newFolder();
+ Assert.assertTrue(mtree.createSnapshot(snapshotDir));
+ final MemSchemaRegionStatistics statistics = newRegionStatistics();
+ final MTreeBelowSGMemoryImpl recovered =
+ MTreeBelowSGMemoryImpl.loadFromSnapshot(
+ snapshotDir,
+ "root.sg",
+ statistics,
+ new SchemaRegionMemMetric(statistics, "root.sg"),
+ node -> {},
+ node -> {},
+ (node, table) -> {},
+ node -> Collections.emptyMap(),
+ node -> Collections.emptyMap());
+ final IMemMNode device = recovered.getNodeByPath(new
PartialPath("root.sg.d"));
+ Assert.assertEquals(
+ 1, ((BasicInternalMNode)
device.getBasicMNode()).getMeasurementChildCount());
+ Assert.assertEquals(2, device.getSubtreeMeasurementCount());
+ recovered.deleteTimeSeries(new PartialPath("root.sg.d.s1"));
+ Assert.assertFalse(recovered.getNodeByPath(new
PartialPath("root.sg.d")).isDevice());
+ Assert.assertTrue(recovered.getNodeByPath(new
PartialPath("root.sg.d.child")).isDevice());
+ recovered.deleteTimeSeries(new PartialPath("root.sg.d.child.s2"));
+ assertPathNotExist(recovered, new PartialPath("root.sg.d"));
+ }
+
+ @Test
+ public void testAlignmentResetsWhenOnlyLogicalViewsRemain() throws Exception
{
+ final MTreeBelowSGMemoryImpl mtree = newMTree();
+ mtree.createTimeSeries(
+ new MeasurementPath("root.sg.d.s"),
+ TSDataType.INT32,
+ TSEncoding.PLAIN,
+ CompressionType.SNAPPY,
+ null,
+ null,
+ false,
+ null);
+ final IMemMNode device = mtree.getNodeByPath(new PartialPath("root.sg.d"));
+ // The OSS factory does not create logical views. Model a view child to
verify cleanup
+ // semantics.
+ final IMemMNode view = mock(IMemMNode.class);
+ final IMeasurementMNode<IMemMNode> viewMeasurement =
mock(IMeasurementMNode.class);
+ when(view.isMeasurement()).thenReturn(true);
+ when(view.getAsMeasurementMNode()).thenReturn(viewMeasurement);
+ when(viewMeasurement.isLogicalView()).thenReturn(true);
+ device.addChild("view", view);
+ device.addChild("view", view);
+ Assert.assertEquals(
+ 2, ((BasicInternalMNode)
device.getBasicMNode()).getMeasurementChildCount());
+ mtree.deleteTimeSeries(new PartialPath("root.sg.d.s"));
+ Assert.assertTrue(device.isDevice());
+ Assert.assertNull(device.getAsDeviceMNode().isAlignedNullable());
+ device.deleteChild("view");
+ mtree.deleteEmptyInternalMNode(device.getAsDeviceMNode());
+ assertPathNotExist(mtree, new PartialPath("root.sg.d"));
+ }
+
+ private static class CountingChildren extends MemMNodeContainer {
+ private int scans;
+
+ @Override
+ public Collection<IMemMNode> values() {
+ scans++;
+ return super.values();
+ }
+ }
+
@Test
public void testPathAndTableDeviceChecksAfterClear() throws Exception {
final MTreeBelowSGMemoryImpl mtree = newMTree();