This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/dev/1.3 by this push:
new 13faa155d32 Added flags to mark whether a device has device
descendants to optimize query like select xx from xxx.** (#17672) (#17802)
13faa155d32 is described below
commit 13faa155d32fe4e27fab86edd8b673942dadf026
Author: Caideyipi <[email protected]>
AuthorDate: Tue Jun 2 18:00:42 2026 +0800
Added flags to mark whether a device has device descendants to optimize
query like select xx from xxx.** (#17672) (#17802)
* flag-
* Fix
* Address device descendant flag review comments
(cherry picked from commit a565a06825317e5f39393e619fb44e3fbbb88a9b)
---
.../mtree/impl/mem/MTreeBelowSGMemoryImpl.java | 130 +++++++++--
.../mtree/impl/mem/mnode/IMemMNode.java | 30 ++-
.../mtree/impl/mem/mnode/basic/BasicMNode.java | 34 ++-
.../mtree/impl/pbtree/MTreeBelowSGCachedImpl.java | 83 +++++--
.../mtree/impl/pbtree/mnode/ICachedMNode.java | 19 ++
.../impl/pbtree/mnode/basic/CachedBasicMNode.java | 40 +++-
.../schemaregion/mtree/traverser/Traverser.java | 102 +++++++++
.../traverser/basic/MeasurementTraverser.java | 5 +
.../schemaRegion/SchemaRegionBasicTest.java | 22 ++
.../mtree/impl/mem/MTreeBelowSGMemoryImplTest.java | 253 +++++++++++++++++++++
.../impl/pbtree/MTreeBelowSGCachedImplTest.java | 217 ++++++++++++++++++
.../node/common/AbstractAboveDatabaseMNode.java | 4 +
.../commons/schema/node/utils/IMNodeIterator.java | 2 +-
.../commons/schema/tree/AbstractTreeVisitor.java | 4 +
14 files changed, 908 insertions(+), 37 deletions(-)
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 b6f1221f701..a82250e2299 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
@@ -174,6 +174,59 @@ public class MTreeBelowSGMemoryImpl {
return store.createSnapshot(snapshotDir);
}
+ private void applySubtreeMeasurementDelta(IMemMNode startNode, final long
delta) {
+ if (delta == 0 || startNode == null) {
+ return;
+ }
+ IMemMNode current = startNode;
+ while (current != null) {
+ current.setSubtreeMeasurementCount(current.getSubtreeMeasurementCount()
+ delta);
+ current = current.getParent();
+ }
+ }
+
+ private IDeviceMNode<IMemMNode> setToEntityAndUpdateFlags(final IMemMNode
node) {
+ final boolean wasDevice = node.isDevice();
+ final IDeviceMNode<IMemMNode> deviceMNode = store.setToEntity(node);
+ if (!wasDevice) {
+ markAncestorsHavingDeviceDescendant(node);
+ }
+ return deviceMNode;
+ }
+
+ private void markAncestorsHavingDeviceDescendant(final IMemMNode deviceNode)
{
+ IMemMNode current = deviceNode.getParent();
+ while (current != null && !current.hasDeviceDescendant()) {
+ current.setHasDeviceDescendant(true);
+ current = current.getParent();
+ }
+ }
+
+ private boolean hasDeviceDescendantInChildren(final IMemMNode node) {
+ try (final IMNodeIterator<IMemMNode> iterator =
store.getChildrenIterator(node)) {
+ while (iterator.hasNext()) {
+ final IMemMNode child = iterator.next();
+ if (child.isDevice() || child.hasDeviceDescendant()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private void refreshAncestorsHavingDeviceDescendant(IMemMNode startNode) {
+ IMemMNode current = startNode;
+ while (current != null) {
+ current.setHasDeviceDescendant(hasDeviceDescendantInChildren(current));
+ current = current.getParent();
+ }
+ }
+
+ private long getTemplateMeasurementCount(final int templateId) {
+ final Template template =
ClusterTemplateManager.getInstance().getTemplate(templateId);
+ return template == null ? 0L : template.getMeasurementNumber();
+ }
+
public static MTreeBelowSGMemoryImpl loadFromSnapshot(
File snapshotDir,
String storageGroupFullPath,
@@ -184,13 +237,16 @@ public class MTreeBelowSGMemoryImpl {
Function<IMeasurementMNode<IMemMNode>, Map<String, String>> tagGetter,
Function<IMeasurementMNode<IMemMNode>, Map<String, String>>
attributeGetter)
throws IOException, IllegalPathException {
- return new MTreeBelowSGMemoryImpl(
- new PartialPath(storageGroupFullPath),
- MemMTreeStore.loadFromSnapshot(
- snapshotDir, measurementProcess, deviceProcess, regionStatistics,
metric),
- tagGetter,
- attributeGetter,
- regionStatistics);
+ final MTreeBelowSGMemoryImpl mtree =
+ new MTreeBelowSGMemoryImpl(
+ new PartialPath(storageGroupFullPath),
+ MemMTreeStore.loadFromSnapshot(
+ snapshotDir, measurementProcess, deviceProcess,
regionStatistics, metric),
+ tagGetter,
+ attributeGetter,
+ regionStatistics);
+ mtree.rebuildSubtreeMeasurementCount();
+ return mtree;
}
// endregion
@@ -268,7 +324,7 @@ public class MTreeBelowSGMemoryImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
}
// create a non-aligned time series
@@ -290,6 +346,7 @@ public class MTreeBelowSGMemoryImpl {
entityMNode.addAlias(alias, measurementMNode);
}
+ applySubtreeMeasurementDelta(measurementMNode.getAsMNode(), 1L);
return measurementMNode;
}
}
@@ -360,7 +417,7 @@ public class MTreeBelowSGMemoryImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
entityMNode.setAligned(true);
}
@@ -386,6 +443,7 @@ public class MTreeBelowSGMemoryImpl {
if (aliasList != null && aliasList.get(i) != null) {
entityMNode.addAlias(aliasList.get(i), measurementMNode);
}
+ applySubtreeMeasurementDelta(measurementMNode.getAsMNode(), 1L);
measurementMNodeList.add(measurementMNode);
}
return measurementMNodeList;
@@ -539,6 +597,7 @@ public class MTreeBelowSGMemoryImpl {
if (deletedNode.getAlias() != null) {
parent.getAsDeviceMNode().deleteAliasChild(deletedNode.getAlias());
}
+ applySubtreeMeasurementDelta(parent, -1L);
}
deleteEmptyInternalMNode(parent.getAsDeviceMNode());
return deletedNode;
@@ -551,14 +610,15 @@ public class MTreeBelowSGMemoryImpl {
boolean hasMeasurement = false;
boolean hasNonViewMeasurement = false;
IMemMNode child;
- IMNodeIterator<IMemMNode> iterator = store.getChildrenIterator(curNode);
- while (iterator.hasNext()) {
- child = iterator.next();
- if (child.isMeasurement()) {
- hasMeasurement = true;
- if (!child.getAsMeasurementMNode().isLogicalView()) {
- hasNonViewMeasurement = true;
- break;
+ 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;
+ }
}
}
}
@@ -567,6 +627,7 @@ public class MTreeBelowSGMemoryImpl {
synchronized (this) {
curNode = store.setToInternal(entityMNode);
}
+ refreshAncestorsHavingDeviceDescendant(curNode.getParent());
} else if (!hasNonViewMeasurement) {
// has some measurement but they are all logical view
entityMNode.setAligned(null);
@@ -889,7 +950,7 @@ public class MTreeBelowSGMemoryImpl {
if (cur.isDevice()) {
entityMNode = cur.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(cur);
+ entityMNode = setToEntityAndUpdateFlags(cur);
}
}
@@ -907,6 +968,7 @@ public class MTreeBelowSGMemoryImpl {
entityMNode.setUseTemplate(true);
entityMNode.setSchemaTemplateId(template.getId());
regionStatistics.activateTemplate(template.getId());
+ applySubtreeMeasurementDelta(entityMNode.getAsMNode(), (long)
template.getMeasurementNumber());
}
public Map<PartialPath, List<Integer>> constructSchemaBlackListWithTemplate(
@@ -968,6 +1030,8 @@ public class MTreeBelowSGMemoryImpl {
resultTemplateSetInfo.put(
node.getPartialPath(),
Collections.singletonList(node.getSchemaTemplateId()));
regionStatistics.deactivateTemplate(node.getSchemaTemplateId());
+ applySubtreeMeasurementDelta(
+ node.getAsMNode(),
-getTemplateMeasurementCount(node.getSchemaTemplateId()));
node.deactivateTemplate();
deleteEmptyInternalMNode(node);
}
@@ -991,7 +1055,7 @@ public class MTreeBelowSGMemoryImpl {
if (cur.isDevice()) {
entityMNode = cur.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(cur);
+ entityMNode = setToEntityAndUpdateFlags(cur);
}
if (!entityMNode.isAligned()) {
@@ -1000,6 +1064,7 @@ public class MTreeBelowSGMemoryImpl {
entityMNode.setUseTemplate(true);
entityMNode.setSchemaTemplateId(templateId);
regionStatistics.activateTemplate(templateId);
+ applySubtreeMeasurementDelta(entityMNode.getAsMNode(),
getTemplateMeasurementCount(templateId));
}
public long countPathsUsingTemplate(PartialPath pathPattern, int templateId)
@@ -1011,6 +1076,30 @@ public class MTreeBelowSGMemoryImpl {
}
}
+ public void rebuildSubtreeMeasurementCount() {
+ rebuildSubtreeMeasurementCountFromNode(rootNode);
+ }
+
+ private long rebuildSubtreeMeasurementCountFromNode(final IMemMNode node) {
+ long count = node.isMeasurement() ? 1L : 0L;
+ boolean hasDeviceDescendant = false;
+ try (final IMNodeIterator<IMemMNode> iterator =
store.getChildrenIterator(node)) {
+ while (iterator.hasNext()) {
+ final IMemMNode child = iterator.next();
+ count += rebuildSubtreeMeasurementCountFromNode(child);
+ if (child.isDevice() || child.hasDeviceDescendant()) {
+ hasDeviceDescendant = true;
+ }
+ }
+ }
+ if (node.isDevice() && node.getAsDeviceMNode().isUseTemplate()) {
+ count +=
getTemplateMeasurementCount(node.getAsDeviceMNode().getSchemaTemplateId());
+ }
+ node.setSubtreeMeasurementCount(count);
+ node.setHasDeviceDescendant(hasDeviceDescendant);
+ return count;
+ }
+
// endregion
// region Interfaces for schema reader
@@ -1278,7 +1367,7 @@ public class MTreeBelowSGMemoryImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
// this parent has no measurement before. The leafName is his first
child who is a logical
// view.
entityMNode.setAligned(null);
@@ -1287,6 +1376,7 @@ public class MTreeBelowSGMemoryImpl {
measurementMNode.setParent(entityMNode.getAsMNode());
store.addChild(entityMNode.getAsMNode(), leafName,
measurementMNode.getAsMNode());
+ applySubtreeMeasurementDelta(measurementMNode.getAsMNode(), 1L);
return measurementMNode;
}
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/IMemMNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/IMemMNode.java
index d3d055928b1..257d7caeef8 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/IMemMNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/IMemMNode.java
@@ -19,5 +19,33 @@
package org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode;
import org.apache.iotdb.commons.schema.node.IMNode;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.basic.BasicMNode;
-public interface IMemMNode extends IMNode<IMemMNode> {}
+public interface IMemMNode extends IMNode<IMemMNode> {
+
+ BasicMNode getBasicMNode();
+
+ /**
+ * The count of measurement nodes contained in the subtree rooted at this
node. The counter is
+ * maintained in memory only.
+ */
+ default long getSubtreeMeasurementCount() {
+ return getBasicMNode().getSubtreeMeasurementCount();
+ }
+
+ default void setSubtreeMeasurementCount(final long subtreeMeasurementCount) {
+ getBasicMNode().setSubtreeMeasurementCount(subtreeMeasurementCount);
+ }
+
+ /**
+ * Whether there is any device node in the subtree rooted at this node,
excluding the node itself.
+ * This flag is maintained in memory only.
+ */
+ default boolean hasDeviceDescendant() {
+ return getBasicMNode().hasDeviceDescendant();
+ }
+
+ default void setHasDeviceDescendant(final boolean hasDeviceDescendant) {
+ getBasicMNode().setHasDeviceDescendant(hasDeviceDescendant);
+ }
+}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/basic/BasicMNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/basic/BasicMNode.java
index ccacef6c7eb..e70885c7d03 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/basic/BasicMNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/basic/BasicMNode.java
@@ -46,6 +46,12 @@ public class BasicMNode implements IMemMNode {
private IMemMNode parent;
private final BasicMNodeInfo basicMNodeInfo;
+ /** Cached count of measurements in this node's subtree, rebuilt on restart.
*/
+ private long subtreeMeasurementCount = 0L;
+
+ /** Cached flag showing whether there is any device in the subtree below
this node. */
+ private boolean hasDeviceDescendant = false;
+
/** from root to this node, only be set when used once for InternalMNode */
private String fullPath;
@@ -55,6 +61,11 @@ public class BasicMNode implements IMemMNode {
this.basicMNodeInfo = new BasicMNodeInfo(name);
}
+ @Override
+ public BasicMNode getBasicMNode() {
+ return this;
+ }
+
@Override
public String getName() {
return basicMNodeInfo.getName();
@@ -98,6 +109,25 @@ public class BasicMNode implements IMemMNode {
this.fullPath = fullPath;
}
+ public long getSubtreeMeasurementCount() {
+ return subtreeMeasurementCount;
+ }
+
+ @Override
+ public void setSubtreeMeasurementCount(final long subtreeMeasurementCount) {
+ this.subtreeMeasurementCount = subtreeMeasurementCount;
+ }
+
+ @Override
+ public boolean hasDeviceDescendant() {
+ return hasDeviceDescendant;
+ }
+
+ @Override
+ public void setHasDeviceDescendant(final boolean hasDeviceDescendant) {
+ this.hasDeviceDescendant = hasDeviceDescendant;
+ }
+
@Override
public PartialPath getPartialPath() {
List<String> detachedPath = new ArrayList<>();
@@ -224,6 +254,8 @@ public class BasicMNode implements IMemMNode {
* <li>basicMNodeInfo reference, 8B
* <li>parent reference, 8B
* <li>fullPath reference, 8B
+ * <li>subtreeMeasurementCount, 8B
+ * <li>hasDeviceDescendant, 1B
* </ol>
* <li>MapEntry in parent
* <ol>
@@ -235,7 +267,7 @@ public class BasicMNode implements IMemMNode {
*/
@Override
public int estimateSize() {
- return 8 + 8 + 8 + 8 + 8 + 8 + 28 + basicMNodeInfo.estimateSize();
+ return 8 + 8 + 8 + 8 + 8 + 1 + 8 + 8 + 28 + basicMNodeInfo.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 f79a8c2e057..829d242187e 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
@@ -132,6 +132,68 @@ public class MTreeBelowSGCachedImpl {
private final int levelOfDB;
private final CachedSchemaRegionStatistics regionStatistics;
+ private IDeviceMNode<ICachedMNode> setToEntityAndUpdateFlags(final
ICachedMNode node)
+ throws MetadataException {
+ final boolean wasDevice = node.isDevice();
+ if (!node.isDeviceDescendantComputed()) {
+ node.setHasDeviceDescendant(hasDeviceDescendantInChildren(node));
+ node.setDeviceDescendantComputed(true);
+ }
+ final IDeviceMNode<ICachedMNode> deviceMNode = store.setToEntity(node);
+ if (!wasDevice) {
+ markAncestorsHavingDeviceDescendant(node);
+ }
+ return deviceMNode;
+ }
+
+ private void markAncestorsHavingDeviceDescendant(final ICachedMNode
deviceNode) {
+ ICachedMNode current = deviceNode.getParent();
+ while (current != null && !current.hasDeviceDescendant()) {
+ current.setHasDeviceDescendant(true);
+ current.setDeviceDescendantComputed(true);
+ current = current.getParent();
+ }
+ }
+
+ private boolean hasDeviceDescendantInChildren(final ICachedMNode node)
throws MetadataException {
+ try (final IMNodeIterator<ICachedMNode> iterator =
store.getChildrenIterator(node)) {
+ while (iterator.hasNext()) {
+ final ICachedMNode child = iterator.next();
+ try {
+ if (child.isDevice() || hasDeviceDescendant(child)) {
+ return true;
+ }
+ } finally {
+ unPinMNode(child);
+ }
+ }
+ return false;
+ }
+ }
+
+ private boolean hasDeviceDescendant(final ICachedMNode node) throws
MetadataException {
+ if (node.isMeasurement()) {
+ node.setHasDeviceDescendant(false);
+ node.setDeviceDescendantComputed(true);
+ return false;
+ }
+ if (!node.isDeviceDescendantComputed()) {
+ node.setHasDeviceDescendant(hasDeviceDescendantInChildren(node));
+ node.setDeviceDescendantComputed(true);
+ }
+ return node.hasDeviceDescendant();
+ }
+
+ private void refreshAncestorsHavingDeviceDescendant(ICachedMNode startNode)
+ throws MetadataException {
+ ICachedMNode current = startNode;
+ while (current != null) {
+ current.setHasDeviceDescendant(hasDeviceDescendantInChildren(current));
+ current.setDeviceDescendantComputed(true);
+ current = current.getParent();
+ }
+ }
+
// region MTree initialization, clear and serialization
public MTreeBelowSGCachedImpl(
PartialPath storageGroupPath,
@@ -348,7 +410,7 @@ public class MTreeBelowSGCachedImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
device = entityMNode.getAsMNode();
}
@@ -453,7 +515,7 @@ public class MTreeBelowSGCachedImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
entityMNode.setAligned(true);
device = entityMNode.getAsMNode();
}
@@ -674,8 +736,7 @@ public class MTreeBelowSGCachedImpl {
boolean hasMeasurement = false;
boolean hasNonViewMeasurement = false;
ICachedMNode child;
- IMNodeIterator<ICachedMNode> iterator =
store.getChildrenIterator(curNode);
- try {
+ try (final IMNodeIterator<ICachedMNode> iterator =
store.getChildrenIterator(curNode)) {
while (iterator.hasNext()) {
child = iterator.next();
unPinMNode(child);
@@ -687,12 +748,11 @@ public class MTreeBelowSGCachedImpl {
}
}
}
- } finally {
- iterator.close();
}
if (!hasMeasurement) {
curNode = store.setToInternal(entityMNode);
+ refreshAncestorsHavingDeviceDescendant(curNode.getParent());
} else if (!hasNonViewMeasurement) {
// has some measurement but they are all logical view
store.updateMNode(entityMNode.getAsMNode(), o ->
o.getAsDeviceMNode().setAligned(null));
@@ -719,14 +779,11 @@ public class MTreeBelowSGCachedImpl {
}
private boolean isEmptyInternalMNode(ICachedMNode node) throws
MetadataException {
- IMNodeIterator<ICachedMNode> iterator = store.getChildrenIterator(node);
- try {
+ try (final IMNodeIterator<ICachedMNode> iterator =
store.getChildrenIterator(node)) {
return !IoTDBConstant.PATH_ROOT.equals(node.getName())
&& !node.isMeasurement()
&& !(node.isDevice() && node.getAsDeviceMNode().isUseTemplate())
&& !iterator.hasNext();
- } finally {
- iterator.close();
}
}
@@ -1064,7 +1121,7 @@ public class MTreeBelowSGCachedImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
// this parent has no measurement before. The leafName is his
first child who is a
// logical
// view.
@@ -1186,7 +1243,7 @@ public class MTreeBelowSGCachedImpl {
if (cur.isDevice()) {
entityMNode = cur.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(cur);
+ entityMNode = setToEntityAndUpdateFlags(cur);
}
if (entityMNode.isUseTemplate()) {
@@ -1232,7 +1289,7 @@ public class MTreeBelowSGCachedImpl {
if (cur.isDevice()) {
entityMNode = cur.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(cur);
+ entityMNode = setToEntityAndUpdateFlags(cur);
}
store.updateMNode(
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/mnode/ICachedMNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/mnode/ICachedMNode.java
index 489b1a642aa..244e077013c 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/mnode/ICachedMNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/mnode/ICachedMNode.java
@@ -22,8 +22,27 @@ package
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.mnode;
import org.apache.iotdb.commons.schema.node.IMNode;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.lock.LockEntry;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.memory.cache.CacheEntry;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.mnode.basic.CachedBasicMNode;
public interface ICachedMNode extends IMNode<ICachedMNode> {
+ CachedBasicMNode getBasicMNode();
+
+ default boolean hasDeviceDescendant() {
+ return getBasicMNode().hasDeviceDescendant();
+ }
+
+ default void setHasDeviceDescendant(final boolean hasDeviceDescendant) {
+ getBasicMNode().setHasDeviceDescendant(hasDeviceDescendant);
+ }
+
+ default boolean isDeviceDescendantComputed() {
+ return getBasicMNode().isDeviceDescendantComputed();
+ }
+
+ default void setDeviceDescendantComputed(final boolean
deviceDescendantComputed) {
+ getBasicMNode().setDeviceDescendantComputed(deviceDescendantComputed);
+ }
+
CacheEntry getCacheEntry();
void setCacheEntry(CacheEntry cacheEntry);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/mnode/basic/CachedBasicMNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/mnode/basic/CachedBasicMNode.java
index d34fde474ab..14bb9f3609b 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/mnode/basic/CachedBasicMNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/mnode/basic/CachedBasicMNode.java
@@ -47,6 +47,17 @@ public class CachedBasicMNode implements ICachedMNode {
private ICachedMNode parent;
private final CacheMNodeInfo cacheMNodeInfo;
+ /** Cached flag showing whether there is any device in the subtree below
this node. */
+ private boolean hasDeviceDescendant = false;
+
+ /**
+ * Whether {@link #hasDeviceDescendant} is trusted for the current in-memory
node instance.
+ *
+ * <p>This state is intentionally not persisted. A node reloaded from PBTree
can lazily recompute
+ * it when the wildcard-suffix optimization needs it.
+ */
+ private boolean deviceDescendantComputed = false;
+
/** from root to this node, only be set when used once for InternalMNode */
private String fullPath;
@@ -56,6 +67,11 @@ public class CachedBasicMNode implements ICachedMNode {
this.cacheMNodeInfo = new CacheMNodeInfo(name);
}
+ @Override
+ public CachedBasicMNode getBasicMNode() {
+ return this;
+ }
+
@Override
public String getName() {
return cacheMNodeInfo.getName();
@@ -99,6 +115,26 @@ public class CachedBasicMNode implements ICachedMNode {
this.fullPath = fullPath;
}
+ @Override
+ public boolean hasDeviceDescendant() {
+ return hasDeviceDescendant;
+ }
+
+ @Override
+ public void setHasDeviceDescendant(final boolean hasDeviceDescendant) {
+ this.hasDeviceDescendant = hasDeviceDescendant;
+ }
+
+ @Override
+ public boolean isDeviceDescendantComputed() {
+ return deviceDescendantComputed;
+ }
+
+ @Override
+ public void setDeviceDescendantComputed(final boolean
deviceDescendantComputed) {
+ this.deviceDescendantComputed = deviceDescendantComputed;
+ }
+
@Override
public PartialPath getPartialPath() {
List<String> detachedPath = new ArrayList<>();
@@ -245,6 +281,8 @@ public class CachedBasicMNode implements ICachedMNode {
* <li>basicMNodeInfo reference, 8B
* <li>parent reference, 8B
* <li>fullPath reference, 8B
+ * <li>hasDeviceDescendant, 1B
+ * <li>deviceDescendantComputed, 1B
* </ol>
* <li>MapEntry in parent
* <ol>
@@ -256,7 +294,7 @@ public class CachedBasicMNode implements ICachedMNode {
*/
@Override
public int estimateSize() {
- return 8 + 8 + 8 + 8 + 8 + 8 + 28 + cacheMNodeInfo.estimateSize();
+ return 8 + 8 + 8 + 8 + 1 + 1 + 8 + 8 + 28 + cacheMNodeInfo.estimateSize();
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/traverser/Traverser.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/traverser/Traverser.java
index f1ac33d99ed..6c9e23e9597 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/traverser/Traverser.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/traverser/Traverser.java
@@ -25,15 +25,18 @@ import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.path.PathPatternTree;
import org.apache.iotdb.commons.path.fa.IFAState;
import org.apache.iotdb.commons.path.fa.IFATransition;
+import org.apache.iotdb.commons.path.fa.match.IStateMatchInfo;
import org.apache.iotdb.commons.schema.node.IMNode;
import org.apache.iotdb.commons.schema.node.role.IDeviceMNode;
import org.apache.iotdb.commons.schema.node.utils.IMNodeFactory;
import org.apache.iotdb.commons.schema.node.utils.IMNodeIterator;
import org.apache.iotdb.commons.schema.tree.AbstractTreeVisitor;
import org.apache.iotdb.db.schemaengine.schemaregion.mtree.IMTreeStore;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.IMemMNode;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.iterator.MNodeIterator;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.ReentrantReadOnlyCachedMTreeStore;
import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.memory.ReleaseFlushMonitor;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.mnode.ICachedMNode;
import org.apache.iotdb.db.schemaengine.schemaregion.utils.MNodeUtils;
import org.apache.iotdb.db.schemaengine.template.Template;
@@ -41,9 +44,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.Set;
import static org.apache.iotdb.commons.conf.IoTDBConstant.PATH_ROOT;
import static org.apache.iotdb.commons.schema.SchemaConstant.NON_TEMPLATE;
@@ -248,10 +253,107 @@ public abstract class Traverser<R, N extends IMNode<N>>
extends AbstractTreeVisi
if (parent.isAboveDatabase()) {
return new MNodeIterator<>(parent.getChildren().values().iterator());
} else {
+ final Iterator<N> optimizedIterator =
+ shouldUseLeafDeviceMeasurementOptimization()
+ ? getOptimizedChildrenIterator(parent)
+ : null;
+ if (optimizedIterator != null) {
+ return optimizedIterator;
+ }
return store.getTraverserIterator(parent, templateMap,
skipPreDeletedSchema);
}
}
+ protected boolean shouldUseLeafDeviceMeasurementOptimization() {
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Iterator<N> getOptimizedChildrenIterator(final N parent) throws
MetadataException {
+ if (!parent.isDevice()) {
+ return null;
+ }
+
+ if (parent instanceof IMemMNode) {
+ if (((IMemMNode) parent).hasDeviceDescendant()) {
+ return null;
+ }
+ } else if (parent instanceof ICachedMNode) {
+ if (hasDeviceDescendant((ICachedMNode) parent)) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+
+ final IStateMatchInfo stateMatchInfo = getCurrentStateMatchInfo();
+ final Set<String> candidateNames = new LinkedHashSet<>();
+
+ for (int i = 0; i < stateMatchInfo.getMatchedStateSize(); i++) {
+ final IFAState matchedState = stateMatchInfo.getMatchedState(i);
+
+ for (final IFATransition transition :
+ patternFA.getPreciseMatchTransition(matchedState).values()) {
+ if (patternFA.getNextState(matchedState, transition).isFinal()) {
+ candidateNames.add(transition.getAcceptEvent());
+ }
+ }
+
+ final Iterator<IFATransition> fuzzyTransitionIterator =
+ patternFA.getFuzzyMatchTransitionIterator(matchedState);
+ while (fuzzyTransitionIterator.hasNext()) {
+ if (patternFA.getNextState(matchedState,
fuzzyTransitionIterator.next()).isFinal()) {
+ return null;
+ }
+ }
+ }
+
+ if (candidateNames.isEmpty()) {
+ return null;
+ }
+
+ // For leaf devices, `**.measurement` does not need to enumerate every
measurement child.
+ try {
+ return getChildrenIterator(parent, candidateNames.iterator());
+ } catch (final MetadataException e) {
+ throw e;
+ } catch (final Exception e) {
+ throw new MetadataException(e.getMessage(), e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean hasDeviceDescendant(final ICachedMNode node) throws
MetadataException {
+ if (node.isMeasurement()) {
+ node.setHasDeviceDescendant(false);
+ node.setDeviceDescendantComputed(true);
+ return false;
+ }
+ if (!node.isDeviceDescendantComputed()) {
+ node.setHasDeviceDescendant(hasDeviceDescendantInChildren(node));
+ node.setDeviceDescendantComputed(true);
+ }
+ return node.hasDeviceDescendant();
+ }
+
+ @SuppressWarnings("unchecked")
+ private boolean hasDeviceDescendantInChildren(final ICachedMNode node)
throws MetadataException {
+ final IMTreeStore<ICachedMNode> cachedStore = (IMTreeStore<ICachedMNode>)
store;
+ try (final IMNodeIterator<ICachedMNode> iterator =
cachedStore.getChildrenIterator(node)) {
+ while (iterator.hasNext()) {
+ final ICachedMNode child = iterator.next();
+ try {
+ if (child.isDevice() || hasDeviceDescendant(child)) {
+ return true;
+ }
+ } finally {
+ cachedStore.unPin(child);
+ }
+ }
+ return false;
+ }
+ }
+
@Override
protected void releaseNodeIterator(Iterator<N> nodeIterator) {
if (nodeIterator instanceof IMNodeIterator) {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/traverser/basic/MeasurementTraverser.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/traverser/basic/MeasurementTraverser.java
index 4996e3f0c8b..490145d5371 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/traverser/basic/MeasurementTraverser.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/traverser/basic/MeasurementTraverser.java
@@ -84,4 +84,9 @@ public abstract class MeasurementTraverser<R, N extends
IMNode<N>> extends Trave
protected boolean shouldVisitSubtreeOfInternalMatchedNode(N node) {
return !node.isMeasurement();
}
+
+ @Override
+ protected boolean shouldUseLeafDeviceMeasurementOptimization() {
+ return true;
+ }
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionBasicTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionBasicTest.java
index 667ec4bc9b4..913b5b151e8 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionBasicTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionBasicTest.java
@@ -1185,6 +1185,28 @@ public class SchemaRegionBasicTest extends
AbstractSchemaRegionTest {
Assert.assertEquals(expectedPathList, actualPathList);
}
+ @Test
+ public void testShowTimeseriesWildcardSuffixWithNestedAndLeafDevices()
throws Exception {
+ final ISchemaRegion schemaRegion = getSchemaRegion("root.test", 0);
+
+ SchemaRegionTestUtil.createSimpleTimeSeriesByList(
+ schemaRegion,
+ Arrays.asList(
+ "root.test.d1.s1", "root.test.d1.a.d2.s1", "root.test.d3.s1",
"root.test.d4.s2"));
+
+ final List<ITimeSeriesSchemaInfo> result =
+ SchemaRegionTestUtil.showTimeseries(schemaRegion, new
PartialPath("root.test.**.s1"));
+ final Set<String> expectedPathList =
+ new HashSet<>(Arrays.asList("root.test.d1.s1", "root.test.d1.a.d2.s1",
"root.test.d3.s1"));
+ Assert.assertEquals(expectedPathList.size(), result.size());
+
+ final Set<String> actualPathList = new HashSet<>();
+ for (final ITimeSeriesSchemaInfo timeSeriesSchemaInfo : result) {
+ actualPathList.add(timeSeriesSchemaInfo.getFullPath());
+ }
+ Assert.assertEquals(expectedPathList, actualPathList);
+ }
+
@Test
public void testGetMatchedDevicesWithSpecialPattern2() throws Exception {
final ISchemaRegion schemaRegion = getSchemaRegion("root.test", 0);
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
new file mode 100644
index 00000000000..1dfa4a056bb
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImplTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.schemaengine.schemaregion.mtree.impl.mem;
+
+import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.path.MeasurementPath;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.schema.SchemaConstant;
+import org.apache.iotdb.commons.schema.node.role.IMeasurementMNode;
+import org.apache.iotdb.commons.schema.node.utils.IMNodeFactory;
+import org.apache.iotdb.commons.schema.node.utils.IMNodeIterator;
+import org.apache.iotdb.db.exception.metadata.PathNotExistException;
+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.loader.MNodeFactoryLoader;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.traverser.collector.MeasurementCollector;
+import org.apache.iotdb.db.schemaengine.template.Template;
+
+import org.apache.tsfile.enums.TSDataType;
+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.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class MTreeBelowSGMemoryImplTest {
+
+ @Test
+ public void
testDeviceDescendantFlagIsMaintainedAcrossCreateDeleteAndRebuild() throws
Exception {
+ final MTreeBelowSGMemoryImpl mtree = newMTree();
+
+ mtree.createTimeSeries(
+ new MeasurementPath("root.sg.a.b.s1"),
+ TSDataType.BOOLEAN,
+ TSEncoding.PLAIN,
+ CompressionType.SNAPPY,
+ null,
+ null,
+ false,
+ null);
+
+ IMemMNode database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ IMemMNode aNode = mtree.getNodeByPath(new PartialPath("root.sg.a"));
+ IMemMNode bNode = mtree.getNodeByPath(new PartialPath("root.sg.a.b"));
+
+ Assert.assertTrue(database.hasDeviceDescendant());
+ Assert.assertFalse(aNode.isDevice());
+ Assert.assertTrue(aNode.hasDeviceDescendant());
+ Assert.assertTrue(bNode.isDevice());
+ Assert.assertFalse(bNode.hasDeviceDescendant());
+
+ mtree.createTimeSeries(
+ new MeasurementPath("root.sg.a.s0"),
+ TSDataType.BOOLEAN,
+ TSEncoding.PLAIN,
+ CompressionType.SNAPPY,
+ null,
+ null,
+ false,
+ null);
+ mtree.createTimeSeries(
+ new MeasurementPath("root.sg.c.d.s2"),
+ TSDataType.BOOLEAN,
+ TSEncoding.PLAIN,
+ CompressionType.SNAPPY,
+ null,
+ null,
+ false,
+ null);
+
+ aNode = mtree.getNodeByPath(new PartialPath("root.sg.a"));
+ IMemMNode cNode = mtree.getNodeByPath(new PartialPath("root.sg.c"));
+ Assert.assertTrue(aNode.isDevice());
+ Assert.assertTrue(aNode.hasDeviceDescendant());
+ Assert.assertTrue(cNode.hasDeviceDescendant());
+
+ database.setHasDeviceDescendant(false);
+ aNode.setHasDeviceDescendant(false);
+ bNode.setHasDeviceDescendant(true);
+ cNode.setHasDeviceDescendant(false);
+ mtree.rebuildSubtreeMeasurementCount();
+
+ database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ aNode = mtree.getNodeByPath(new PartialPath("root.sg.a"));
+ bNode = mtree.getNodeByPath(new PartialPath("root.sg.a.b"));
+ cNode = mtree.getNodeByPath(new PartialPath("root.sg.c"));
+ Assert.assertTrue(database.hasDeviceDescendant());
+ Assert.assertTrue(aNode.hasDeviceDescendant());
+ Assert.assertFalse(bNode.hasDeviceDescendant());
+ Assert.assertTrue(cNode.hasDeviceDescendant());
+
+ mtree.deleteTimeseries(new PartialPath("root.sg.a.s0"));
+ aNode = mtree.getNodeByPath(new PartialPath("root.sg.a"));
+ Assert.assertFalse(aNode.isDevice());
+ Assert.assertTrue(aNode.hasDeviceDescendant());
+
+ mtree.deleteTimeseries(new PartialPath("root.sg.a.b.s1"));
+ database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ Assert.assertTrue(database.hasDeviceDescendant());
+ assertPathNotExist(mtree, new PartialPath("root.sg.a"));
+
+ mtree.deleteTimeseries(new PartialPath("root.sg.c.d.s2"));
+ database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ Assert.assertFalse(database.hasDeviceDescendant());
+ assertPathNotExist(mtree, new PartialPath("root.sg.c"));
+ }
+
+ @Test
+ public void testLeafDeviceWildcardSuffixUsesDirectMeasurementLookup() throws
Exception {
+ final PartialPath databasePath = new PartialPath("root.sg");
+ final MemSchemaRegionStatistics regionStatistics = newRegionStatistics();
+ final CountingMemMTreeStore store =
+ new CountingMemMTreeStore(
+ databasePath, regionStatistics, new
SchemaRegionMemMetric(regionStatistics, "root.sg"));
+ final IMNodeFactory<IMemMNode> nodeFactory =
+ MNodeFactoryLoader.getInstance().getMemMNodeIMNodeFactory();
+
+ final IMemMNode databaseMNode = store.getRoot();
+ final IMemMNode rootNode = store.generatePrefix(databasePath);
+ final IMemMNode deviceNode =
+ store.addChild(databaseMNode, "d1",
nodeFactory.createInternalMNode(databaseMNode, "d1"));
+ store.setToEntity(deviceNode);
+
+ for (int i = 0; i < 64; i++) {
+ final String measurement = i == 0 ? "target" : "s" + i;
+ final IMeasurementMNode<IMemMNode> measurementMNode =
+ nodeFactory.createMeasurementMNode(
+ deviceNode.getAsDeviceMNode(),
+ measurement,
+ new MeasurementSchema(
+ measurement, TSDataType.BOOLEAN, TSEncoding.PLAIN,
CompressionType.SNAPPY),
+ null);
+ store.addChild(deviceNode, measurement, measurementMNode.getAsMNode());
+ }
+
+ store.watchParent(deviceNode);
+ final List<String> matchedPaths = new ArrayList<>();
+ try (final MeasurementCollector<Void, IMemMNode> collector =
+ new MeasurementCollector<Void, IMemMNode>(
+ rootNode,
+ new PartialPath("root.sg.**.target"),
+ store,
+ false,
+ SchemaConstant.ALL_MATCH_SCOPE) {
+ @Override
+ protected Void collectMeasurement(final IMeasurementMNode<IMemMNode>
node) {
+
matchedPaths.add(getCurrentMeasurementPathInTraverse(node).getFullPath());
+ return null;
+ }
+ }) {
+ collector.traverse();
+ }
+
+ Assert.assertEquals(Collections.singletonList("root.sg.d1.target"),
matchedPaths);
+ Assert.assertEquals(0, store.getWatchedTraverserIteratorCount());
+ Assert.assertEquals(1, store.getWatchedChildLookupCount());
+ }
+
+ private static MTreeBelowSGMemoryImpl newMTree() throws Exception {
+ final MemSchemaRegionStatistics regionStatistics = newRegionStatistics();
+ return new MTreeBelowSGMemoryImpl(
+ new PartialPath("root.sg"),
+ node -> Collections.emptyMap(),
+ node -> Collections.emptyMap(),
+ regionStatistics,
+ new SchemaRegionMemMetric(regionStatistics, "root.sg"));
+ }
+
+ private static MemSchemaRegionStatistics newRegionStatistics() {
+ return new MemSchemaRegionStatistics(0, new MemSchemaEngineStatistics());
+ }
+
+ private static void assertPathNotExist(final MTreeBelowSGMemoryImpl mtree,
final PartialPath path)
+ throws MetadataException {
+ try {
+ mtree.getNodeByPath(path);
+ Assert.fail("Expected path not exist: " + path.getFullPath());
+ } catch (final PathNotExistException ignored) {
+ // expected
+ }
+ }
+
+ private static class CountingMemMTreeStore extends MemMTreeStore {
+ private IMemMNode watchedParent;
+ private int watchedTraverserIteratorCount;
+ private int watchedChildLookupCount;
+
+ private CountingMemMTreeStore(
+ final PartialPath rootPath,
+ final MemSchemaRegionStatistics regionStatistics,
+ final SchemaRegionMemMetric metric) {
+ super(rootPath, regionStatistics, metric);
+ }
+
+ private void watchParent(final IMemMNode parent) {
+ this.watchedParent = parent;
+ this.watchedTraverserIteratorCount = 0;
+ this.watchedChildLookupCount = 0;
+ }
+
+ private int getWatchedTraverserIteratorCount() {
+ return watchedTraverserIteratorCount;
+ }
+
+ private int getWatchedChildLookupCount() {
+ return watchedChildLookupCount;
+ }
+
+ @Override
+ public IMemMNode getChild(final IMemMNode parent, final String name) {
+ if (parent == watchedParent) {
+ watchedChildLookupCount++;
+ }
+ return super.getChild(parent, name);
+ }
+
+ @Override
+ public IMNodeIterator<IMemMNode> getTraverserIterator(
+ final IMemMNode parent,
+ final Map<Integer, Template> templateMap,
+ final boolean skipPreDeletedSchema)
+ throws MetadataException {
+ if (parent == watchedParent) {
+ watchedTraverserIteratorCount++;
+ }
+ return super.getTraverserIterator(parent, templateMap,
skipPreDeletedSchema);
+ }
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/MTreeBelowSGCachedImplTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/MTreeBelowSGCachedImplTest.java
new file mode 100644
index 00000000000..65a6f31a75d
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/pbtree/MTreeBelowSGCachedImplTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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.schemaengine.schemaregion.mtree.impl.pbtree;
+
+import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.path.MeasurementPath;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.schema.SchemaConstant;
+import org.apache.iotdb.commons.schema.node.role.IMeasurementMNode;
+import org.apache.iotdb.db.conf.IoTDBConfig;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.exception.metadata.PathNotExistException;
+import org.apache.iotdb.db.schemaengine.metric.SchemaRegionCachedMetric;
+import org.apache.iotdb.db.schemaengine.rescon.CachedSchemaEngineStatistics;
+import org.apache.iotdb.db.schemaengine.rescon.CachedSchemaRegionStatistics;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.memory.ReleaseFlushMonitor;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.mnode.ICachedMNode;
+import
org.apache.iotdb.db.schemaengine.schemaregion.mtree.traverser.collector.MeasurementCollector;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.enums.CompressionType;
+import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MTreeBelowSGCachedImplTest {
+
+ private static final Field STORE_FIELD;
+ private static final Field ROOT_NODE_FIELD;
+
+ static {
+ try {
+ STORE_FIELD = MTreeBelowSGCachedImpl.class.getDeclaredField("store");
+ STORE_FIELD.setAccessible(true);
+ ROOT_NODE_FIELD =
MTreeBelowSGCachedImpl.class.getDeclaredField("rootNode");
+ ROOT_NODE_FIELD.setAccessible(true);
+ } catch (final NoSuchFieldException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig();
+ private int rawCachedMNodeSize;
+ private MTreeBelowSGCachedImpl mtree;
+
+ @Before
+ public void setUp() {
+ rawCachedMNodeSize = config.getCachedMNodeSizeInPBTreeMode();
+ config.setCachedMNodeSizeInPBTreeMode(10000);
+ ReleaseFlushMonitor.getInstance().clear();
+ ReleaseFlushMonitor.getInstance().init(new CachedSchemaEngineStatistics());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mtree != null) {
+ mtree.clear();
+ mtree = null;
+ }
+ ReleaseFlushMonitor.getInstance().clear();
+ FileUtils.deleteDirectory(new File(config.getSchemaDir()));
+ config.setCachedMNodeSizeInPBTreeMode(rawCachedMNodeSize);
+ }
+
+ @Test
+ public void
testDeviceDescendantFlagIsMaintainedAcrossCreateDeleteAndLazyRecompute()
+ throws Exception {
+ mtree = newMTree();
+
+ createBooleanTimeSeries("root.sg.a.b.s1");
+
+ ICachedMNode database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ ICachedMNode aNode = mtree.getNodeByPath(new PartialPath("root.sg.a"));
+ ICachedMNode bNode = mtree.getNodeByPath(new PartialPath("root.sg.a.b"));
+
+ Assert.assertTrue(database.hasDeviceDescendant());
+ Assert.assertTrue(database.isDeviceDescendantComputed());
+ Assert.assertFalse(aNode.isDevice());
+ Assert.assertTrue(aNode.hasDeviceDescendant());
+ Assert.assertTrue(aNode.isDeviceDescendantComputed());
+ Assert.assertTrue(bNode.isDevice());
+ Assert.assertFalse(bNode.hasDeviceDescendant());
+ Assert.assertTrue(bNode.isDeviceDescendantComputed());
+ mtree.unPinMNode(database);
+ mtree.unPinMNode(aNode);
+ mtree.unPinMNode(bNode);
+
+ createBooleanTimeSeries("root.sg.a.s0");
+ createBooleanTimeSeries("root.sg.c.d.s2");
+
+ aNode = mtree.getNodeByPath(new PartialPath("root.sg.a"));
+ ICachedMNode cNode = mtree.getNodeByPath(new PartialPath("root.sg.c"));
+ Assert.assertTrue(aNode.isDevice());
+ Assert.assertTrue(aNode.hasDeviceDescendant());
+ Assert.assertTrue(aNode.isDeviceDescendantComputed());
+ Assert.assertTrue(cNode.hasDeviceDescendant());
+ Assert.assertTrue(cNode.isDeviceDescendantComputed());
+
+ aNode.setHasDeviceDescendant(false);
+ aNode.setDeviceDescendantComputed(false);
+ mtree.unPinMNode(aNode);
+ cNode.setHasDeviceDescendant(false);
+ cNode.setDeviceDescendantComputed(false);
+ mtree.unPinMNode(cNode);
+
+ Assert.assertEquals(
+ Collections.singletonList("root.sg.a.b.s1"),
+ collectMeasurementPaths(new PartialPath("root.sg.a.**.s1")));
+
+ aNode = mtree.getNodeByPath(new PartialPath("root.sg.a"));
+ Assert.assertTrue(aNode.hasDeviceDescendant());
+ Assert.assertTrue(aNode.isDeviceDescendantComputed());
+ mtree.unPinMNode(aNode);
+ cNode = mtree.getNodeByPath(new PartialPath("root.sg.c"));
+ Assert.assertFalse(cNode.isDeviceDescendantComputed());
+ mtree.unPinMNode(cNode);
+
+ mtree.deleteTimeseries(new PartialPath("root.sg.a.s0"));
+ aNode = mtree.getNodeByPath(new PartialPath("root.sg.a"));
+ Assert.assertFalse(aNode.isDevice());
+ Assert.assertTrue(aNode.hasDeviceDescendant());
+ Assert.assertTrue(aNode.isDeviceDescendantComputed());
+ mtree.unPinMNode(aNode);
+
+ mtree.deleteTimeseries(new PartialPath("root.sg.a.b.s1"));
+ database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ Assert.assertTrue(database.hasDeviceDescendant());
+ Assert.assertTrue(database.isDeviceDescendantComputed());
+ mtree.unPinMNode(database);
+ assertPathNotExist(new PartialPath("root.sg.a"));
+
+ mtree.deleteTimeseries(new PartialPath("root.sg.c.d.s2"));
+ database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ Assert.assertFalse(database.hasDeviceDescendant());
+ Assert.assertTrue(database.isDeviceDescendantComputed());
+ mtree.unPinMNode(database);
+ assertPathNotExist(new PartialPath("root.sg.c"));
+ }
+
+ private MTreeBelowSGCachedImpl newMTree() throws Exception {
+ final CachedSchemaRegionStatistics regionStatistics =
+ new CachedSchemaRegionStatistics(0, new
CachedSchemaEngineStatistics());
+ return new MTreeBelowSGCachedImpl(
+ new PartialPath("root.sg"),
+ node -> Collections.emptyMap(),
+ node -> Collections.emptyMap(),
+ () -> {},
+ node -> {},
+ node -> {},
+ 0,
+ regionStatistics,
+ new SchemaRegionCachedMetric(regionStatistics, "root.sg"));
+ }
+
+ private void createBooleanTimeSeries(final String path) throws
MetadataException {
+ mtree.createTimeSeries(
+ new MeasurementPath(path),
+ TSDataType.BOOLEAN,
+ TSEncoding.PLAIN,
+ CompressionType.SNAPPY,
+ null,
+ null);
+ }
+
+ private List<String> collectMeasurementPaths(final PartialPath pattern)
throws Exception {
+ final ICachedMNode rootNode = (ICachedMNode) ROOT_NODE_FIELD.get(mtree);
+ final CachedMTreeStore store = (CachedMTreeStore) STORE_FIELD.get(mtree);
+ final List<String> matchedPaths = new ArrayList<>();
+ try (MeasurementCollector<Void, ICachedMNode> collector =
+ new MeasurementCollector<Void, ICachedMNode>(
+ rootNode, pattern, store, false, SchemaConstant.ALL_MATCH_SCOPE) {
+ @Override
+ protected Void collectMeasurement(final
IMeasurementMNode<ICachedMNode> node) {
+
matchedPaths.add(getCurrentMeasurementPathInTraverse(node).getFullPath());
+ return null;
+ }
+ }) {
+ collector.traverse();
+ }
+ return matchedPaths;
+ }
+
+ private void assertPathNotExist(final PartialPath path) throws
MetadataException {
+ try {
+ mtree.getNodeByPath(path);
+ Assert.fail("Expected path not exist: " + path.getFullPath());
+ } catch (final PathNotExistException ignored) {
+ // expected
+ }
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/common/AbstractAboveDatabaseMNode.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/common/AbstractAboveDatabaseMNode.java
index ab0592480e5..fc379e34e5b 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/common/AbstractAboveDatabaseMNode.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/common/AbstractAboveDatabaseMNode.java
@@ -37,6 +37,10 @@ public abstract class AbstractAboveDatabaseMNode<N extends
IMNode<N>, BasicNode
this.basicMNode = basicMNode;
}
+ public BasicNode getBasicMNode() {
+ return basicMNode;
+ }
+
@Override
public String getName() {
return basicMNode.getName();
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/utils/IMNodeIterator.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/utils/IMNodeIterator.java
index 75fd9d40c67..d1f410c5460 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/utils/IMNodeIterator.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/utils/IMNodeIterator.java
@@ -22,7 +22,7 @@ import org.apache.iotdb.commons.schema.node.IMNode;
import java.util.Iterator;
-public interface IMNodeIterator<N extends IMNode<?>> extends Iterator<N> {
+public interface IMNodeIterator<N extends IMNode<?>> extends Iterator<N>,
AutoCloseable {
void skipTemplateChildren();
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/tree/AbstractTreeVisitor.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/tree/AbstractTreeVisitor.java
index caa8019ef87..28b192e99ca 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/tree/AbstractTreeVisitor.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/tree/AbstractTreeVisitor.java
@@ -413,6 +413,10 @@ public abstract class AbstractTreeVisitor<N extends
ITreeNode, R> implements Sch
}
}
+ protected final IStateMatchInfo getCurrentStateMatchInfo() {
+ return currentStateMatchInfo;
+ }
+
// Release a child node.
protected void releaseNode(N node) {}