You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by qi...@apache.org on 2021/12/29 13:19:10 UTC

[iotdb] branch master updated: [IOTDB-2153][IOTDB-2157] fix incorrect path search space (#4581)

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 9d9e6b9  [IOTDB-2153][IOTDB-2157] fix incorrect path search space (#4581)
9d9e6b9 is described below

commit 9d9e6b994455baec7eabd49b417afef9dace0dd3
Author: Zhong Wang <wa...@alibaba-inc.com>
AuthorDate: Wed Dec 29 21:18:32 2021 +0800

    [IOTDB-2153][IOTDB-2157] fix incorrect path search space (#4581)
---
 .../apache/iotdb/cluster/metadata/CMManager.java   |  46 +++++----
 .../iotdb/cluster/query/ClusterPlanExecutor.java   |  26 +++--
 .../org/apache/iotdb/db/engine/StorageEngine.java  |  29 ++++--
 .../org/apache/iotdb/db/metadata/MManager.java     |   5 +-
 .../org/apache/iotdb/db/metadata/mtree/MTree.java  |   7 +-
 .../apache/iotdb/db/metadata/path/PartialPath.java | 113 +++++++++++++++++----
 .../apache/iotdb/db/metadata/PartialPathTest.java  |  55 +++++++---
 7 files changed, 209 insertions(+), 72 deletions(-)

diff --git a/cluster/src/main/java/org/apache/iotdb/cluster/metadata/CMManager.java b/cluster/src/main/java/org/apache/iotdb/cluster/metadata/CMManager.java
index d0d6a93..04f5e3d 100644
--- a/cluster/src/main/java/org/apache/iotdb/cluster/metadata/CMManager.java
+++ b/cluster/src/main/java/org/apache/iotdb/cluster/metadata/CMManager.java
@@ -920,7 +920,7 @@ public class CMManager extends MManager {
    * @return all paths after removing wildcards in the path
    */
   public Set<PartialPath> getMatchedDevices(PartialPath originPath) throws MetadataException {
-    Map<String, String> sgPathMap = groupPathByStorageGroup(originPath);
+    Map<String, List<PartialPath>> sgPathMap = groupPathByStorageGroup(originPath);
     Set<PartialPath> ret = getMatchedDevices(sgPathMap);
     logger.debug("The devices of path {} are {}", originPath, ret);
     return ret;
@@ -933,14 +933,14 @@ public class CMManager extends MManager {
    *     storage group added
    * @return a collection of all queried paths
    */
-  private List<MeasurementPath> getMatchedPaths(Map<String, String> sgPathMap, boolean withAlias)
-      throws MetadataException {
+  private List<MeasurementPath> getMatchedPaths(
+      Map<String, List<PartialPath>> sgPathMap, boolean withAlias) throws MetadataException {
     List<MeasurementPath> result = new ArrayList<>();
     // split the paths by the data group they belong to
     Map<PartitionGroup, List<String>> remoteGroupPathMap = new HashMap<>();
-    for (Entry<String, String> sgPathEntry : sgPathMap.entrySet()) {
+    for (Entry<String, List<PartialPath>> sgPathEntry : sgPathMap.entrySet()) {
       String storageGroupName = sgPathEntry.getKey();
-      PartialPath pathUnderSG = new PartialPath(sgPathEntry.getValue());
+      List<PartialPath> paths = sgPathEntry.getValue();
       // find the data group that should hold the timeseries schemas of the storage group
       PartitionGroup partitionGroup =
           metaGroupMember.getPartitionTable().route(storageGroupName, 0);
@@ -954,7 +954,10 @@ public class CMManager extends MManager {
         } catch (CheckConsistencyException e) {
           logger.warn("Failed to check consistency.", e);
         }
-        List<MeasurementPath> allTimeseriesName = getMatchedPathsLocally(pathUnderSG, withAlias);
+        List<MeasurementPath> allTimeseriesName = new ArrayList<>();
+        for (PartialPath path : paths) {
+          allTimeseriesName.addAll(getMatchedPathsLocally(path, withAlias));
+        }
         logger.debug(
             "{}: get matched paths of {} locally, result {}",
             metaGroupMember.getName(),
@@ -963,9 +966,11 @@ public class CMManager extends MManager {
         result.addAll(allTimeseriesName);
       } else {
         // batch the queries of the same group to reduce communication
-        remoteGroupPathMap
-            .computeIfAbsent(partitionGroup, p -> new ArrayList<>())
-            .add(pathUnderSG.getFullPath());
+        for (PartialPath path : paths) {
+          remoteGroupPathMap
+              .computeIfAbsent(partitionGroup, p -> new ArrayList<>())
+              .add(path.getFullPath());
+        }
       }
     }
 
@@ -1078,14 +1083,14 @@ public class CMManager extends MManager {
    *     queried with storage group added
    * @return a collection of all queried devices
    */
-  private Set<PartialPath> getMatchedDevices(Map<String, String> sgPathMap)
+  private Set<PartialPath> getMatchedDevices(Map<String, List<PartialPath>> sgPathMap)
       throws MetadataException {
     Set<PartialPath> result = new HashSet<>();
     // split the paths by the data group they belong to
     Map<PartitionGroup, List<String>> groupPathMap = new HashMap<>();
-    for (Entry<String, String> sgPathEntry : sgPathMap.entrySet()) {
+    for (Entry<String, List<PartialPath>> sgPathEntry : sgPathMap.entrySet()) {
       String storageGroupName = sgPathEntry.getKey();
-      PartialPath pathUnderSG = new PartialPath(sgPathEntry.getValue());
+      List<PartialPath> paths = sgPathEntry.getValue();
       // find the data group that should hold the timeseries schemas of the storage group
       PartitionGroup partitionGroup =
           metaGroupMember.getPartitionTable().route(storageGroupName, 0);
@@ -1099,7 +1104,10 @@ public class CMManager extends MManager {
         } catch (CheckConsistencyException e) {
           logger.warn("Failed to check consistency.", e);
         }
-        Set<PartialPath> allDevices = super.getMatchedDevices(pathUnderSG);
+        Set<PartialPath> allDevices = new HashSet<>();
+        for (PartialPath path : paths) {
+          allDevices.addAll(super.getMatchedDevices(path));
+        }
         logger.debug(
             "{}: get matched paths of {} locally, result {}",
             metaGroupMember.getName(),
@@ -1108,9 +1116,11 @@ public class CMManager extends MManager {
         result.addAll(allDevices);
       } else {
         // batch the queries of the same group to reduce communication
-        groupPathMap
-            .computeIfAbsent(partitionGroup, p -> new ArrayList<>())
-            .add(pathUnderSG.getFullPath());
+        for (PartialPath path : paths) {
+          groupPathMap
+              .computeIfAbsent(partitionGroup, p -> new ArrayList<>())
+              .add(path.getFullPath());
+        }
       }
     }
 
@@ -1198,7 +1208,7 @@ public class CMManager extends MManager {
   @Override
   public Pair<List<MeasurementPath>, Integer> getMeasurementPathsWithAlias(
       PartialPath pathPattern, int limit, int offset) throws MetadataException {
-    Map<String, String> sgPathMap = groupPathByStorageGroup(pathPattern);
+    Map<String, List<PartialPath>> sgPathMap = groupPathByStorageGroup(pathPattern);
     List<MeasurementPath> result = getMatchedPaths(sgPathMap, true);
 
     int skippedOffset = 0;
@@ -1229,7 +1239,7 @@ public class CMManager extends MManager {
    * @return all paths after removing wildcards in the path
    */
   public List<MeasurementPath> getMatchedPaths(PartialPath originPath) throws MetadataException {
-    Map<String, String> sgPathMap = groupPathByStorageGroup(originPath);
+    Map<String, List<PartialPath>> sgPathMap = groupPathByStorageGroup(originPath);
     List<MeasurementPath> ret = getMatchedPaths(sgPathMap, false);
     logger.debug("The paths of path {} are {}", originPath, ret);
     return ret;
diff --git a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPlanExecutor.java b/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPlanExecutor.java
index 3485cfd..a1ab986 100644
--- a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPlanExecutor.java
+++ b/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPlanExecutor.java
@@ -134,7 +134,7 @@ public class ClusterPlanExecutor extends PlanExecutor {
     } catch (CheckConsistencyException e) {
       throw new MetadataException(e);
     }
-    Map<String, String> sgPathMap = IoTDB.metaManager.groupPathByStorageGroup(path);
+    Map<String, List<PartialPath>> sgPathMap = IoTDB.metaManager.groupPathByStorageGroup(path);
     if (sgPathMap.isEmpty()) {
       throw new PathNotExistException(path.getFullPath());
     }
@@ -149,7 +149,7 @@ public class ClusterPlanExecutor extends PlanExecutor {
     return ret;
   }
 
-  private int getDeviceCount(Map<String, String> sgPathMap, PartialPath queryPath)
+  private int getDeviceCount(Map<String, List<PartialPath>> sgPathMap, PartialPath queryPath)
       throws CheckConsistencyException, MetadataException {
     AtomicInteger result = new AtomicInteger();
     // split the paths by the data group they belong to
@@ -311,7 +311,8 @@ public class ClusterPlanExecutor extends PlanExecutor {
     if (!wildcardPath.getMeasurement().equals(IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD)) {
       wildcardPath = wildcardPath.concatNode(IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD);
     }
-    Map<String, String> sgPathMap = IoTDB.metaManager.groupPathByStorageGroup(wildcardPath);
+    Map<String, List<PartialPath>> sgPathMap =
+        IoTDB.metaManager.groupPathByStorageGroup(wildcardPath);
     if (sgPathMap.isEmpty()) {
       return 0;
     }
@@ -370,14 +371,14 @@ public class ClusterPlanExecutor extends PlanExecutor {
    * @param level the max depth to match the pattern, -1 means matching the whole pattern
    * @return the number of paths that match the pattern at given level
    */
-  private int getPathCount(Map<String, String> sgPathMap, int level)
+  private int getPathCount(Map<String, List<PartialPath>> sgPathMap, int level)
       throws MetadataException, CheckConsistencyException {
     AtomicInteger result = new AtomicInteger();
     // split the paths by the data group they belong to
     Map<PartitionGroup, List<String>> groupPathMap = new HashMap<>();
-    for (Entry<String, String> sgPathEntry : sgPathMap.entrySet()) {
+    for (Entry<String, List<PartialPath>> sgPathEntry : sgPathMap.entrySet()) {
       String storageGroupName = sgPathEntry.getKey();
-      PartialPath pathUnderSG = new PartialPath(sgPathEntry.getValue());
+      List<PartialPath> paths = sgPathEntry.getValue();
       // find the data group that should hold the timeseries schemas of the storage group
       PartitionGroup partitionGroup =
           metaGroupMember.getPartitionTable().route(storageGroupName, 0);
@@ -387,7 +388,10 @@ public class ClusterPlanExecutor extends PlanExecutor {
         metaGroupMember
             .getLocalDataMember(partitionGroup.getHeader(), partitionGroup.getRaftId())
             .syncLeaderWithConsistencyCheck(false);
-        int localResult = getLocalPathCount(pathUnderSG, level);
+        int localResult = 0;
+        for (PartialPath path : paths) {
+          localResult += getLocalPathCount(path, level);
+        }
         logger.debug(
             "{}: get path count of {} locally, result {}",
             metaGroupMember.getName(),
@@ -396,9 +400,11 @@ public class ClusterPlanExecutor extends PlanExecutor {
         result.addAndGet(localResult);
       } else {
         // batch the queries of the same group to reduce communication
-        groupPathMap
-            .computeIfAbsent(partitionGroup, p -> new ArrayList<>())
-            .add(pathUnderSG.getFullPath());
+        for (PartialPath path : paths) {
+          groupPathMap
+              .computeIfAbsent(partitionGroup, p -> new ArrayList<>())
+              .add(path.getFullPath());
+        }
       }
     }
     if (groupPathMap.isEmpty()) {
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/StorageEngine.java b/server/src/main/java/org/apache/iotdb/db/engine/StorageEngine.java
index f208490..744cc2d 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/StorageEngine.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/StorageEngine.java
@@ -33,7 +33,14 @@ import org.apache.iotdb.db.engine.storagegroup.TsFileResource;
 import org.apache.iotdb.db.engine.storagegroup.VirtualStorageGroupProcessor;
 import org.apache.iotdb.db.engine.storagegroup.VirtualStorageGroupProcessor.TimePartitionFilter;
 import org.apache.iotdb.db.engine.storagegroup.virtualSg.StorageGroupManager;
-import org.apache.iotdb.db.exception.*;
+import org.apache.iotdb.db.exception.BatchProcessException;
+import org.apache.iotdb.db.exception.LoadFileException;
+import org.apache.iotdb.db.exception.ShutdownException;
+import org.apache.iotdb.db.exception.StorageEngineException;
+import org.apache.iotdb.db.exception.StorageGroupProcessorException;
+import org.apache.iotdb.db.exception.TsFileProcessorException;
+import org.apache.iotdb.db.exception.WriteProcessException;
+import org.apache.iotdb.db.exception.WriteProcessRejectException;
 import org.apache.iotdb.db.exception.metadata.IllegalPathException;
 import org.apache.iotdb.db.exception.metadata.MetadataException;
 import org.apache.iotdb.db.exception.metadata.StorageGroupNotSetException;
@@ -722,10 +729,12 @@ public class StorageEngine implements IService {
           continue;
         }
 
-        PartialPath newPath = path.alterPrefixPath(storageGroupPath);
-        processorMap
-            .get(storageGroupPath)
-            .delete(newPath, startTime, endTime, planIndex, timePartitionFilter);
+        List<PartialPath> possiblePaths = path.alterPrefixPath(storageGroupPath);
+        for (PartialPath possiblePath : possiblePaths) {
+          processorMap
+              .get(storageGroupPath)
+              .delete(possiblePath, startTime, endTime, planIndex, timePartitionFilter);
+        }
       }
     } catch (IOException | MetadataException e) {
       throw new StorageEngineException(e.getMessage());
@@ -744,10 +753,12 @@ public class StorageEngine implements IService {
           continue;
         }
 
-        PartialPath newPath = path.alterPrefixPath(storageGroupPath);
-        processorMap
-            .get(storageGroupPath)
-            .delete(newPath, Long.MIN_VALUE, Long.MAX_VALUE, planIndex, timePartitionFilter);
+        List<PartialPath> possiblePaths = path.alterPrefixPath(storageGroupPath);
+        for (PartialPath possiblePath : possiblePaths) {
+          processorMap
+              .get(storageGroupPath)
+              .delete(possiblePath, Long.MIN_VALUE, Long.MAX_VALUE, planIndex, timePartitionFilter);
+        }
       }
     } catch (IOException | MetadataException e) {
       throw new StorageEngineException(e.getMessage());
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
index 2bf02f4..16acda2 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
@@ -1642,8 +1642,9 @@ public class MManager {
    * @return StorageGroupName-FullPath pairs
    * @apiNote :for cluster
    */
-  public Map<String, String> groupPathByStorageGroup(PartialPath path) throws MetadataException {
-    Map<String, String> sgPathMap = mtree.groupPathByStorageGroup(path);
+  public Map<String, List<PartialPath>> groupPathByStorageGroup(PartialPath path)
+      throws MetadataException {
+    Map<String, List<PartialPath>> sgPathMap = mtree.groupPathByStorageGroup(path);
     if (logger.isDebugEnabled()) {
       logger.debug("The storage groups of path {} are {}", path, sgPathMap.keySet());
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTree.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTree.java
index 13e43da..d9f6734 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTree.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTree.java
@@ -868,14 +868,15 @@ public class MTree implements Serializable {
    * storage group using the children of a mNode. If one child is a storage group node, put a
    * storageGroupName-fullPath pair into paths.
    */
-  public Map<String, String> groupPathByStorageGroup(PartialPath path) throws MetadataException {
-    Map<String, String> result = new HashMap<>();
+  public Map<String, List<PartialPath>> groupPathByStorageGroup(PartialPath path)
+      throws MetadataException {
+    Map<String, List<PartialPath>> result = new HashMap<>();
     StorageGroupCollector<Map<String, String>> collector =
         new StorageGroupCollector<Map<String, String>>(root, path) {
           @Override
           protected void collectStorageGroup(IStorageGroupMNode node) {
             PartialPath sgPath = node.getPartialPath();
-            result.put(sgPath.getFullPath(), path.alterPrefixPath(sgPath).getFullPath());
+            result.put(sgPath.getFullPath(), path.alterPrefixPath(sgPath));
           }
         };
     collector.setCollectInternal(true);
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/path/PartialPath.java b/server/src/main/java/org/apache/iotdb/db/metadata/path/PartialPath.java
index 405f214..6dbee2f 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/path/PartialPath.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/path/PartialPath.java
@@ -41,6 +41,7 @@ import org.apache.iotdb.tsfile.utils.Pair;
 import org.apache.iotdb.tsfile.write.schema.IMeasurementSchema;
 import org.apache.iotdb.tsfile.write.writer.RestorableTsFileIOWriter;
 
+import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,6 +52,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import static org.apache.iotdb.db.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD;
 import static org.apache.iotdb.db.conf.IoTDBConstant.ONE_LEVEL_PATH_WILDCARD;
@@ -145,27 +147,104 @@ public class PartialPath extends Path implements Comparable<Path>, Cloneable {
   }
 
   /**
-   * Construct a new PartialPath by resetting the prefix nodes with prefixPath. If the prefix nodes
-   * contains **, then the prefixPath will be set before **. For example, give this = root.**.d and
-   * prefixPath = root.a.sg, then the result will be root.a.sg.**.d
+   * Return the intersection of paths starting with the given prefix and paths matching this path
+   * pattern.
    *
-   * @param prefixPath the prefix path used to replace current nodes
-   * @return A new PartialPath with altered prefix
+   * <p>For example, minimizing "root.**.b.c" with prefix "root.a.b" produces "root.a.b.c",
+   * "root.a.b.b.c", "root.a.b.**.b.c", since the multi-level wildcard can match 'a', 'a.b', and any
+   * other sub paths start with 'a.b'.
+   *
+   * <p>The goal of this method is to reduce the search space when querying a storage group with a
+   * path with wildcard.
+   *
+   * @param prefix The prefix. Cannot be null and cannot contain any wildcard.
    */
-  public PartialPath alterPrefixPath(PartialPath prefixPath) {
-    int newLength = Math.max(nodes.length, prefixPath.getNodeLength());
-    int startIndex = Math.min(nodes.length, prefixPath.getNodeLength());
-    for (int i = 0; i < startIndex; i++) {
-      if (nodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)) {
-        newLength += startIndex - i;
-        startIndex = i;
-        break;
+  public List<PartialPath> alterPrefixPath(PartialPath prefix) {
+    Validate.notNull(prefix);
+
+    // Make sure the prefix path doesn't contain any wildcard.
+    for (String node : prefix.getNodes()) {
+      if (MULTI_LEVEL_PATH_WILDCARD.equals(node) || ONE_LEVEL_PATH_WILDCARD.equals(node)) {
+        throw new IllegalArgumentException(
+            "Wildcards are not allowed in the prefix path: " + prefix.getFullPath());
       }
     }
-    String[] newNodes = Arrays.copyOf(prefixPath.getNodes(), newLength);
-    System.arraycopy(
-        nodes, startIndex, newNodes, prefixPath.getNodeLength(), nodes.length - startIndex);
-    return new PartialPath(newNodes);
+
+    List<List<String>> results = new ArrayList<>();
+    alterPrefixPathInternal(
+        Arrays.asList(prefix.getNodes()), Arrays.asList(nodes), new ArrayList<>(), results);
+    return results.stream()
+        .map(r -> new PartialPath(r.toArray(new String[0])))
+        .collect(Collectors.toList());
+  }
+
+  /**
+   * Let PRE stands for the remaining prefix nodes, PATH for the remaining path nodes, and C for the
+   * path being processed. And assume the size of the prefix is i and that of this path is j.
+   *
+   * <p>If the first node of the path is not '**' and it matches the first node of the path. The
+   * optimality equation is,
+   *
+   * <p>O(PRE(0, i), PATH(0, j), C) = O(PRE(1, i), PATH(1, j), [PRE(0)])
+   *
+   * <p>If the first node of the path is '**', it may match the first 1, 2, ... i nodes of the
+   * prefix. '**' may match the all the remaining nodes of the prefix, as well as some additional
+   * levels. Thus, the optimality equation is,
+   *
+   * <p>O(PRE(0, i), PATH(0, j), C) = { O(PRE(1, i), PATH(1, j), [PRE(0)]), ... , O([], PATH(1, j),
+   * C + PRE(0) + ... + PRE(i-1)), O([], PATH(0, j), C + PRE(0) + ... + PRE(i-1))}
+   *
+   * <p>And when all the prefix nodes are matched,
+   *
+   * <p>O([], PATH, C) = C + PATH
+   *
+   * <p>The algorithm above takes all the possible matches of a multi-level wildcard into account.
+   * Thus, it produces the correct result.
+   *
+   * <p>To prevent overlapping among the final results, this algorithm stops when the prefix has no
+   * remaining node and the first remaining node of the path is '**'. This strategy works because
+   * this algorithm will always find the path with the least levels in the search space.
+   *
+   * @param prefix The remaining nodes of the prefix.
+   * @param path The remaining nodes of the path.
+   * @param current The path being processed.
+   * @param results The final results.
+   * @return True if the search should be stopped, else false.
+   */
+  private boolean alterPrefixPathInternal(
+      List<String> prefix, List<String> path, List<String> current, List<List<String>> results) {
+    if (prefix.isEmpty()) {
+      current.addAll(path);
+      results.add(current);
+      return !path.isEmpty() && MULTI_LEVEL_PATH_WILDCARD.equals(path.get(0));
+    }
+    if (path.isEmpty()) {
+      return false;
+    }
+
+    if (MULTI_LEVEL_PATH_WILDCARD.equals(path.get(0))) {
+      // Wildcard matches part of or the whole the prefix.
+      for (int j = 1; j <= prefix.size(); j++) {
+        List<String> copy = new ArrayList<>(current);
+        copy.addAll(prefix.subList(0, j));
+        if (alterPrefixPathInternal(
+            prefix.subList(j, prefix.size()), path.subList(1, path.size()), copy, results)) {
+          return true;
+        }
+      }
+      // Wildcard matches the prefix and some more levels.
+      List<String> copy = new ArrayList<>(current);
+      copy.addAll(prefix);
+      return alterPrefixPathInternal(
+          Collections.emptyList(), path.subList(0, path.size()), copy, results);
+    } else if (ONE_LEVEL_PATH_WILDCARD.equals(path.get(0)) || prefix.get(0).equals(path.get(0))) {
+      // No need to make a copy.
+      current.add(prefix.get(0));
+      return alterPrefixPathInternal(
+          prefix.subList(1, prefix.size()), path.subList(1, path.size()), current, results);
+    }
+
+    return false;
   }
 
   /**
diff --git a/server/src/test/java/org/apache/iotdb/db/metadata/PartialPathTest.java b/server/src/test/java/org/apache/iotdb/db/metadata/PartialPathTest.java
index c1cc619..fcde71c 100644
--- a/server/src/test/java/org/apache/iotdb/db/metadata/PartialPathTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/PartialPathTest.java
@@ -82,19 +82,48 @@ public class PartialPathTest {
 
   @Test
   public void testAlterPrefixPath() throws IllegalPathException {
-    PartialPath p1 = new PartialPath("root.sg1.d1.s1");
-    PartialPath p2 = p1.alterPrefixPath(new PartialPath("root.sg2"));
-    PartialPath p3 = p1.alterPrefixPath(new PartialPath("root.sg2.d1.d2.s3"));
-
-    Assert.assertEquals("root.sg2.d1.s1", p2.getFullPath());
-    Assert.assertEquals("root.sg2.d1.d2.s3", p3.getFullPath());
-
-    PartialPath p4 = new PartialPath("root.**.d.s");
-    PartialPath p5 = p4.alterPrefixPath(new PartialPath("root.sg"));
-    Assert.assertEquals("root.sg.**.d.s", p5.getFullPath());
-    PartialPath p6 = new PartialPath("root.a.**.d.s");
-    PartialPath p7 = p5.alterPrefixPath(new PartialPath("root.b.sg"));
-    Assert.assertEquals("root.b.sg.**.d.s", p7.getFullPath());
+    // Plain path.
+    PartialPath p = new PartialPath("root.a.b.c");
+    List<PartialPath> results = p.alterPrefixPath(new PartialPath("root.a.b"));
+    Assert.assertEquals(results.toString(), 1, results.size());
+    Assert.assertEquals("root.a.b.c", results.get(0).getFullPath());
+
+    // Path with single level wildcard.
+    p = new PartialPath("root.*.b.c");
+    results = p.alterPrefixPath(new PartialPath("root.a.b"));
+    Assert.assertEquals(results.toString(), 1, results.size());
+    Assert.assertEquals("root.a.b.c", results.get(0).getFullPath());
+
+    // Path with multi level wildcard.
+    p = new PartialPath("root.**.b.c");
+    results = p.alterPrefixPath(new PartialPath("root.a.b"));
+    Assert.assertEquals(results.toString(), 3, results.size());
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.a.b.c")));
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.a.b.b.c")));
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.a.b.**.b.c")));
+
+    p = new PartialPath("root.**");
+    results = p.alterPrefixPath(new PartialPath("root.a.b"));
+    Assert.assertEquals(results.toString(), 2, results.size());
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.a.b")));
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.a.b.**")));
+
+    p = new PartialPath("root.**.b.**");
+    results = p.alterPrefixPath(new PartialPath("root.a.b.c"));
+    Assert.assertEquals(results.toString(), 2, results.size());
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.a.b.c")));
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.a.b.c.**")));
+
+    p = new PartialPath("root.**.b.**.b");
+    results = p.alterPrefixPath(new PartialPath("root.b.b.b"));
+    Assert.assertEquals(results.toString(), 2, results.size());
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.b.b.b.b")));
+    Assert.assertTrue(results.toString(), results.contains(new PartialPath("root.b.b.b.**.b")));
+
+    // Path cannot be altered.
+    p = new PartialPath("root.b.c.**");
+    results = p.alterPrefixPath(new PartialPath("root.a.b.c"));
+    Assert.assertEquals(results.toString(), 0, results.size());
   }
 
   @Test