You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by zy...@apache.org on 2023/05/05 01:34:11 UTC

[iotdb] branch master updated: [IOTDB-5824] Fix show devices with * cannot display satisfied devices (#9755)

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

zyk 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 3f166ed37a5 [IOTDB-5824] Fix show devices with * cannot display satisfied devices (#9755)
3f166ed37a5 is described below

commit 3f166ed37a5500cf8c3511e5dc2c43ac3e58d6a6
Author: Marcos_Zyk <38...@users.noreply.github.com>
AuthorDate: Fri May 5 09:34:03 2023 +0800

    [IOTDB-5824] Fix show devices with * cannot display satisfied devices (#9755)
---
 .../commons/schema/tree/AbstractTreeVisitor.java   | 122 +++++++++++++++++----
 .../db/metadata/mtree/traverser/Traverser.java     |  61 +++++++++++
 .../traverser/TraverserWithLimitOffsetWrapper.java |   5 +
 .../mtree/traverser/basic/DatabaseTraverser.java   |   5 +
 .../mtree/traverser/basic/EntityTraverser.java     |   8 ++
 .../mtree/traverser/basic/MNodeTraverser.java      |   5 +
 .../traverser/basic/MeasurementTraverser.java      |   5 +
 .../visitor/SchemaTreeDeviceVisitor.java           |   5 +
 .../visitor/SchemaTreeMeasurementVisitor.java      |   5 +
 .../SchemaTreeVisitorWithLimitOffsetWrapper.java   |   6 +
 .../schemaRegion/SchemaRegionBasicTest.java        | 103 ++++++++++++++++-
 11 files changed, 305 insertions(+), 25 deletions(-)

diff --git a/node-commons/src/main/java/org/apache/iotdb/commons/schema/tree/AbstractTreeVisitor.java b/node-commons/src/main/java/org/apache/iotdb/commons/schema/tree/AbstractTreeVisitor.java
index a2fa004d9ca..817f3c44ea1 100644
--- a/node-commons/src/main/java/org/apache/iotdb/commons/schema/tree/AbstractTreeVisitor.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/schema/tree/AbstractTreeVisitor.java
@@ -578,10 +578,10 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
       while (iterator.hasNext()) {
         child = iterator.next();
 
+        // find first matched state
         if (!preciseMatchTransitionMap.isEmpty()) {
           matchedState = tryGetNextState(child, sourceState, preciseMatchTransitionMap);
         }
-
         transitionIterator = patternFA.getFuzzyMatchTransitionIterator(sourceState);
         if (matchedState == null) {
           while (transitionIterator.hasNext()) {
@@ -596,16 +596,36 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
           }
         }
 
-        if (patternFA.mayTransitionOverlap()) {
-          if (transitionIterator.hasNext()) {
+        // check whether accept the first matched state
+        if (mayTargetNodeType(child) && !matchedState.isFinal()) {
+          // not accept the first matched state since this node may be a target result, check the
+          // other states
+          if (patternFA.mayTransitionOverlap() && transitionIterator.hasNext()) {
             stateMatchInfo = new StateMultiMatchInfo(patternFA, matchedState, transitionIterator);
             firstAncestorOfTraceback = ancestorStack.size();
+
+            while (transitionIterator.hasNext()) {
+              matchedState = tryGetNextState(child, sourceState, transitionIterator.next());
+              if (matchedState != null) {
+                stateMatchInfo.addMatchedState(matchedState);
+                if (matchedState.isFinal()) {
+                  break;
+                }
+              }
+            }
           } else {
             stateMatchInfo = new StateSingleMatchInfo(patternFA, matchedState);
           }
         } else {
-          stateMatchInfo = new StateSingleMatchInfo(patternFA, matchedState);
+          // accept the first matched state, directly save it
+          if (patternFA.mayTransitionOverlap() && transitionIterator.hasNext()) {
+            stateMatchInfo = new StateMultiMatchInfo(patternFA, matchedState, transitionIterator);
+            firstAncestorOfTraceback = ancestorStack.size();
+          } else {
+            stateMatchInfo = new StateSingleMatchInfo(patternFA, matchedState);
+          }
         }
+
         saveResult(child, stateMatchInfo);
         return;
       }
@@ -651,21 +671,43 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
         child = iterator.next();
 
         stateMatchInfo = new StateMultiMatchInfo(patternFA);
-        for (int i = 0; i < sourceStateMatchInfo.getMatchedStateSize(); i++) {
-          sourceState = sourceStateMatchInfo.getMatchedState(i);
-          transitionIterator = tryGetNextMatchedState(child, sourceState, stateMatchInfo);
-          if (stateMatchInfo.getMatchedStateSize() > 0) {
-            stateMatchInfo.setSourceStateOrdinal(i);
-            stateMatchInfo.setSourceTransitionIterator(transitionIterator);
-            break;
+        if (mayTargetNodeType(child)) {
+          for (int i = 0; i < sourceStateMatchInfo.getMatchedStateSize(); i++) {
+            sourceState = sourceStateMatchInfo.getMatchedState(i);
+            transitionIterator = tryGetNextMatchedState(child, sourceState, stateMatchInfo, true);
+            if (stateMatchInfo.getMatchedStateSize() > 0) {
+              stateMatchInfo.setSourceStateOrdinal(i);
+              stateMatchInfo.setSourceTransitionIterator(transitionIterator);
+              if (stateMatchInfo.hasFinalState()) {
+                break;
+              }
+            }
+          }
+
+          if (stateMatchInfo.getMatchedStateSize() == 0 || !stateMatchInfo.hasFinalState()) {
+            traceback(child, stateMatchInfo, sourceStateMatchInfo.getMatchedStateSize() - 1, true);
+            if (stateMatchInfo.getMatchedStateSize() == 0) {
+              releaseNode(child);
+              continue;
+            }
+          }
+        } else {
+          for (int i = 0; i < sourceStateMatchInfo.getMatchedStateSize(); i++) {
+            sourceState = sourceStateMatchInfo.getMatchedState(i);
+            transitionIterator = tryGetNextMatchedState(child, sourceState, stateMatchInfo, false);
+            if (stateMatchInfo.getMatchedStateSize() > 0) {
+              stateMatchInfo.setSourceStateOrdinal(i);
+              stateMatchInfo.setSourceTransitionIterator(transitionIterator);
+              break;
+            }
           }
-        }
 
-        if (stateMatchInfo.getMatchedStateSize() == 0) {
-          traceback(child, stateMatchInfo, sourceStateMatchInfo.getMatchedStateSize() - 1);
           if (stateMatchInfo.getMatchedStateSize() == 0) {
-            releaseNode(child);
-            continue;
+            traceback(child, stateMatchInfo, sourceStateMatchInfo.getMatchedStateSize() - 1, false);
+            if (stateMatchInfo.getMatchedStateSize() == 0) {
+              releaseNode(child);
+              continue;
+            }
           }
         }
 
@@ -683,7 +725,10 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
      * @return iterator of rest transitions
      */
     private Iterator<IFATransition> tryGetNextMatchedState(
-        N child, IFAState sourceState, IStateMatchInfo currentStateMatchInfo) {
+        N child,
+        IFAState sourceState,
+        IStateMatchInfo currentStateMatchInfo,
+        boolean needFinalState) {
       Map<String, IFATransition> preciseMatchTransitionMap =
           patternFA.getPreciseMatchTransition(sourceState);
 
@@ -692,7 +737,9 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
         matchedState = tryGetNextState(child, sourceState, preciseMatchTransitionMap);
         if (matchedState != null) {
           currentStateMatchInfo.addMatchedState(matchedState);
-          return patternFA.getFuzzyMatchTransitionIterator(sourceState);
+          if (!needFinalState || matchedState.isFinal()) {
+            return patternFA.getFuzzyMatchTransitionIterator(sourceState);
+          }
         }
       }
 
@@ -702,20 +749,26 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
         matchedState = tryGetNextState(child, sourceState, transitionIterator.next());
         if (matchedState != null) {
           currentStateMatchInfo.addMatchedState(matchedState);
-          return transitionIterator;
+          if (!needFinalState || matchedState.isFinal()) {
+            return transitionIterator;
+          }
         }
       }
       return transitionIterator;
     }
 
-    private void traceback(N node, IStateMatchInfo stateMatchInfo, int checkedSourceStateOrdinal) {
+    private void traceback(
+        N node,
+        IStateMatchInfo stateMatchInfo,
+        int checkedSourceStateOrdinal,
+        boolean needFinalState) {
       IStateMatchInfo parentStateMatchInfo;
 
       N currentNode;
       IStateMatchInfo currentStateMatchInfo;
 
       int sourceStateOrdinal;
-      IFAState sourceState;
+      IFAState sourceState = null;
       Iterator<IFATransition> transitionIterator = null;
 
       int matchedStateSize;
@@ -770,7 +823,8 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
               sourceState = parentStateMatchInfo.getMatchedState(sourceStateOrdinal);
               matchedStateSize = currentStateMatchInfo.getMatchedStateSize();
               transitionIterator =
-                  tryGetNextMatchedState(currentNode, sourceState, currentStateMatchInfo);
+                  tryGetNextMatchedState(
+                      currentNode, sourceState, currentStateMatchInfo, needFinalState);
               // change of matchedStateSize means currentNode there is transition from sourceState
               // matching currentNode
               if (matchedStateSize != currentStateMatchInfo.getMatchedStateSize()) {
@@ -791,7 +845,20 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
           currentStateMatchInfo.addMatchedState(matchedState);
 
           if (currentNode == node) {
-            return;
+            if (needFinalState && !currentStateMatchInfo.hasFinalState()) {
+              while (transitionIterator.hasNext()) {
+                matchedState = tryGetNextState(currentNode, sourceState, transitionIterator.next());
+                if (matchedState != null) {
+                  currentStateMatchInfo.addMatchedState(matchedState);
+                  if (matchedState.isFinal()) {
+                    return;
+                  }
+                }
+              }
+              currentNodeIndex--;
+            } else {
+              return;
+            }
           } else {
             currentNodeIndex++;
           }
@@ -834,4 +901,13 @@ public abstract class AbstractTreeVisitor<N extends ITreeNode, R>
       return null;
     }
   }
+
+  /**
+   * May node can be accepted if it reaches final state. Its implementation should not depend on the
+   * context.
+   *
+   * @param node node to be checked
+   * @return false is if node must not be accepted. Otherwise, return true.
+   */
+  protected abstract boolean mayTargetNodeType(N node);
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/Traverser.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/Traverser.java
index 9e588a1152f..9b66c9fb4ae 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/Traverser.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/Traverser.java
@@ -21,6 +21,8 @@ package org.apache.iotdb.db.metadata.mtree.traverser;
 import org.apache.iotdb.commons.exception.IllegalPathException;
 import org.apache.iotdb.commons.exception.MetadataException;
 import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.path.fa.IFAState;
+import org.apache.iotdb.commons.path.fa.IFATransition;
 import org.apache.iotdb.commons.schema.node.IMNode;
 import org.apache.iotdb.commons.schema.node.utils.IMNodeFactory;
 import org.apache.iotdb.commons.schema.node.utils.IMNodeIterator;
@@ -179,4 +181,63 @@ public abstract class Traverser<R, N extends IMNode<N>> extends AbstractTreeVisi
   public void setSkipPreDeletedSchema(boolean skipPreDeletedSchema) {
     this.skipPreDeletedSchema = skipPreDeletedSchema;
   }
+
+  @Override
+  protected IFAState tryGetNextState(
+      N node, IFAState sourceState, Map<String, IFATransition> preciseMatchTransitionMap) {
+    IFATransition transition;
+    IFAState state;
+    if (node.isMeasurement()) {
+      String alias = node.getAsMeasurementMNode().getAlias();
+      if (alias != null) {
+        transition = preciseMatchTransitionMap.get(alias);
+        if (transition != null) {
+          state = patternFA.getNextState(sourceState, transition);
+          if (state.isFinal()) {
+            return state;
+          }
+        }
+      }
+      transition = preciseMatchTransitionMap.get(node.getName());
+      if (transition != null) {
+        state = patternFA.getNextState(sourceState, transition);
+        if (state.isFinal()) {
+          return state;
+        }
+      }
+      return null;
+    }
+
+    transition = preciseMatchTransitionMap.get(node.getName());
+    if (transition == null) {
+      return null;
+    }
+    return patternFA.getNextState(sourceState, transition);
+  }
+
+  @Override
+  protected IFAState tryGetNextState(N node, IFAState sourceState, IFATransition transition) {
+    IFAState state;
+    if (node.isMeasurement()) {
+      String alias = node.getAsMeasurementMNode().getAlias();
+      if (alias != null && transition.isMatch(alias)) {
+        state = patternFA.getNextState(sourceState, transition);
+        if (state.isFinal()) {
+          return state;
+        }
+      }
+      if (transition.isMatch(node.getName())) {
+        state = patternFA.getNextState(sourceState, transition);
+        if (state.isFinal()) {
+          return state;
+        }
+      }
+      return null;
+    }
+
+    if (transition.isMatch(node.getName())) {
+      return patternFA.getNextState(sourceState, transition);
+    }
+    return null;
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/TraverserWithLimitOffsetWrapper.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/TraverserWithLimitOffsetWrapper.java
index c497885102e..6f71725f252 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/TraverserWithLimitOffsetWrapper.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/TraverserWithLimitOffsetWrapper.java
@@ -108,6 +108,11 @@ public class TraverserWithLimitOffsetWrapper<R, N extends IMNode<N>> extends Tra
     return null;
   }
 
+  @Override
+  protected boolean mayTargetNodeType(N node) {
+    return false;
+  }
+
   @Override
   public void close() {
     traverser.close();
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/DatabaseTraverser.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/DatabaseTraverser.java
index 7e5f704d40e..dbc4239b795 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/DatabaseTraverser.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/DatabaseTraverser.java
@@ -43,6 +43,11 @@ public abstract class DatabaseTraverser<R, N extends IMNode<N>> extends Traverse
     super(startNode, path, store, isPrefixMatch);
   }
 
+  @Override
+  protected boolean mayTargetNodeType(N node) {
+    return collectInternal || node.isDatabase();
+  }
+
   @Override
   protected boolean acceptFullMatchedNode(N node) {
     return node.isDatabase();
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/EntityTraverser.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/EntityTraverser.java
index 6ead59dad4e..5b6109aae63 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/EntityTraverser.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/EntityTraverser.java
@@ -43,6 +43,14 @@ public abstract class EntityTraverser<R, N extends IMNode<N>> extends Traverser<
     super(startNode, path, store, isPrefixMatch);
   }
 
+  @Override
+  protected boolean mayTargetNodeType(N node) {
+    if (node.isDevice()) {
+      return !usingTemplate || schemaTemplateId == node.getAsDeviceMNode().getSchemaTemplateId();
+    }
+    return false;
+  }
+
   @Override
   protected boolean acceptFullMatchedNode(N node) {
     if (node.isDevice()) {
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/MNodeTraverser.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/MNodeTraverser.java
index e2bcf315676..4b627a8e209 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/MNodeTraverser.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/MNodeTraverser.java
@@ -50,6 +50,11 @@ public abstract class MNodeTraverser<R, N extends IMNode<N>> extends Traverser<R
     super(startNode, path, store, isPrefixMatch);
   }
 
+  @Override
+  protected boolean mayTargetNodeType(N node) {
+    return true;
+  }
+
   @Override
   protected boolean acceptFullMatchedNode(N node) {
     if (targetLevel >= 0) {
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/MeasurementTraverser.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/MeasurementTraverser.java
index 6ced2e9cbba..c2b7dff8359 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/MeasurementTraverser.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/basic/MeasurementTraverser.java
@@ -41,6 +41,11 @@ public abstract class MeasurementTraverser<R, N extends IMNode<N>> extends Trave
     super(startNode, path, store, isPrefixMatch);
   }
 
+  @Override
+  protected boolean mayTargetNodeType(N node) {
+    return node.isMeasurement();
+  }
+
   @Override
   protected boolean acceptFullMatchedNode(N node) {
     return node.isMeasurement();
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
index 4e0ab4fb569..e70f39e72e9 100644
--- 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
@@ -35,6 +35,11 @@ public class SchemaTreeDeviceVisitor extends SchemaTreeVisitor<DeviceSchemaInfo>
     super(root, pathPattern, isPrefixMatch);
   }
 
+  @Override
+  protected boolean mayTargetNodeType(SchemaNode node) {
+    return node.isEntity();
+  }
+
   @Override
   protected boolean acceptInternalMatchedNode(SchemaNode node) {
     return false;
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
index 7221b1641c7..526bbc57865 100644
--- 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
@@ -37,6 +37,11 @@ public class SchemaTreeMeasurementVisitor extends SchemaTreeVisitor<MeasurementP
     tailNode = pathPattern.getTailNode();
   }
 
+  @Override
+  protected boolean mayTargetNodeType(SchemaNode node) {
+    return node.isMeasurement();
+  }
+
   @Override
   protected IFAState tryGetNextState(
       SchemaNode node, IFAState sourceState, Map<String, IFATransition> preciseMatchTransitionMap) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeVisitorWithLimitOffsetWrapper.java b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeVisitorWithLimitOffsetWrapper.java
index b9ab5a3fbc6..e63f0062531 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeVisitorWithLimitOffsetWrapper.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/common/schematree/visitor/SchemaTreeVisitorWithLimitOffsetWrapper.java
@@ -101,6 +101,12 @@ public class SchemaTreeVisitorWithLimitOffsetWrapper<R> extends SchemaTreeVisito
     return null;
   }
 
+  @Override
+  protected boolean mayTargetNodeType(SchemaNode node) {
+    // do nothing
+    return false;
+  }
+
   @Override
   public void reset() {
     visitor.reset();
diff --git a/server/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionBasicTest.java b/server/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionBasicTest.java
index 879884ca5c0..f7ea5e08c22 100644
--- a/server/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionBasicTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/schemaRegion/SchemaRegionBasicTest.java
@@ -48,6 +48,7 @@ import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -729,7 +730,7 @@ public class SchemaRegionBasicTest extends AbstractSchemaRegionTest {
         SchemaRegionTestUtil.showTimeseries(
             schemaRegion,
             SchemaRegionReadPlanFactory.getShowTimeSeriesPlan(new PartialPath("root.**")));
-    HashSet<String> expectedPathList =
+    Set<String> expectedPathList =
         new HashSet<>(
             Arrays.asList(
                 "root.laptop.d0",
@@ -740,7 +741,7 @@ public class SchemaRegionBasicTest extends AbstractSchemaRegionTest {
                 "root.laptop.d2.s2"));
     int expectedSize = 6;
     Assert.assertEquals(expectedSize, result.size());
-    HashSet<String> actualPathList = new HashSet<>();
+    Set<String> actualPathList = new HashSet<>();
     for (int index = 0; index < expectedSize; index++) {
       actualPathList.add(result.get(index).getFullPath());
     }
@@ -766,4 +767,102 @@ public class SchemaRegionBasicTest extends AbstractSchemaRegionTest {
     }
     Assert.assertEquals(expectedPathList, actualPathList);
   }
+
+  @Test
+  public void testGetMatchedDevicesWithSpecialPattern() throws Exception {
+    ISchemaRegion schemaRegion = getSchemaRegion("root.test", 0);
+
+    SchemaRegionTestUtil.createSimpleTimeseriesByList(
+        schemaRegion,
+        Arrays.asList("root.test.d1.s", "root.test.dac.device1.s", "root.test.dac.device1.d1.s"));
+
+    List<IDeviceSchemaInfo> expectedList =
+        Arrays.asList(
+            new ShowDevicesResult("root.test.d1", false),
+            new ShowDevicesResult("root.test.dac.device1", false),
+            new ShowDevicesResult("root.test.dac.device1.d1", false));
+    List<IDeviceSchemaInfo> actualResult =
+        SchemaRegionTestUtil.getMatchedDevices(
+            schemaRegion,
+            SchemaRegionReadPlanFactory.getShowDevicesPlan(new PartialPath("root.**.d*")));
+    // Compare hash sets because the order does not matter.
+    Set<IDeviceSchemaInfo> expectedHashset = new HashSet<>(expectedList);
+    Set<IDeviceSchemaInfo> actualHashset = new HashSet<>(actualResult);
+    Assert.assertEquals(expectedHashset, actualHashset);
+
+    List<ITimeSeriesSchemaInfo> result =
+        SchemaRegionTestUtil.showTimeseries(
+            schemaRegion,
+            SchemaRegionReadPlanFactory.getShowTimeSeriesPlan(new PartialPath("root.**.d*.*")));
+    Set<String> expectedPathList =
+        new HashSet<>(
+            Arrays.asList(
+                "root.test.d1.s", "root.test.dac.device1.s", "root.test.dac.device1.d1.s"));
+    int expectedSize = 3;
+    Assert.assertEquals(expectedSize, result.size());
+    Set<String> actualPathList = new HashSet<>();
+    for (int index = 0; index < expectedSize; index++) {
+      actualPathList.add(result.get(index).getFullPath());
+    }
+    Assert.assertEquals(expectedPathList, actualPathList);
+  }
+
+  @Test
+  public void testGetMatchedDevicesWithSpecialPattern2() throws Exception {
+    ISchemaRegion schemaRegion = getSchemaRegion("root.test", 0);
+
+    SchemaRegionTestUtil.createSimpleTimeseriesByList(
+        schemaRegion,
+        Arrays.asList(
+            "root.test.abc57.bcde22.def89.efg1",
+            "root.test.abc57.bcde22.def89.efg2",
+            "root.test.abc57.bcd22.def89.efg1",
+            "root.test.abc57.bcd22.def89.efg2"));
+
+    // case1: show devices root.**.*b*.*
+    List<IDeviceSchemaInfo> expectedList =
+        Arrays.asList(
+            new ShowDevicesResult("root.test.abc57.bcde22.def89", false),
+            new ShowDevicesResult("root.test.abc57.bcd22.def89", false));
+    List<IDeviceSchemaInfo> actualResult =
+        SchemaRegionTestUtil.getMatchedDevices(
+            schemaRegion,
+            SchemaRegionReadPlanFactory.getShowDevicesPlan(new PartialPath("root.**.*b*.*")));
+    // Compare hash sets because the order does not matter.
+    Set<IDeviceSchemaInfo> expectedHashset = new HashSet<>(expectedList);
+    Set<IDeviceSchemaInfo> actualHashset = new HashSet<>(actualResult);
+    Assert.assertEquals(expectedHashset, actualHashset);
+
+    // case2: show timeseries root.**.*e*.*e*
+    List<ITimeSeriesSchemaInfo> result =
+        SchemaRegionTestUtil.showTimeseries(
+            schemaRegion,
+            SchemaRegionReadPlanFactory.getShowTimeSeriesPlan(new PartialPath("root.**.*e*.*e*")));
+    Set<String> expectedPathList =
+        new HashSet<>(
+            Arrays.asList(
+                "root.test.abc57.bcde22.def89.efg1",
+                "root.test.abc57.bcde22.def89.efg2",
+                "root.test.abc57.bcd22.def89.efg1",
+                "root.test.abc57.bcd22.def89.efg2"));
+    int expectedSize = expectedPathList.size();
+    Assert.assertEquals(expectedSize, result.size());
+    Set<String> actualPathList = new HashSet<>();
+    for (int index = 0; index < expectedSize; index++) {
+      actualPathList.add(result.get(index).getFullPath());
+    }
+    Assert.assertEquals(expectedPathList, actualPathList);
+
+    // case3: show timeseries root.**.*e*
+    result =
+        SchemaRegionTestUtil.showTimeseries(
+            schemaRegion,
+            SchemaRegionReadPlanFactory.getShowTimeSeriesPlan(new PartialPath("root.**.*e*")));
+    Assert.assertEquals(expectedSize, result.size());
+    actualPathList = new HashSet<>();
+    for (int index = 0; index < expectedSize; index++) {
+      actualPathList.add(result.get(index).getFullPath());
+    }
+    Assert.assertEquals(expectedPathList, actualPathList);
+  }
 }