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 a565a068253 Added flags to mark whether a device has device
descendants to optimize query like select xx from xxx.** (#17672)
a565a068253 is described below
commit a565a06825317e5f39393e619fb44e3fbbb88a9b
Author: Caideyipi <[email protected]>
AuthorDate: Mon Jun 1 10:58:03 2026 +0800
Added flags to mark whether a device has device descendants to optimize
query like select xx from xxx.** (#17672)
* flag-
* Fix
* Address device descendant flag review comments
---
.../mtree/impl/mem/MTreeBelowSGMemoryImpl.java | 81 ++++--
.../mtree/impl/mem/mnode/IMemMNode.java | 23 +-
.../mtree/impl/mem/mnode/basic/BasicMNode.java | 21 +-
.../impl/mem/mnode/impl/AboveDatabaseMNode.java | 10 -
.../mtree/impl/mem/mnode/impl/DatabaseMNode.java | 10 -
.../impl/mem/mnode/impl/MeasurementMNode.java | 10 -
.../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 | 281 +++++++++++++++++++++
.../impl/pbtree/MTreeBelowSGCachedImplTest.java | 217 ++++++++++++++++
.../node/common/AbstractAboveDatabaseMNode.java | 4 +
.../commons/schema/node/utils/IMNodeIterator.java | 2 +-
.../commons/schema/tree/AbstractTreeVisitor.java | 4 +
17 files changed, 869 insertions(+), 65 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 ad1fff7403d..44c4c49f274 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
@@ -208,6 +208,43 @@ public class MTreeBelowSGMemoryImpl {
}
}
+ 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();
@@ -316,7 +353,7 @@ public class MTreeBelowSGMemoryImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
}
// create a non-aligned time series
@@ -410,7 +447,7 @@ public class MTreeBelowSGMemoryImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
entityMNode.setAligned(true);
}
@@ -658,14 +695,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;
+ }
}
}
}
@@ -674,6 +712,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);
@@ -1034,7 +1073,7 @@ public class MTreeBelowSGMemoryImpl {
if (cur.isDevice()) {
entityMNode = cur.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(cur);
+ entityMNode = setToEntityAndUpdateFlags(cur);
}
}
@@ -1142,7 +1181,7 @@ public class MTreeBelowSGMemoryImpl {
if (cur.isDevice()) {
entityMNode = cur.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(cur);
+ entityMNode = setToEntityAndUpdateFlags(cur);
}
if (!entityMNode.isAligned()) {
@@ -1197,14 +1236,21 @@ public class MTreeBelowSGMemoryImpl {
private long rebuildSubtreeMeasurementCountFromNode(final IMemMNode node) {
long count = node.isMeasurement() ? 1L : 0L;
- final IMNodeIterator<IMemMNode> iterator = store.getChildrenIterator(node);
- while (iterator.hasNext()) {
- count += rebuildSubtreeMeasurementCountFromNode(iterator.next());
+ 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;
}
@@ -1787,7 +1833,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);
@@ -1928,7 +1974,7 @@ public class MTreeBelowSGMemoryImpl {
(TableDeviceInfo<IMemMNode>) entityMNode.getDeviceInfo();
attributeUpdater.accept(deviceInfo.getAttributePointer());
} else {
- entityMNode = store.setToEntity(cur);
+ entityMNode = setToEntityAndUpdateFlags(cur);
final TableDeviceInfo<IMemMNode> deviceInfo = new TableDeviceInfo<>();
deviceInfo.setAttributePointer(attributePointerGetter.getAsInt());
entityMNode.getAsInternalMNode().setDeviceInfo(deviceInfo);
@@ -2046,6 +2092,7 @@ public class MTreeBelowSGMemoryImpl {
collector.traverse();
}
databaseMNode.deleteChild(tableName);
+ refreshAncestorsHavingDeviceDescendant(databaseMNode);
regionStatistics.resetTableDevice(tableName);
store.releaseMemory(memoryReleased.get());
return true;
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 6cd14800f3e..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,14 +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> {
+ BasicMNode getBasicMNode();
+
/**
* The count of measurement nodes contained in the subtree rooted at this
node. The counter is
* maintained in memory only.
*/
- long getSubtreeMeasurementCount();
+ 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();
+ }
- void setSubtreeMeasurementCount(long subtreeMeasurementCount);
+ 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 bd80d10193b..fc46f9a1196 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
@@ -50,6 +50,9 @@ public class BasicMNode implements IMemMNode {
/** 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;
@@ -59,6 +62,11 @@ public class BasicMNode implements IMemMNode {
this.basicMNodeInfo = new BasicMNodeInfo(name);
}
+ @Override
+ public BasicMNode getBasicMNode() {
+ return this;
+ }
+
@Override
public String getName() {
return basicMNodeInfo.getName();
@@ -113,6 +121,16 @@ public class BasicMNode implements IMemMNode {
this.subtreeMeasurementCount = subtreeMeasurementCount;
}
+ @Override
+ public boolean hasDeviceDescendant() {
+ return hasDeviceDescendant;
+ }
+
+ @Override
+ public void setHasDeviceDescendant(final boolean hasDeviceDescendant) {
+ this.hasDeviceDescendant = hasDeviceDescendant;
+ }
+
@Override
public PartialPath getPartialPath() {
final List<String> detachedPath = new ArrayList<>();
@@ -240,6 +258,7 @@ public class BasicMNode implements IMemMNode {
* <li>parent reference, 8B
* <li>fullPath reference, 8B
* <li>subtreeMeasurementCount, 8B
+ * <li>hasDeviceDescendant, 1B
* </ol>
* <li>MapEntry in parent
* <ol>
@@ -251,7 +270,7 @@ public class BasicMNode implements IMemMNode {
*/
@Override
public int estimateSize() {
- return 8 + 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/mem/mnode/impl/AboveDatabaseMNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/AboveDatabaseMNode.java
index 87144d4954a..cff30d8b8c4 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/AboveDatabaseMNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/AboveDatabaseMNode.java
@@ -33,14 +33,4 @@ public class AboveDatabaseMNode extends
AbstractAboveDatabaseMNode<IMemMNode, Ba
public IMemMNode getAsMNode() {
return this;
}
-
- @Override
- public long getSubtreeMeasurementCount() {
- return basicMNode.getSubtreeMeasurementCount();
- }
-
- @Override
- public void setSubtreeMeasurementCount(long subtreeMeasurementCount) {
- basicMNode.setSubtreeMeasurementCount(subtreeMeasurementCount);
- }
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/DatabaseMNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/DatabaseMNode.java
index 290cf427360..c6b2f5e2427 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/DatabaseMNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/DatabaseMNode.java
@@ -45,14 +45,4 @@ public class DatabaseMNode extends
AbstractDatabaseMNode<IMemMNode, BasicInterna
public void setDeviceInfo(IDeviceInfo<IMemMNode> deviceInfo) {
basicMNode.setDeviceInfo(deviceInfo);
}
-
- @Override
- public long getSubtreeMeasurementCount() {
- return basicMNode.getSubtreeMeasurementCount();
- }
-
- @Override
- public void setSubtreeMeasurementCount(long subtreeMeasurementCount) {
- basicMNode.setSubtreeMeasurementCount(subtreeMeasurementCount);
- }
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/MeasurementMNode.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/MeasurementMNode.java
index d2a2cbd80c9..a40cbe6bc0f 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/MeasurementMNode.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/impl/MeasurementMNode.java
@@ -52,14 +52,4 @@ public class MeasurementMNode extends
AbstractMeasurementMNode<IMemMNode, BasicM
public final boolean isLogicalView() {
return false;
}
-
- @Override
- public long getSubtreeMeasurementCount() {
- return basicMNode.getSubtreeMeasurementCount();
- }
-
- @Override
- public void setSubtreeMeasurementCount(long subtreeMeasurementCount) {
- basicMNode.setSubtreeMeasurementCount(subtreeMeasurementCount);
- }
}
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 edf4c17cb45..a660314df95 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
@@ -138,6 +138,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,
@@ -354,7 +416,7 @@ public class MTreeBelowSGCachedImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
device = entityMNode.getAsMNode();
}
@@ -459,7 +521,7 @@ public class MTreeBelowSGCachedImpl {
if (device.isDevice()) {
entityMNode = device.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(device);
+ entityMNode = setToEntityAndUpdateFlags(device);
entityMNode.setAligned(true);
device = entityMNode.getAsMNode();
}
@@ -685,8 +747,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);
@@ -698,12 +759,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));
@@ -730,14 +790,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();
}
}
@@ -1076,7 +1133,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.
@@ -1198,7 +1255,7 @@ public class MTreeBelowSGCachedImpl {
if (cur.isDevice()) {
entityMNode = cur.getAsDeviceMNode();
} else {
- entityMNode = store.setToEntity(cur);
+ entityMNode = setToEntityAndUpdateFlags(cur);
}
if (entityMNode.isUseTemplate()) {
@@ -1244,7 +1301,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 6f29c738a04..c23eebbb272 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
@@ -48,6 +48,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;
@@ -57,6 +68,11 @@ public class CachedBasicMNode implements ICachedMNode {
this.cacheMNodeInfo = new CacheMNodeInfo(name);
}
+ @Override
+ public CachedBasicMNode getBasicMNode() {
+ return this;
+ }
+
@Override
public String getName() {
return cacheMNodeInfo.getName();
@@ -100,6 +116,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<>();
@@ -246,6 +282,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>
@@ -257,7 +295,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 24b7e93939e..93c0d9e31c7 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,6 +25,7 @@ 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;
@@ -32,18 +33,22 @@ import
org.apache.iotdb.commons.schema.node.utils.IMNodeIterator;
import org.apache.iotdb.commons.schema.template.Template;
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.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.schema.SchemaConstant.NON_TEMPLATE;
@@ -247,10 +252,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 601233d9d60..3de059127d3 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..1dd3fa456b7
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/MTreeBelowSGMemoryImplTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.commons.schema.template.Template;
+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.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;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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 testDeviceDescendantFlagIsMaintainedAcrossTableDeviceDrop()
throws Exception {
+ final MTreeBelowSGMemoryImpl mtree = newMTree();
+ final AtomicInteger attributePointer = new AtomicInteger();
+
+ mtree.createOrUpdateTableDevice(
+ "t",
+ new String[] {"hebei", "p_1", "d_0"},
+ attributePointer::getAndIncrement,
+ ignored -> {});
+
+ IMemMNode database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ final IMemMNode table = mtree.getNodeByPath(new PartialPath("root.sg.t"));
+ final IMemMNode province = mtree.getNodeByPath(new
PartialPath("root.sg.t.hebei"));
+ final IMemMNode device = mtree.getNodeByPath(new
PartialPath("root.sg.t.hebei.p_1.d_0"));
+ Assert.assertTrue(database.hasDeviceDescendant());
+ Assert.assertTrue(table.hasDeviceDescendant());
+ Assert.assertTrue(province.hasDeviceDescendant());
+ Assert.assertTrue(device.isDevice());
+ Assert.assertFalse(device.hasDeviceDescendant());
+
+ Assert.assertTrue(mtree.deleteTableDevice("t", ignored -> {}));
+ database = mtree.getNodeByPath(new PartialPath("root.sg"));
+ Assert.assertFalse(database.hasDeviceDescendant());
+ assertPathNotExist(mtree, new PartialPath("root.sg.t"));
+ }
+
+ @Test
+ public void testLeafDeviceWildcardSuffixUsesDirectMeasurementLookup() throws
Exception {
+ final PartialPath databasePath =
PartialPath.getQualifiedDatabasePartialPath("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(
+ PartialPath.getQualifiedDatabasePartialPath("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..81936640b49
--- /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.tsfile.enums.TSDataType;
+import org.apache.tsfile.external.commons.io.FileUtils;
+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(
+ PartialPath.getQualifiedDatabasePartialPath("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 3b821567c38..757e6bafbbc 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
@@ -38,6 +38,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 a60a4ff121a..512270c379a 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
@@ -415,6 +415,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) {}