This is an automated email from the ASF dual-hosted git repository.

qiaojialin 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 dd5071df75 New schema tree traverse implementation (#5686)
dd5071df75 is described below

commit dd5071df755264aed9a337994b2e77985bad1df6
Author: Marcos_Zyk <[email protected]>
AuthorDate: Wed Apr 27 16:00:10 2022 +0800

    New schema tree traverse implementation (#5686)
---
 .../db/metadata/tree/AbstractTreeVisitor.java      | 376 +++++++++++++++++++++
 .../tree/AbstractTreeVisitorWithLimitOffset.java   | 106 ++++++
 .../tree/ITreeNode.java}                           |  28 +-
 .../db/mpp/common/schematree/DeviceSchemaInfo.java |  40 ++-
 .../iotdb/db/mpp/common/schematree/SchemaTree.java |  33 +-
 .../mpp/common/schematree/SchemaTreeVisitor.java   | 237 -------------
 .../schematree/{ => node}/SchemaEntityNode.java    |   2 +-
 .../schematree/{ => node}/SchemaInternalNode.java  |   2 +-
 .../{ => node}/SchemaMeasurementNode.java          |   2 +-
 .../common/schematree/{ => node}/SchemaNode.java   |   6 +-
 .../visitor/SchemaTreeDeviceVisitor.java           |  61 ++++
 .../visitor/SchemaTreeMeasurementVisitor.java      |  80 +++++
 .../schematree/visitor/SchemaTreeVisitor.java      |  60 ++++
 .../db/mpp/sql/analyze/FakeSchemaFetcherImpl.java  |   8 +-
 .../db/mpp/common/schematree/SchemaTreeTest.java   | 167 +++++++--
 .../operator/schema/SchemaFetchOperatorTest.java   |   2 +-
 16 files changed, 894 insertions(+), 316 deletions(-)

diff --git 
a/server/src/main/java/org/apache/iotdb/db/metadata/tree/AbstractTreeVisitor.java
 
b/server/src/main/java/org/apache/iotdb/db/metadata/tree/AbstractTreeVisitor.java
new file mode 100644
index 0000000000..658a6ebfc3
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/metadata/tree/AbstractTreeVisitor.java
@@ -0,0 +1,376 @@
+/*
+ * 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.metadata.tree;
+
+import org.apache.iotdb.db.metadata.path.PartialPath;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.regex.Pattern;
+
+import static 
org.apache.iotdb.commons.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD;
+import static 
org.apache.iotdb.commons.conf.IoTDBConstant.ONE_LEVEL_PATH_WILDCARD;
+
+/**
+ * This class defines a dfs-based algorithm of tree-traversing with path 
pattern match, and support
+ * iterating each element of the result.
+ *
+ * <p>This class takes three basis parameters as input:
+ *
+ * <ol>
+ *   <li>N root: the root node of the tree to be traversed.
+ *   <li>PartialPath patPattern: the pattern of path that the path of target 
element matches
+ *   <li>boolean isPrefixMatch: whether the pathPattern is used for matching 
the prefix; if so, all
+ *       elements with path starting with the matched prefix will be collected
+ * </ol>
+ *
+ * <p>If any tree wants to integrate and use this class. The following steps 
must be attained:
+ *
+ * <ol>
+ *   <li>The node of the tree must implement ITreeNode interface and the 
generic N should be defined
+ *       as the node class.
+ *   <li>The result type R should be defined.
+ *   <li>Implement the abstract methods, and for the concrete requirements, 
please refer to the
+ *       javadoc of specific method.
+ * </ol>
+ *
+ * @param <N> The node consisting the tree.
+ * @param <R> The result extracted from the tree.
+ */
+public abstract class AbstractTreeVisitor<N extends ITreeNode, R> implements 
Iterator<R> {
+
+  protected final N root;
+  protected final String[] nodes;
+  protected final boolean isPrefixMatch;
+
+  protected final Deque<VisitorStackEntry<N>> visitorStack = new 
ArrayDeque<>();
+  protected final Deque<N> ancestorStack = new ArrayDeque<>();
+
+  protected N nextMatchedNode;
+
+  protected AbstractTreeVisitor(N root, PartialPath pathPattern, boolean 
isPrefixMatch) {
+    this.root = root;
+    this.nodes = optimizePathPattern(pathPattern);
+    this.isPrefixMatch = isPrefixMatch;
+
+    visitorStack.push(
+        new VisitorStackEntry<>(Collections.singletonList(root).iterator(), 0, 
0, -1));
+  }
+
+  /**
+   * Optimize the given path pattern. Currently, the node name used for one 
level match will be
+   * transformed into a regex.
+   */
+  private String[] optimizePathPattern(PartialPath pathPattern) {
+    String[] rawNodes = pathPattern.getNodes();
+    List<String> optimizedNodes = new ArrayList<>(rawNodes.length);
+    for (String rawNode : rawNodes) {
+      if (rawNode.equals(MULTI_LEVEL_PATH_WILDCARD)) {
+        optimizedNodes.add(MULTI_LEVEL_PATH_WILDCARD);
+      } else if (rawNode.contains(ONE_LEVEL_PATH_WILDCARD)) {
+        optimizedNodes.add(rawNode.replace("*", ".*"));
+      } else {
+        optimizedNodes.add(rawNode);
+      }
+    }
+
+    return optimizedNodes.toArray(new String[0]);
+  }
+
+  @Override
+  public boolean hasNext() {
+    if (nextMatchedNode == null) {
+      getNext();
+    }
+    return nextMatchedNode != null;
+  }
+
+  @Override
+  public R next() {
+    if (!hasNext()) {
+      throw new NoSuchElementException();
+    }
+    R result = generateResult();
+    nextMatchedNode = null;
+    return result;
+  }
+
+  /**
+   * Basically, the algorithm traverse the tree with dfs strategy. When it 
comes to push children
+   * into stack, the path pattern will be used to filter the children.
+   *
+   * <p>When there's MULTI_LEVEL_WILDCARD in given path pattern. There are the 
following notices:
+   *
+   * <ol>
+   *   <li>When it comes to push children into stack and there's 
MULTI_LEVEL_WILDCARD before the
+   *       current patternIndex, all the children will be pushed.
+   *   <li>When a node cannot match the target node name in the patternIndex 
place and there's
+   *       MULTI_LEVEL_WILDCARD before the current patternIndex, the node 
names between the current
+   *       patternIndex and lastMultiLevelWildcardIndex will be checked until 
the partial path end
+   *       with current node can match one. The children will be pushed with 
the matched index + 1.
+   * </ol>
+   *
+   * <p>Each node and fullPath of the tree will be traversed at most once.
+   */
+  protected void getNext() {
+    nextMatchedNode = null;
+    VisitorStackEntry<N> stackEntry;
+    int patternIndex;
+    N node;
+    Iterator<N> iterator;
+    int lastMultiLevelWildcardIndex;
+    while (!visitorStack.isEmpty()) {
+      stackEntry = visitorStack.peek();
+      iterator = stackEntry.iterator;
+
+      if (!iterator.hasNext()) {
+        popStack();
+        continue;
+      }
+
+      node = iterator.next();
+      patternIndex = stackEntry.patternIndex;
+      lastMultiLevelWildcardIndex = stackEntry.lastMultiLevelWildcardIndex;
+
+      // only prefixMatch
+      if (patternIndex == nodes.length) {
+        if (processFullMatchedNode(node)) {
+          return;
+        }
+
+        if (!isLeafNode(node)) {
+          pushAllChildren(node, patternIndex, lastMultiLevelWildcardIndex);
+        }
+
+        if (nextMatchedNode != null) {
+          return;
+        }
+
+        continue;
+      }
+
+      if (checkIsMatch(patternIndex, node)) {
+        if (patternIndex == nodes.length - 1) {
+          if (processFullMatchedNode(node)) {
+            return;
+          }
+
+          if (!isLeafNode(node)) {
+            if (isPrefixMatch) {
+              pushAllChildren(node, patternIndex + 1, 
lastMultiLevelWildcardIndex);
+            } else if (nodes[patternIndex].equals(MULTI_LEVEL_PATH_WILDCARD)) {
+              pushAllChildren(node, patternIndex, patternIndex);
+            }
+          }
+
+          if (nextMatchedNode != null) {
+            return;
+          }
+
+          continue;
+        }
+
+        if (processInternalMatchedNode(node)) {
+          return;
+        }
+
+        if (!isLeafNode(node)) {
+          if (nodes[patternIndex + 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
+            pushAllChildren(node, patternIndex + 1, patternIndex + 1);
+          } else {
+            if (lastMultiLevelWildcardIndex > -1) {
+              pushAllChildren(node, patternIndex + 1, 
lastMultiLevelWildcardIndex);
+            } else if (nodes[patternIndex + 
1].contains(ONE_LEVEL_PATH_WILDCARD)) {
+              pushAllChildren(node, patternIndex + 1, 
lastMultiLevelWildcardIndex);
+            } else {
+              pushSingleChild(
+                  node, nodes[patternIndex + 1], patternIndex + 1, 
lastMultiLevelWildcardIndex);
+            }
+          }
+        }
+
+        if (nextMatchedNode != null) {
+          return;
+        }
+
+      } else {
+        if (lastMultiLevelWildcardIndex == -1) {
+          continue;
+        }
+
+        int lastMatchIndex = lastMultiLevelWildcardIndex;
+        for (int i = patternIndex - 1; i > lastMultiLevelWildcardIndex; i--) {
+          if (!checkIsMatch(i, node)) {
+            continue;
+          }
+
+          Iterator<N> ancestors = ancestorStack.iterator();
+          boolean allMatch = true;
+          for (int j = i - 1; j > lastMultiLevelWildcardIndex; j--) {
+            if (!checkIsMatch(j, ancestors.next())) {
+              allMatch = false;
+              break;
+            }
+          }
+
+          if (allMatch) {
+            lastMatchIndex = i;
+            break;
+          }
+        }
+
+        if (processInternalMatchedNode(node)) {
+          return;
+        }
+
+        if (!isLeafNode(node)) {
+          pushAllChildren(node, lastMatchIndex + 1, 
lastMultiLevelWildcardIndex);
+        }
+
+        if (nextMatchedNode != null) {
+          return;
+        }
+      }
+    }
+  }
+
+  public void reset() {
+    visitorStack.clear();
+    ancestorStack.clear();
+    nextMatchedNode = null;
+    visitorStack.push(
+        new VisitorStackEntry<>(Collections.singletonList(root).iterator(), 0, 
0, -1));
+  }
+
+  private void popStack() {
+    visitorStack.pop();
+    // The ancestor pop operation with level check supports the children of 
one node pushed by
+    // batch.
+    if (!visitorStack.isEmpty() && visitorStack.peek().level < 
ancestorStack.size()) {
+      ancestorStack.pop();
+    }
+  }
+
+  protected void pushSingleChild(
+      N parent, String childName, int patternIndex, int 
lastMultiLevelWildcardIndex) {
+    N child = getChild(parent, childName);
+    if (child != null) {
+      ancestorStack.push(parent);
+      visitorStack.push(
+          new VisitorStackEntry<>(
+              Collections.singletonList(child).iterator(),
+              patternIndex,
+              ancestorStack.size(),
+              lastMultiLevelWildcardIndex));
+    }
+  }
+
+  protected void pushAllChildren(N parent, int patternIndex, int 
lastMultiLevelWildcardIndex) {
+    ancestorStack.push(parent);
+    visitorStack.push(
+        new VisitorStackEntry<>(
+            getChildrenIterator(parent),
+            patternIndex,
+            ancestorStack.size(),
+            lastMultiLevelWildcardIndex));
+  }
+
+  protected boolean checkIsMatch(int patternIndex, N node) {
+    if (nodes[patternIndex].equals(MULTI_LEVEL_PATH_WILDCARD)) {
+      return true;
+    } else if (nodes[patternIndex].contains(ONE_LEVEL_PATH_WILDCARD)) {
+      return checkOneLevelWildcardMatch(nodes[patternIndex], node);
+    } else {
+      return checkNameMatch(nodes[patternIndex], node);
+    }
+  }
+
+  protected boolean checkOneLevelWildcardMatch(String regex, N node) {
+    return Pattern.matches(regex, node.getName());
+  }
+
+  protected boolean checkNameMatch(String targetName, N node) {
+    return targetName.equals(node.getName());
+  }
+
+  protected String[] generateFullPathNodes(N node) {
+    List<String> nodeNames = new ArrayList<>();
+    Iterator<N> iterator = ancestorStack.descendingIterator();
+    while (iterator.hasNext()) {
+      nodeNames.add(iterator.next().getName());
+    }
+    nodeNames.add(node.getName());
+    return nodeNames.toArray(new String[0]);
+  }
+
+  // Check whether the given node is a leaf node of this tree.
+  protected abstract boolean isLeafNode(N node);
+
+  // Get a child with the given childName.
+  protected abstract N getChild(N parent, String childName);
+
+  // Get a iterator of all children.
+  protected abstract Iterator<N> getChildrenIterator(N parent);
+
+  /**
+   * Internal-match means the node matches an internal node name of the given 
path pattern. root.sg
+   * internal match root.sg.**(pattern). This method should be implemented 
according to concrete
+   * tasks.
+   *
+   * <p>If return true, the traversing process won't check the subtree with 
the given node as root,
+   * and the result will be return immediately. If return false, the 
traversing process will keep
+   * traversing the subtree.
+   */
+  protected abstract boolean processInternalMatchedNode(N node);
+
+  /**
+   * Full-match means the node matches the last node name of the given path 
pattern. root.sg.d full
+   * match root.sg.**(pattern) This method should be implemented according to 
concrete tasks.
+   *
+   * <p>If return true, the traversing process won't check the subtree with 
the given node as root,
+   * and the result will be return immediately. f return false, the traversing 
process will keep
+   * traversing the subtree.
+   */
+  protected abstract boolean processFullMatchedNode(N node);
+
+  /** The method used for generating the result based on the matched node. */
+  protected abstract R generateResult();
+
+  protected static class VisitorStackEntry<N> {
+
+    private final Iterator<N> iterator;
+    private final int patternIndex;
+    private final int level;
+    private final int lastMultiLevelWildcardIndex;
+
+    VisitorStackEntry(
+        Iterator<N> iterator, int patternIndex, int level, int 
lastMultiLevelWildcardIndex) {
+      this.iterator = iterator;
+      this.patternIndex = patternIndex;
+      this.level = level;
+      this.lastMultiLevelWildcardIndex = lastMultiLevelWildcardIndex;
+    }
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/metadata/tree/AbstractTreeVisitorWithLimitOffset.java
 
b/server/src/main/java/org/apache/iotdb/db/metadata/tree/AbstractTreeVisitorWithLimitOffset.java
new file mode 100644
index 0000000000..d9e3b8336c
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/metadata/tree/AbstractTreeVisitorWithLimitOffset.java
@@ -0,0 +1,106 @@
+/*
+ * 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.metadata.tree;
+
+import org.apache.iotdb.db.metadata.path.PartialPath;
+
+/**
+ * This class defines a dfs-based traversing algorithm with limit and offset 
based on
+ * AbstractTreeVisitor.
+ *
+ * <p>This class takes two extra parameters as input:
+ *
+ * <ol>
+ *   <li>int limit: the max count of the results collected by one traversing 
process.
+ *   <li>int offset: the index of first matched node to be collected.
+ * </ol>
+ */
+public abstract class AbstractTreeVisitorWithLimitOffset<N extends ITreeNode, 
R>
+    extends AbstractTreeVisitor<N, R> {
+
+  protected final int limit;
+  protected final int offset;
+  protected final boolean hasLimit;
+
+  protected int count = 0;
+  protected int curOffset = -1;
+
+  protected AbstractTreeVisitorWithLimitOffset(
+      N root, PartialPath pathPattern, int limit, int offset, boolean 
isPrefixMatch) {
+    super(root, pathPattern, isPrefixMatch);
+    this.limit = limit;
+    this.offset = offset;
+    hasLimit = limit != 0;
+  }
+
+  @Override
+  public boolean hasNext() {
+    if (hasLimit) {
+      return count < limit && super.hasNext();
+    }
+
+    return super.hasNext();
+  }
+
+  @Override
+  protected void getNext() {
+    if (hasLimit) {
+      if (curOffset < offset) {
+        while (curOffset < offset) {
+          super.getNext();
+          curOffset += 1;
+          if (nextMatchedNode == null) {
+            return;
+          }
+        }
+      } else {
+        super.getNext();
+        curOffset += 1;
+      }
+    } else {
+      super.getNext();
+    }
+  }
+
+  @Override
+  public R next() {
+    R result = super.next();
+    if (hasLimit) {
+      count++;
+    }
+    return result;
+  }
+
+  @Override
+  public void reset() {
+    super.reset();
+    count = 0;
+    curOffset = -1;
+  }
+
+  public int getNextOffset() {
+    return curOffset + 1;
+  }
+
+  @Override
+  protected boolean processInternalMatchedNode(N node) {
+    return false;
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/DeviceSchemaInfo.java
 b/server/src/main/java/org/apache/iotdb/db/metadata/tree/ITreeNode.java
similarity index 51%
copy from 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/DeviceSchemaInfo.java
copy to server/src/main/java/org/apache/iotdb/db/metadata/tree/ITreeNode.java
index 8da6b056d1..f7300d282d 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/DeviceSchemaInfo.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/tree/ITreeNode.java
@@ -17,31 +17,9 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.mpp.common.schematree;
+package org.apache.iotdb.db.metadata.tree;
 
-import org.apache.iotdb.db.metadata.path.PartialPath;
-import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
+public interface ITreeNode {
 
-import java.util.List;
-
-public class DeviceSchemaInfo {
-
-  private PartialPath devicePath;
-  private boolean isAligned;
-  private List<MeasurementSchema> measurementSchemaList;
-
-  public DeviceSchemaInfo(
-      PartialPath devicePath, boolean isAligned, List<MeasurementSchema> 
measurementSchemaList) {
-    this.devicePath = devicePath;
-    this.isAligned = isAligned;
-    this.measurementSchemaList = measurementSchemaList;
-  }
-
-  public List<MeasurementSchema> getMeasurementSchemaList() {
-    return measurementSchemaList;
-  }
-
-  public boolean isAligned() {
-    return isAligned;
-  }
+  String getName();
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/DeviceSchemaInfo.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/DeviceSchemaInfo.java
index 8da6b056d1..9e1eecfc91 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/DeviceSchemaInfo.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/DeviceSchemaInfo.java
@@ -19,29 +19,55 @@
 
 package org.apache.iotdb.db.mpp.common.schematree;
 
+import org.apache.iotdb.db.metadata.path.MeasurementPath;
 import org.apache.iotdb.db.metadata.path.PartialPath;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaMeasurementNode;
 import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class DeviceSchemaInfo {
 
-  private PartialPath devicePath;
-  private boolean isAligned;
-  private List<MeasurementSchema> measurementSchemaList;
+  private final PartialPath devicePath;
+  private final boolean isAligned;
+  private final List<SchemaMeasurementNode> measurementNodeList;
 
   public DeviceSchemaInfo(
-      PartialPath devicePath, boolean isAligned, List<MeasurementSchema> 
measurementSchemaList) {
+      PartialPath devicePath, boolean isAligned, List<SchemaMeasurementNode> 
measurementNodeList) {
     this.devicePath = devicePath;
     this.isAligned = isAligned;
-    this.measurementSchemaList = measurementSchemaList;
+    this.measurementNodeList = measurementNodeList;
   }
 
-  public List<MeasurementSchema> getMeasurementSchemaList() {
-    return measurementSchemaList;
+  public PartialPath getDevicePath() {
+    return devicePath;
   }
 
   public boolean isAligned() {
     return isAligned;
   }
+
+  public List<MeasurementSchema> getMeasurementSchemaList() {
+    return measurementNodeList.stream()
+        .map(SchemaMeasurementNode::getSchema)
+        .collect(Collectors.toList());
+  }
+
+  public List<MeasurementPath> getMeasurements() {
+    return measurementNodeList.stream()
+        .map(
+            measurementNode -> {
+              MeasurementPath measurementPath =
+                  new MeasurementPath(
+                      devicePath.concatNode(measurementNode.getName()),
+                      measurementNode.getSchema());
+              measurementPath.setUnderAlignedEntity(isAligned);
+              if (measurementNode.getAlias() != null) {
+                
measurementPath.setMeasurementAlias(measurementNode.getAlias());
+              }
+              return measurementPath;
+            })
+        .collect(Collectors.toList());
+  }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTree.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTree.java
index d8a5586362..2bf2d8e72a 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTree.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTree.java
@@ -21,9 +21,14 @@ package org.apache.iotdb.db.mpp.common.schematree;
 
 import org.apache.iotdb.commons.utils.TestOnly;
 import org.apache.iotdb.db.exception.metadata.MetadataException;
-import 
org.apache.iotdb.db.exception.metadata.template.NoTemplateOnMNodeException;
 import org.apache.iotdb.db.metadata.path.MeasurementPath;
 import org.apache.iotdb.db.metadata.path.PartialPath;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaEntityNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaInternalNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaMeasurementNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaNode;
+import 
org.apache.iotdb.db.mpp.common.schematree.visitor.SchemaTreeDeviceVisitor;
+import 
org.apache.iotdb.db.mpp.common.schematree.visitor.SchemaTreeMeasurementVisitor;
 import org.apache.iotdb.tsfile.utils.Pair;
 import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
 import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
@@ -33,11 +38,10 @@ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.List;
-import java.util.Set;
 
 import static org.apache.iotdb.commons.conf.IoTDBConstant.PATH_ROOT;
-import static 
org.apache.iotdb.db.mpp.common.schematree.SchemaNode.SCHEMA_ENTITY_NODE;
-import static 
org.apache.iotdb.db.mpp.common.schematree.SchemaNode.SCHEMA_MEASUREMENT_NODE;
+import static 
org.apache.iotdb.db.mpp.common.schematree.node.SchemaNode.SCHEMA_ENTITY_NODE;
+import static 
org.apache.iotdb.db.mpp.common.schematree.node.SchemaNode.SCHEMA_MEASUREMENT_NODE;
 
 public class SchemaTree {
 
@@ -62,21 +66,21 @@ public class SchemaTree {
    */
   public Pair<List<MeasurementPath>, Integer> searchMeasurementPaths(
       PartialPath pathPattern, int slimit, int soffset, boolean isPrefixMatch) 
{
-    SchemaTreeVisitor visitor =
-        new SchemaTreeVisitor(root, pathPattern, slimit, soffset, 
isPrefixMatch);
+    SchemaTreeMeasurementVisitor visitor =
+        new SchemaTreeMeasurementVisitor(root, pathPattern, slimit, soffset, 
isPrefixMatch);
     return new Pair<>(visitor.getAllResult(), visitor.getNextOffset());
   }
 
   /**
-   * Get all device paths matching the path pattern.
+   * Get all device matching the path pattern.
    *
    * @param pathPattern the pattern of the target devices.
-   * @return A HashSet instance which stores devices paths matching the given 
path pattern.
+   * @return A HashSet instance which stores info of the devices matching the 
given path pattern.
    */
-  public Set<PartialPath> getMatchedDevices(PartialPath pathPattern, boolean 
isPrefixMatch)
+  public List<DeviceSchemaInfo> getMatchedDevices(PartialPath pathPattern, 
boolean isPrefixMatch)
       throws MetadataException {
-    // TODO: @zyk
-    throw new NoTemplateOnMNodeException("");
+    SchemaTreeDeviceVisitor visitor = new SchemaTreeDeviceVisitor(root, 
pathPattern, isPrefixMatch);
+    return visitor.getAllResult();
   }
 
   public DeviceSchemaInfo searchDeviceSchemaInfo(
@@ -88,13 +92,12 @@ public class SchemaTree {
       cur = cur.getChild(nodes[i]);
     }
 
-    List<MeasurementSchema> measurementSchemaList = new ArrayList<>();
+    List<SchemaMeasurementNode> measurementNodeList = new ArrayList<>();
     for (String measurement : measurements) {
-      
measurementSchemaList.add(cur.getChild(measurement).getAsMeasurementNode().getSchema());
+      
measurementNodeList.add(cur.getChild(measurement).getAsMeasurementNode());
     }
 
-    return new DeviceSchemaInfo(
-        devicePath, cur.getAsEntityNode().isAligned(), measurementSchemaList);
+    return new DeviceSchemaInfo(devicePath, cur.getAsEntityNode().isAligned(), 
measurementNodeList);
   }
 
   public void appendMeasurementPaths(List<MeasurementPath> 
measurementPathList) {
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTreeVisitor.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTreeVisitor.java
deleted file mode 100644
index b186881064..0000000000
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTreeVisitor.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * 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.mpp.common.schematree;
-
-import org.apache.iotdb.db.metadata.path.MeasurementPath;
-import org.apache.iotdb.db.metadata.path.PartialPath;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.regex.Pattern;
-
-import static 
org.apache.iotdb.commons.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD;
-import static 
org.apache.iotdb.commons.conf.IoTDBConstant.ONE_LEVEL_PATH_WILDCARD;
-
-public class SchemaTreeVisitor implements Iterator<MeasurementPath> {
-
-  private final SchemaNode root;
-  private final String[] nodes;
-  private final boolean isPrefixMatch;
-
-  private final int limit;
-  private final int offset;
-  private final boolean hasLimit;
-
-  private int count = 0;
-  private int curOffset = -1;
-
-  private final Deque<Integer> indexStack = new ArrayDeque<>();
-  private final Deque<Iterator<SchemaNode>> stack = new ArrayDeque<>();
-  private final Deque<SchemaNode> context = new ArrayDeque<>();
-
-  private SchemaMeasurementNode nextMatchedNode;
-
-  public SchemaTreeVisitor(
-      SchemaNode root, PartialPath pathPattern, int slimit, int soffset, 
boolean isPrefixMatch) {
-    this.root = root;
-    nodes = pathPattern.getNodes();
-    this.isPrefixMatch = isPrefixMatch;
-
-    limit = slimit;
-    offset = soffset;
-    hasLimit = slimit != 0;
-
-    indexStack.push(0);
-    stack.push(Collections.singletonList(root).iterator());
-  }
-
-  @Override
-  public boolean hasNext() {
-    if (nextMatchedNode == null) {
-      getNext();
-    }
-    return nextMatchedNode != null;
-  }
-
-  @Override
-  public MeasurementPath next() {
-    if (!hasNext()) {
-      throw new NoSuchElementException();
-    }
-    MeasurementPath result = generateMeasurementPath();
-    nextMatchedNode = null;
-    return result;
-  }
-
-  public List<MeasurementPath> getAllResult() {
-    List<MeasurementPath> result = new ArrayList<>();
-    while (hasNext()) {
-      result.add(next());
-    }
-    return result;
-  }
-
-  public int getNextOffset() {
-    return curOffset + 1;
-  }
-
-  public void resetStatus() {
-    count = 0;
-    curOffset = -1;
-    context.clear();
-    indexStack.clear();
-    indexStack.push(0);
-    stack.clear();
-    stack.push(Collections.singletonList(root).iterator());
-  }
-
-  private void getNext() {
-    if (hasLimit && count == limit) {
-      return;
-    }
-
-    int patternIndex;
-    SchemaNode node;
-    Iterator<SchemaNode> iterator;
-    while (!stack.isEmpty()) {
-      iterator = stack.peek();
-
-      if (!iterator.hasNext()) {
-        popStack();
-        continue;
-      }
-
-      node = iterator.next();
-      patternIndex = indexStack.peek();
-      if (patternIndex >= nodes.length - 1) {
-        if (node.isMeasurement()) {
-          if (hasLimit) {
-            curOffset += 1;
-            if (curOffset < offset) {
-              continue;
-            }
-            count++;
-          }
-
-          nextMatchedNode = node.getAsMeasurementNode();
-          return;
-        }
-
-        if (nodes[nodes.length - 1].equals(MULTI_LEVEL_PATH_WILDCARD) || 
isPrefixMatch) {
-          pushAllChildren(node, patternIndex);
-        }
-
-        continue;
-      }
-
-      if (nodes[patternIndex].equals(ONE_LEVEL_PATH_WILDCARD)) {
-        String regex = nodes[patternIndex].replace("*", ".*");
-        while (!checkOneLevelWildcardMatch(regex, node) && iterator.hasNext()) 
{
-          node = iterator.next();
-        }
-        if (!checkOneLevelWildcardMatch(regex, node)) {
-          popStack();
-          continue;
-        }
-      }
-
-      if (node.isMeasurement()) {
-        continue;
-      }
-
-      if (nodes[patternIndex + 1].contains(ONE_LEVEL_PATH_WILDCARD)) {
-        pushAllChildren(node, patternIndex + 1);
-      } else {
-        pushSingleChild(node, nodes[patternIndex + 1], patternIndex + 1);
-      }
-    }
-  }
-
-  private void popStack() {
-    stack.pop();
-    int patternIndex = indexStack.pop();
-    if (patternIndex == 0) {
-      return;
-    }
-    SchemaNode node = context.pop();
-
-    if (indexStack.isEmpty()) {
-      return;
-    }
-
-    int parentIndex = indexStack.peek();
-    if (patternIndex != parentIndex
-        && parentIndex < nodes.length - 1
-        && nodes[parentIndex].equals(MULTI_LEVEL_PATH_WILDCARD)) {
-      pushAllChildren(node, parentIndex);
-    }
-  }
-
-  private void pushAllChildren(SchemaNode node, int patternIndex) {
-    stack.push(node.getChildrenIterator());
-    context.push(node);
-    indexStack.push(patternIndex);
-  }
-
-  private void pushSingleChild(SchemaNode node, String childName, int 
patternIndex) {
-    SchemaNode child = node.getChild(childName);
-    if (child != null) {
-      stack.push(Collections.singletonList(child).iterator());
-    } else {
-      stack.push(Collections.emptyIterator());
-    }
-    context.push(node);
-    indexStack.push(patternIndex);
-  }
-
-  private boolean checkOneLevelWildcardMatch(String regex, SchemaNode node) {
-    if (!node.isMeasurement()) {
-      return Pattern.matches(regex, node.getName());
-    }
-
-    SchemaMeasurementNode measurementNode = node.getAsMeasurementNode();
-
-    return Pattern.matches(regex, measurementNode.getName())
-        || Pattern.matches(regex, measurementNode.getAlias());
-  }
-
-  private MeasurementPath generateMeasurementPath() {
-    List<String> nodeNames = new ArrayList<>();
-    Iterator<SchemaNode> iterator = context.descendingIterator();
-    while (iterator.hasNext()) {
-      nodeNames.add(iterator.next().getName());
-    }
-    nodeNames.add(nextMatchedNode.getName());
-    MeasurementPath result =
-        new MeasurementPath(nodeNames.toArray(new String[0]), 
nextMatchedNode.getSchema());
-    result.setUnderAlignedEntity(context.peek().getAsEntityNode().isAligned());
-    String alias = nextMatchedNode.getAlias();
-    if (nodes[nodes.length - 1].equals(alias)) {
-      result.setMeasurementAlias(alias);
-    }
-
-    return result;
-  }
-}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaEntityNode.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaEntityNode.java
similarity index 98%
rename from 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaEntityNode.java
rename to 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaEntityNode.java
index d477c16194..a1e1aff60e 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaEntityNode.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaEntityNode.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.mpp.common.schematree;
+package org.apache.iotdb.db.mpp.common.schematree.node;
 
 import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
 
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaInternalNode.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaInternalNode.java
similarity index 97%
rename from 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaInternalNode.java
rename to 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaInternalNode.java
index 215ac84e98..10af366554 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaInternalNode.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaInternalNode.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.mpp.common.schematree;
+package org.apache.iotdb.db.mpp.common.schematree.node;
 
 import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
 
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaMeasurementNode.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaMeasurementNode.java
similarity index 98%
rename from 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaMeasurementNode.java
rename to 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaMeasurementNode.java
index b8d76be279..ea9a5d1f19 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaMeasurementNode.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaMeasurementNode.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.mpp.common.schematree;
+package org.apache.iotdb.db.mpp.common.schematree.node;
 
 import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
 import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaNode.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaNode.java
similarity index 92%
rename from 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaNode.java
rename to 
server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaNode.java
index ed295fff42..069cbe02c8 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/SchemaNode.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/node/SchemaNode.java
@@ -17,14 +17,16 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.mpp.common.schematree;
+package org.apache.iotdb.db.mpp.common.schematree.node;
+
+import org.apache.iotdb.db.metadata.tree.ITreeNode;
 
 import java.nio.ByteBuffer;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Map;
 
-public abstract class SchemaNode {
+public abstract class SchemaNode implements ITreeNode {
 
   public static final byte SCHEMA_INTERNAL_NODE = 0;
   public static final byte SCHEMA_ENTITY_NODE = 1;
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeDeviceVisitor.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeDeviceVisitor.java
new file mode 100644
index 0000000000..c0a847783f
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeDeviceVisitor.java
@@ -0,0 +1,61 @@
+/*
+ * 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.mpp.common.schematree.visitor;
+
+import org.apache.iotdb.db.metadata.path.PartialPath;
+import org.apache.iotdb.db.mpp.common.schematree.DeviceSchemaInfo;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaMeasurementNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaNode;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class SchemaTreeDeviceVisitor extends 
SchemaTreeVisitor<DeviceSchemaInfo> {
+
+  public SchemaTreeDeviceVisitor(SchemaNode root, PartialPath pathPattern, 
boolean isPrefixMatch) {
+    super(root, pathPattern, 0, 0, isPrefixMatch);
+  }
+
+  @Override
+  protected boolean processFullMatchedNode(SchemaNode node) {
+    if (node.isEntity()) {
+      nextMatchedNode = node;
+    }
+    return false;
+  }
+
+  @Override
+  protected DeviceSchemaInfo generateResult() {
+    PartialPath path = new PartialPath(generateFullPathNodes(nextMatchedNode));
+    List<SchemaMeasurementNode> measurementNodeList = new ArrayList<>();
+    Iterator<SchemaNode> iterator = getChildrenIterator(nextMatchedNode);
+    SchemaNode node;
+    while (iterator.hasNext()) {
+      node = iterator.next();
+      if (node.isMeasurement()) {
+        measurementNodeList.add(node.getAsMeasurementNode());
+      }
+    }
+
+    return new DeviceSchemaInfo(
+        path, nextMatchedNode.getAsEntityNode().isAligned(), 
measurementNodeList);
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeMeasurementVisitor.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeMeasurementVisitor.java
new file mode 100644
index 0000000000..3d49ebd7d1
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeMeasurementVisitor.java
@@ -0,0 +1,80 @@
+/*
+ * 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.mpp.common.schematree.visitor;
+
+import org.apache.iotdb.db.metadata.path.MeasurementPath;
+import org.apache.iotdb.db.metadata.path.PartialPath;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaMeasurementNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaNode;
+
+import java.util.regex.Pattern;
+
+public class SchemaTreeMeasurementVisitor extends 
SchemaTreeVisitor<MeasurementPath> {
+
+  public SchemaTreeMeasurementVisitor(
+      SchemaNode root, PartialPath pathPattern, int slimit, int soffset, 
boolean isPrefixMatch) {
+    super(root, pathPattern, slimit, soffset, isPrefixMatch);
+  }
+
+  @Override
+  protected boolean checkOneLevelWildcardMatch(String regex, SchemaNode node) {
+    if (!node.isMeasurement()) {
+      return Pattern.matches(regex, node.getName());
+    }
+
+    SchemaMeasurementNode measurementNode = node.getAsMeasurementNode();
+
+    return Pattern.matches(regex, measurementNode.getName())
+        || Pattern.matches(regex, measurementNode.getAlias());
+  }
+
+  @Override
+  protected boolean checkNameMatch(String targetName, SchemaNode node) {
+    if (node.isMeasurement()) {
+      return targetName.equals(node.getName())
+          || targetName.equals(node.getAsMeasurementNode().getAlias());
+    }
+    return targetName.equals(node.getName());
+  }
+
+  @Override
+  protected boolean processFullMatchedNode(SchemaNode node) {
+    if (node.isMeasurement()) {
+      nextMatchedNode = node;
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  protected MeasurementPath generateResult() {
+    MeasurementPath result =
+        new MeasurementPath(
+            generateFullPathNodes(nextMatchedNode),
+            nextMatchedNode.getAsMeasurementNode().getSchema());
+    
result.setUnderAlignedEntity(ancestorStack.peek().getAsEntityNode().isAligned());
+    String alias = nextMatchedNode.getAsMeasurementNode().getAlias();
+    if (nodes[nodes.length - 1].equals(alias)) {
+      result.setMeasurementAlias(alias);
+    }
+
+    return result;
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeVisitor.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeVisitor.java
new file mode 100644
index 0000000000..01f99452a9
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeVisitor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.mpp.common.schematree.visitor;
+
+import org.apache.iotdb.db.metadata.path.PartialPath;
+import org.apache.iotdb.db.metadata.tree.AbstractTreeVisitorWithLimitOffset;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaNode;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public abstract class SchemaTreeVisitor<R>
+    extends AbstractTreeVisitorWithLimitOffset<SchemaNode, R> {
+
+  public SchemaTreeVisitor(
+      SchemaNode root, PartialPath pathPattern, int limit, int offset, boolean 
isPrefixMatch) {
+    super(root, pathPattern, limit, offset, isPrefixMatch);
+  }
+
+  public List<R> getAllResult() {
+    List<R> result = new ArrayList<>();
+    while (hasNext()) {
+      result.add(next());
+    }
+    return result;
+  }
+
+  @Override
+  protected boolean isLeafNode(SchemaNode node) {
+    return node.isMeasurement();
+  }
+
+  @Override
+  protected SchemaNode getChild(SchemaNode parent, String childName) {
+    return parent.getChild(childName);
+  }
+
+  @Override
+  protected Iterator<SchemaNode> getChildrenIterator(SchemaNode parent) {
+    return parent.getChildrenIterator();
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/sql/analyze/FakeSchemaFetcherImpl.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/sql/analyze/FakeSchemaFetcherImpl.java
index 66ca66f402..8924b1e09f 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/sql/analyze/FakeSchemaFetcherImpl.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/sql/analyze/FakeSchemaFetcherImpl.java
@@ -21,11 +21,11 @@ package org.apache.iotdb.db.mpp.sql.analyze;
 
 import org.apache.iotdb.db.metadata.path.PartialPath;
 import org.apache.iotdb.db.mpp.common.schematree.PathPatternTree;
-import org.apache.iotdb.db.mpp.common.schematree.SchemaEntityNode;
-import org.apache.iotdb.db.mpp.common.schematree.SchemaInternalNode;
-import org.apache.iotdb.db.mpp.common.schematree.SchemaMeasurementNode;
-import org.apache.iotdb.db.mpp.common.schematree.SchemaNode;
 import org.apache.iotdb.db.mpp.common.schematree.SchemaTree;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaEntityNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaInternalNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaMeasurementNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaNode;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
 
diff --git 
a/server/src/test/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTreeTest.java
 
b/server/src/test/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTreeTest.java
index adbd42431e..9f8d6d486d 100644
--- 
a/server/src/test/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTreeTest.java
+++ 
b/server/src/test/java/org/apache/iotdb/db/mpp/common/schematree/SchemaTreeTest.java
@@ -21,6 +21,11 @@ package org.apache.iotdb.db.mpp.common.schematree;
 import org.apache.iotdb.db.exception.metadata.IllegalPathException;
 import org.apache.iotdb.db.metadata.path.MeasurementPath;
 import org.apache.iotdb.db.metadata.path.PartialPath;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaEntityNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaInternalNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaMeasurementNode;
+import org.apache.iotdb.db.mpp.common.schematree.node.SchemaNode;
+import 
org.apache.iotdb.db.mpp.common.schematree.visitor.SchemaTreeMeasurementVisitor;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.utils.Pair;
 import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
@@ -45,28 +50,82 @@ public class SchemaTreeTest {
 
   @Test
   public void testMultiWildcard() throws IllegalPathException {
-    SchemaNode root = generateSchemaTree();
-    SchemaTreeVisitor visitor =
-        new SchemaTreeVisitor(root, new PartialPath("root.**.s1"), 0, 0, 
false);
+    SchemaNode root = generateSchemaTreeWithInternalRepeatedName();
+
+    SchemaTreeMeasurementVisitor visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.**.**.s"), 0, 0, false);
+    checkVisitorResult(
+        visitor,
+        4,
+        new String[] {"root.a.a.a.a.a.s", "root.a.a.a.a.s", "root.a.a.a.s", 
"root.a.a.s"},
+        null,
+        new boolean[] {false, false, false, false});
+
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.*.**.s"), 0, 0, false);
+    checkVisitorResult(
+        visitor,
+        4,
+        new String[] {"root.a.a.a.a.a.s", "root.a.a.a.a.s", "root.a.a.a.s", 
"root.a.a.s"},
+        null,
+        new boolean[] {false, false, false, false});
+
+    visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.**.a.**.s"), 0, 0, false);
     checkVisitorResult(
         visitor,
         3,
-        new String[] {"root.sg.d1.s1", "root.sg.d2.s1", "root.sg.d2.a.s1"},
+        new String[] {"root.a.a.a.a.a.s", "root.a.a.a.a.s", "root.a.a.a.s"},
         null,
-        new boolean[] {false, false, true});
+        new boolean[] {false, false, false});
+
+    visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.**.a.**.*.s"), 0, 0, false);
+    checkVisitorResult(
+        visitor,
+        2,
+        new String[] {"root.a.a.a.a.a.s", "root.a.a.a.a.s"},
+        null,
+        new boolean[] {false, false, false});
+
+    visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.a.**.a.*.s"), 0, 0, false);
+    checkVisitorResult(
+        visitor,
+        2,
+        new String[] {"root.a.a.a.a.a.s", "root.a.a.a.a.s"},
+        null,
+        new boolean[] {false, false, false});
+
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.**.c.s1"), 0, 0, false);
+    checkVisitorResult(
+        visitor,
+        2,
+        new String[] {"root.c.c.c.d.c.c.s1", "root.c.c.c.d.c.s1"},
+        null,
+        new boolean[] {false, false});
+
+    visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.**.c.d.c.s1"), 0, 0, false);
+    checkVisitorResult(visitor, 1, new String[] {"root.c.c.c.d.c.s1"}, null, 
new boolean[] {false});
+
+    visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.**.d.**.c.s1"), 0, 0, false);
+    checkVisitorResult(
+        visitor, 1, new String[] {"root.c.c.c.d.c.c.s1"}, null, new boolean[] 
{false});
   }
 
   private void testSchemaTree(SchemaNode root) throws Exception {
 
-    SchemaTreeVisitor visitor =
-        new SchemaTreeVisitor(root, new PartialPath("root.sg.d2.a.s1"), 0, 0, 
false);
+    SchemaTreeMeasurementVisitor visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.d2.a.s1"), 0, 0, false);
     checkVisitorResult(visitor, 1, new String[] {"root.sg.d2.a.s1"}, null, new 
boolean[] {true});
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.*.s2"), 0, 
0, false);
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.*.s2"), 0, 0, false);
     checkVisitorResult(
         visitor, 2, new String[] {"root.sg.d1.s2", "root.sg.d2.s2"}, new 
String[] {"", ""}, null);
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.*.status"), 
0, 0, false);
+    visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.*.status"), 0, 0, false);
     checkVisitorResult(
         visitor,
         2,
@@ -74,7 +133,8 @@ public class SchemaTreeTest {
         new String[] {"status", "status"},
         null);
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.d2.*.*"), 
0, 0, false);
+    visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.d2.*.*"), 0, 0, false);
     checkVisitorResult(
         visitor,
         2,
@@ -82,7 +142,7 @@ public class SchemaTreeTest {
         new String[] {"", ""},
         new boolean[] {true, true});
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.d1"), 0, 0, 
true);
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.d1"), 0, 0, true);
     checkVisitorResult(
         visitor,
         2,
@@ -90,7 +150,7 @@ public class SchemaTreeTest {
         new String[] {"", ""},
         new boolean[] {false, false});
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.*.a"), 0, 
0, true);
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.*.a"), 0, 0, true);
     checkVisitorResult(
         visitor,
         2,
@@ -99,7 +159,7 @@ public class SchemaTreeTest {
         new boolean[] {true, true},
         new int[] {0, 0});
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.*.*"), 2, 
2, false);
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.*.*"), 2, 2, false);
     checkVisitorResult(
         visitor,
         2,
@@ -108,7 +168,7 @@ public class SchemaTreeTest {
         new boolean[] {false, false},
         new int[] {3, 4});
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.*"), 2, 3, 
true);
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.*"), 2, 3, true);
     checkVisitorResult(
         visitor,
         2,
@@ -117,7 +177,7 @@ public class SchemaTreeTest {
         new boolean[] {true, false},
         new int[] {4, 5});
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.d1.**"), 0, 
0, false);
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.d1.**"), 0, 0, false);
     checkVisitorResult(
         visitor,
         2,
@@ -125,7 +185,7 @@ public class SchemaTreeTest {
         new String[] {"", ""},
         new boolean[] {false, false});
 
-    visitor = new SchemaTreeVisitor(root, new PartialPath("root.sg.d2.**"), 3, 
1, true);
+    visitor = new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.d2.**"), 3, 1, true);
     checkVisitorResult(
         visitor,
         3,
@@ -134,13 +194,14 @@ public class SchemaTreeTest {
         new boolean[] {true, false, false},
         new int[] {2, 3, 4});
 
-    visitor = new SchemaTreeVisitor(root, new 
PartialPath("root.sg.**.status"), 2, 1, true);
+    visitor =
+        new SchemaTreeMeasurementVisitor(root, new 
PartialPath("root.sg.**.status"), 2, 1, true);
     checkVisitorResult(
         visitor,
         2,
-        new String[] {"root.sg.d2.s2", "root.sg.d2.a.s2"},
+        new String[] {"root.sg.d2.a.s2", "root.sg.d2.s2"},
         new String[] {"status", "status"},
-        new boolean[] {false, true},
+        new boolean[] {true, false},
         new int[] {2, 3});
   }
 
@@ -184,8 +245,51 @@ public class SchemaTreeTest {
     return root;
   }
 
+  /**
+   * Generate the following tree: root.a.s, root.a.a.s, root.a.a.a.s, 
root.a.a.a.a.s,
+   * root.a.a.a.a.a.s, root.c.c.c.d.c.s1, root.c.c.c.d.c.c.s1
+   *
+   * @return the root node of the generated schemTree
+   */
+  private SchemaNode generateSchemaTreeWithInternalRepeatedName() {
+    SchemaNode root = new SchemaInternalNode("root");
+
+    SchemaNode parent = root;
+    SchemaNode a;
+    MeasurementSchema schema = new MeasurementSchema("s", TSDataType.INT32);
+    SchemaNode s;
+    for (int i = 0; i < 5; i++) {
+      a = new SchemaEntityNode("a");
+      s = new SchemaMeasurementNode("s", schema);
+      a.addChild("s", s);
+      parent.addChild("a", a);
+      parent = a;
+    }
+
+    parent = root;
+    SchemaNode c;
+    for (int i = 0; i < 3; i++) {
+      c = new SchemaInternalNode("c");
+      parent.addChild("c", c);
+      parent = c;
+    }
+
+    SchemaNode d = new SchemaInternalNode("d");
+    parent.addChild("d", d);
+    parent = d;
+
+    for (int i = 0; i < 2; i++) {
+      c = new SchemaEntityNode("c");
+      c.addChild("s1", new SchemaMeasurementNode("s1", schema));
+      parent.addChild("c", c);
+      parent = c;
+    }
+
+    return root;
+  }
+
   private void checkVisitorResult(
-      SchemaTreeVisitor visitor,
+      SchemaTreeMeasurementVisitor visitor,
       int expectedNum,
       String[] expectedPath,
       String[] expectedAlias,
@@ -210,7 +314,7 @@ public class SchemaTreeTest {
   }
 
   private void checkVisitorResult(
-      SchemaTreeVisitor visitor,
+      SchemaTreeMeasurementVisitor visitor,
       int expectedNum,
       String[] expectedPath,
       String[] expectedAlias,
@@ -218,7 +322,7 @@ public class SchemaTreeTest {
       int[] expectedOffset) {
     checkVisitorResult(visitor, expectedNum, expectedPath, expectedAlias, 
expectedAligned);
 
-    visitor.resetStatus();
+    visitor.reset();
     int i = 0;
     MeasurementPath result;
     while (visitor.hasNext()) {
@@ -266,6 +370,25 @@ public class SchemaTreeTest {
             .collect(Collectors.toList()));
   }
 
+  @Test
+  public void testGetMatchedDevices() throws Exception {
+    SchemaTree schemaTree = new SchemaTree(generateSchemaTree());
+
+    List<DeviceSchemaInfo> deviceSchemaInfoList =
+        schemaTree.getMatchedDevices(new PartialPath("root.sg.d2.a"), false);
+    Assert.assertEquals(1, deviceSchemaInfoList.size());
+    DeviceSchemaInfo deviceSchemaInfo = deviceSchemaInfoList.get(0);
+    Assert.assertEquals(new PartialPath("root.sg.d2.a"), 
deviceSchemaInfo.getDevicePath());
+    Assert.assertTrue(deviceSchemaInfo.isAligned());
+    Assert.assertEquals(2, deviceSchemaInfo.getMeasurements().size());
+
+    deviceSchemaInfoList = schemaTree.getMatchedDevices(new 
PartialPath("root.sg.*"), false);
+    Assert.assertEquals(2, deviceSchemaInfoList.size());
+
+    deviceSchemaInfoList = schemaTree.getMatchedDevices(new 
PartialPath("root.sg.**"), false);
+    Assert.assertEquals(3, deviceSchemaInfoList.size());
+  }
+
   @Test
   public void testSerialization() throws Exception {
     SchemaNode root = generateSchemaTree();
diff --git 
a/server/src/test/java/org/apache/iotdb/db/mpp/operator/schema/SchemaFetchOperatorTest.java
 
b/server/src/test/java/org/apache/iotdb/db/mpp/operator/schema/SchemaFetchOperatorTest.java
index 2f5317e458..d58640c922 100644
--- 
a/server/src/test/java/org/apache/iotdb/db/mpp/operator/schema/SchemaFetchOperatorTest.java
+++ 
b/server/src/test/java/org/apache/iotdb/db/mpp/operator/schema/SchemaFetchOperatorTest.java
@@ -99,7 +99,7 @@ public class SchemaFetchOperatorTest {
         schemaTree.searchMeasurementPaths(new 
PartialPath("root.sg.**.status"), 0, 0, false);
     Assert.assertEquals(3, pair.left.size());
     Assert.assertEquals(
-        Arrays.asList("root.sg.d1.s2", "root.sg.d2.s2", "root.sg.d2.a.s2"),
+        Arrays.asList("root.sg.d1.s2", "root.sg.d2.a.s2", "root.sg.d2.s2"),
         
pair.left.stream().map(MeasurementPath::getFullPath).collect(Collectors.toList()));
   }
 

Reply via email to