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

haonan 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 56ffbcf  [ISSUE-2484] Fix creating timeseries error by using "create" 
or "insert" statement (#2468)
56ffbcf is described below

commit 56ffbcf77ec59e5c3065a81a1a2086178b94cecd
Author: Al Wei <[email protected]>
AuthorDate: Wed Feb 24 09:24:40 2021 +0800

    [ISSUE-2484] Fix creating timeseries error by using "create" or "insert" 
statement (#2468)
    
    Co-authored-by: weizihan0110 <[email protected]>
---
 .../apache/iotdb/cluster/metadata/CMManager.java   |   9 +-
 .../org/apache/iotdb/db/conf/IoTDBDescriptor.java  |   2 +-
 .../org/apache/iotdb/db/metadata/MManager.java     |  62 ++++----
 .../java/org/apache/iotdb/db/metadata/MTree.java   |  26 ++--
 .../org/apache/iotdb/db/metadata/MetaUtils.java    |  37 +++++
 .../org/apache/iotdb/db/metadata/mnode/MNode.java  |  49 ++++++-
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    |   2 +-
 .../db/integration/IoTDBAutoCreateSchemaIT.java    | 106 ++++++++++++++
 .../db/integration/IoTDBCreateTimeseriesIT.java    | 157 +++++++++++++++++++++
 .../org/apache/iotdb/db/metadata/MTreeTest.java    |  27 ++++
 .../apache/iotdb/db/metadata/MetaUtilsTest.java    |  35 +++++
 .../apache/iotdb/db/metadata/mnode/MNodeTest.java  |  71 ++++++++++
 12 files changed, 529 insertions(+), 54 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 e3a58f1..c5094c8 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
@@ -1423,13 +1423,12 @@ public class CMManager extends MManager {
   }
 
   @Override
-  protected MeasurementMNode getMeasurementMNode(MNode deviceMNode, String 
measurement) {
-    MNode child;
-    child = deviceMNode.getChild(measurement);
+  public MNode getMNode(MNode deviceMNode, String measurementName) {
+    MNode child = deviceMNode.getChild(measurementName);
     if (child == null) {
-      child = 
mRemoteMetaCache.get(deviceMNode.getPartialPath().concatNode(measurement));
+      child = 
mRemoteMetaCache.get(deviceMNode.getPartialPath().concatNode(measurementName));
     }
-    return child != null ? (MeasurementMNode) child : null;
+    return child;
   }
 
   public List<ShowTimeSeriesResult> showLocalTimeseries(
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java 
b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
index a1a4e58..f26a1b5 100644
--- a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
+++ b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
@@ -1146,7 +1146,7 @@ public class IoTDBDescriptor {
   }
 
   /** Get default encode algorithm by data type */
-  public TSEncoding getDefualtEncodingByType(TSDataType dataType) {
+  public TSEncoding getDefaultEncodingByType(TSDataType dataType) {
     switch (dataType) {
       case BOOLEAN:
         return conf.getDefaultBooleanEncoding();
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 6c7e5de..844d023 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
@@ -470,7 +470,12 @@ public class MManager {
       createTimeseries(
           new CreateTimeSeriesPlan(path, dataType, encoding, compressor, 
props, null, null, null));
     } catch (PathAlreadyExistException | AliasAlreadyExistException e) {
-      // ignore
+      if (logger.isDebugEnabled()) {
+        logger.debug(
+            "Ignore PathAlreadyExistException and AliasAlreadyExistException 
when Concurrent inserting"
+                + " a non-exist time series {}",
+            path);
+      }
     }
   }
 
@@ -991,10 +996,6 @@ public class MManager {
     return res;
   }
 
-  protected MeasurementMNode getMeasurementMNode(MNode deviceMNode, String 
measurement) {
-    return (MeasurementMNode) deviceMNode.getChild(measurement);
-  }
-
   public MeasurementSchema getSeriesSchema(PartialPath device, String 
measurement)
       throws MetadataException {
     MNode node = mtree.getNodeByPath(device);
@@ -1828,28 +1829,26 @@ public class MManager {
     MNode deviceMNode = getDeviceNodeWithAutoCreate(deviceId);
 
     // 2. get schema of each measurement
+    // if do not has measurement
+    MeasurementMNode measurementMNode;
+    TSDataType dataType;
     for (int i = 0; i < measurementList.length; i++) {
       try {
-        // if do not has measurement
-        MeasurementMNode measurementMNode;
-        if (!deviceMNode.hasChild(measurementList[i])) {
-          // could not create it
+        MNode child = getMNode(deviceMNode, measurementList[i]);
+        if (child instanceof MeasurementMNode) {
+          measurementMNode = (MeasurementMNode) child;
+        } else if (child instanceof StorageGroupMNode) {
+          throw new PathAlreadyExistException(deviceId + PATH_SEPARATOR + 
measurementList[i]);
+        } else {
           if (!config.isAutoCreateSchemaEnabled()) {
-            // but measurement not in MTree and cannot auto-create, try the 
cache
-            measurementMNode = getMeasurementMNode(deviceMNode, 
measurementList[i]);
-            if (measurementMNode == null) {
-              throw new PathNotExistException(deviceId + PATH_SEPARATOR + 
measurementList[i]);
-            }
+            throw new PathNotExistException(deviceId + PATH_SEPARATOR + 
measurementList[i]);
           } else {
-            // create it
-
-            TSDataType dataType = getTypeInLoc(plan, i);
+            // child is null or child is type of MNode
+            dataType = getTypeInLoc(plan, i);
             // create it, may concurrent created by multiple thread
             internalCreateTimeseries(deviceId.concatNode(measurementList[i]), 
dataType);
             measurementMNode = (MeasurementMNode) 
deviceMNode.getChild(measurementList[i]);
           }
-        } else {
-          measurementMNode = getMeasurementMNode(deviceMNode, 
measurementList[i]);
         }
 
         // check type is match
@@ -1906,24 +1905,19 @@ public class MManager {
     return deviceMNode;
   }
 
+  public MNode getMNode(MNode deviceMNode, String measurementName) {
+    return deviceMNode.getChild(measurementName);
+  }
+
   /** create timeseries with ignore PathAlreadyExistException */
   private void internalCreateTimeseries(PartialPath path, TSDataType dataType)
       throws MetadataException {
-    try {
-      createTimeseries(
-          path,
-          dataType,
-          getDefaultEncoding(dataType),
-          TSFileDescriptor.getInstance().getConfig().getCompressor(),
-          Collections.emptyMap());
-    } catch (PathAlreadyExistException | AliasAlreadyExistException e) {
-      if (logger.isDebugEnabled()) {
-        logger.debug(
-            "Ignore PathAlreadyExistException and AliasAlreadyExistException 
when Concurrent inserting"
-                + " a non-exist time series {}",
-            path);
-      }
-    }
+    createTimeseries(
+        path,
+        dataType,
+        getDefaultEncoding(dataType),
+        TSFileDescriptor.getInstance().getConfig().getCompressor(),
+        Collections.emptyMap());
   }
 
   /** get dataType of plan, in loc measurements only support InsertRowPlan and 
InsertTabletPlan */
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MTree.java 
b/server/src/main/java/org/apache/iotdb/db/metadata/MTree.java
index f628f86..a9bc505 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MTree.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MTree.java
@@ -214,23 +214,33 @@ public class MTree implements Serializable {
     // synchronize check and add, we need addChild and add Alias become atomic 
operation
     // only write on mtree will be synchronized
     synchronized (this) {
-      if (cur.hasChild(leafName)) {
+      MNode child = cur.getChild(leafName);
+      if (child instanceof MeasurementMNode || child instanceof 
StorageGroupMNode) {
         throw new PathAlreadyExistException(path.getFullPath());
       }
-      if (alias != null && cur.hasChild(alias)) {
-        throw new AliasAlreadyExistException(path.getFullPath(), alias);
+
+      if (alias != null) {
+        MNode childByAlias = cur.getChild(alias);
+        if (childByAlias instanceof MeasurementMNode) {
+          throw new AliasAlreadyExistException(path.getFullPath(), alias);
+        }
       }
-      MeasurementMNode leaf =
-          new MeasurementMNode(cur, leafName, alias, dataType, encoding, 
compressor, props);
 
-      cur.addChild(leafName, leaf);
+      // this measurementMNode could be a leaf or not.
+      MeasurementMNode measurementMNode =
+          new MeasurementMNode(cur, leafName, alias, dataType, encoding, 
compressor, props);
+      if (child != null) {
+        cur.replaceChild(measurementMNode.getName(), measurementMNode);
+      } else {
+        cur.addChild(leafName, measurementMNode);
+      }
 
       // link alias to LeafMNode
       if (alias != null) {
-        cur.addAlias(alias, leaf);
+        cur.addAlias(alias, measurementMNode);
       }
 
-      return leaf;
+      return measurementMNode;
     }
   }
 
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MetaUtils.java 
b/server/src/main/java/org/apache/iotdb/db/metadata/MetaUtils.java
index 61c8bec..7b9fe11 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MetaUtils.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MetaUtils.java
@@ -21,9 +21,14 @@ package org.apache.iotdb.db.metadata;
 import org.apache.iotdb.db.conf.IoTDBConstant;
 import org.apache.iotdb.db.exception.metadata.IllegalPathException;
 import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.metadata.mnode.MNode;
+import org.apache.iotdb.db.utils.TestOnly;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import static org.apache.iotdb.db.conf.IoTDBConstant.PATH_WILDCARD;
 
@@ -100,4 +105,36 @@ public class MetaUtils {
     System.arraycopy(nodeNames, 0, storageGroupNodes, 0, level + 1);
     return new PartialPath(storageGroupNodes);
   }
+
+  @TestOnly
+  public static List<String> getMultiFullPaths(MNode node) {
+    if (node == null) {
+      return Collections.emptyList();
+    }
+
+    List<MNode> lastNodeList = new ArrayList<>();
+    collectLastNode(node, lastNodeList);
+
+    List<String> result = new ArrayList<>();
+    for (MNode mNode : lastNodeList) {
+      result.add(mNode.getFullPath());
+    }
+
+    return result;
+  }
+
+  @TestOnly
+  public static void collectLastNode(MNode node, List<MNode> lastNodeList) {
+    if (node != null) {
+      Map<String, MNode> children = node.getChildren();
+      if (children.isEmpty()) {
+        lastNodeList.add(node);
+      }
+
+      for (Entry<String, MNode> entry : children.entrySet()) {
+        MNode childNode = entry.getValue();
+        collectLastNode(childNode, lastNodeList);
+      }
+    }
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MNode.java 
b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MNode.java
index 0f898c8..23f201c 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MNode.java
@@ -31,7 +31,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 
 /**
  * This class is the implementation of Metadata Node. One MNode instance 
represents one node in the
@@ -54,13 +53,19 @@ public class MNode implements Serializable {
   /**
    * use in Measurement Node so it's protected suppress warnings reason: 
volatile for double
    * synchronized check
+   *
+   * <p>This will be a ConcurrentHashMap instance
    */
   @SuppressWarnings("squid:S3077")
-  protected transient volatile ConcurrentMap<String, MNode> children = null;
+  protected transient volatile Map<String, MNode> children = null;
 
-  /** suppress warnings reason: volatile for double synchronized check */
+  /**
+   * suppress warnings reason: volatile for double synchronized check
+   *
+   * <p>This will be a ConcurrentHashMap instance
+   */
   @SuppressWarnings("squid:S3077")
-  private transient volatile ConcurrentMap<String, MNode> aliasChildren = null;
+  private transient volatile Map<String, MNode> aliasChildren = null;
 
   /** Constructor of MNode. */
   public MNode(MNode parent, String name) {
@@ -207,10 +212,21 @@ public class MNode implements Serializable {
     return children;
   }
 
-  public void setChildren(ConcurrentMap<String, MNode> children) {
+  public Map<String, MNode> getAliasChildren() {
+    if (aliasChildren == null) {
+      return Collections.emptyMap();
+    }
+    return aliasChildren;
+  }
+
+  public void setChildren(Map<String, MNode> children) {
     this.children = children;
   }
 
+  private void setAliasChildren(Map<String, MNode> aliasChildren) {
+    this.aliasChildren = aliasChildren;
+  }
+
   public String getName() {
     return name;
   }
@@ -233,4 +249,27 @@ public class MNode implements Serializable {
       entry.getValue().serializeTo(logWriter);
     }
   }
+
+  public void replaceChild(String measurement, MNode newChildNode) {
+    MNode oldChildNode = this.getChild(measurement);
+    if (oldChildNode == null) {
+      return;
+    }
+
+    // newChildNode builds parent-child relationship
+    Map<String, MNode> grandChildren = oldChildNode.getChildren();
+    newChildNode.setChildren(grandChildren);
+    grandChildren.forEach(
+        (grandChildName, grandChildNode) -> 
grandChildNode.setParent(newChildNode));
+
+    Map<String, MNode> grandAliasChildren = oldChildNode.getAliasChildren();
+    newChildNode.setAliasChildren(grandAliasChildren);
+    grandAliasChildren.forEach(
+        (grandAliasChildName, grandAliasChild) -> 
grandAliasChild.setParent(newChildNode));
+
+    newChildNode.setParent(this);
+
+    this.deleteChild(measurement);
+    this.addChild(newChildNode.getName(), newChildNode);
+  }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java 
b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
index 1c100fa..83a6802 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
@@ -1907,7 +1907,7 @@ public class IoTDBSqlVisitor extends 
SqlBaseBaseVisitor<Operator> {
     createTimeSeriesOperator.setDataType(tsDataType);
 
     final IoTDBDescriptor ioTDBDescriptor = IoTDBDescriptor.getInstance();
-    TSEncoding encoding = ioTDBDescriptor.getDefualtEncodingByType(tsDataType);
+    TSEncoding encoding = ioTDBDescriptor.getDefaultEncodingByType(tsDataType);
     if (Objects.nonNull(ctx.encoding())) {
       String encodingString = 
ctx.encoding().getChild(0).getText().toUpperCase();
       encoding = TSEncoding.valueOf(encodingString);
diff --git 
a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBAutoCreateSchemaIT.java
 
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBAutoCreateSchemaIT.java
index 8f9b7ce..d1651e3 100644
--- 
a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBAutoCreateSchemaIT.java
+++ 
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBAutoCreateSchemaIT.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.db.integration;
 import org.apache.iotdb.db.constant.TestConstant;
 import org.apache.iotdb.db.utils.EnvironmentUtils;
 import org.apache.iotdb.jdbc.Config;
+import org.apache.iotdb.jdbc.IoTDBSQLException;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -33,22 +34,34 @@ import java.sql.DatabaseMetaData;
 import java.sql.DriverManager;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Notice that, all test begins with "IoTDB" is integration test. All test 
which will start the
  * IoTDB server should be defined as integration test.
  */
 public class IoTDBAutoCreateSchemaIT {
+  private Statement statement;
+  private Connection connection;
 
   @Before
   public void setUp() {
     EnvironmentUtils.closeStatMonitor();
     EnvironmentUtils.envSetUp();
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    connection = DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
+    statement = connection.createStatement();
   }
 
   @After
   public void tearDown() throws Exception {
+    statement.close();
+    connection.close();
     EnvironmentUtils.cleanEnv();
   }
 
@@ -141,4 +154,97 @@ public class IoTDBAutoCreateSchemaIT {
       e.printStackTrace();
     }
   }
+
+  /**
+   * insert data when the time series that is a prefix path of an existing 
time series hasn't been
+   * created
+   */
+  @Test
+  public void testInsertAutoCreate1() throws Exception {
+    String[] timeSeriesArray = {"root.sg1.a.a", "root.sg1.a", 
"root.sg1.a.a.a"};
+
+    for (String timeSeries : timeSeriesArray) {
+      statement.execute(
+          String.format("INSERT INTO %s(timestamp, a) values(123, \"aabb\")", 
timeSeries));
+    }
+
+    // ensure that insert data in cache is right.
+    insertAutoCreate1Tool();
+
+    EnvironmentUtils.stopDaemon();
+    setUp();
+
+    // ensure that insert data in cache is right after recovering.
+    insertAutoCreate1Tool();
+  }
+
+  private void insertAutoCreate1Tool() throws SQLException {
+    boolean hasResult = statement.execute("select * from root.sg1");
+    Assert.assertTrue(hasResult);
+
+    Set<String> strSet = new HashSet<>();
+    String[] valueList = {};
+    try (ResultSet resultSet = statement.getResultSet()) {
+      while (resultSet.next()) {
+        valueList =
+            new String[] {
+              resultSet.getString("root.sg1.a.a"),
+              resultSet.getString("root.sg1.a.a.a"),
+              resultSet.getString("root.sg1.a.a.a.a")
+            };
+        strSet = new HashSet<>(Arrays.asList(valueList));
+      }
+    }
+    Assert.assertEquals(3, valueList.length);
+    Assert.assertEquals(1, strSet.size());
+    Assert.assertTrue(strSet.contains("aabb"));
+  }
+
+  /**
+   * test if automatically creating a time series will cause the storage group 
with same name to
+   * disappear
+   */
+  @Test
+  public void testInsertAutoCreate2() throws Exception {
+    String storageGroup = "root.sg2.a.b.c";
+    String timeSeriesPrefix = "root.sg2.a.b";
+
+    statement.execute(String.format("SET storage group TO %s", storageGroup));
+    try {
+      statement.execute(
+          String.format("INSERT INTO %s(timestamp, c) values(123, \"aabb\")", 
timeSeriesPrefix));
+    } catch (IoTDBSQLException ignored) {
+    }
+
+    // ensure that current storage group in cache is right.
+    InsertAutoCreate2Tool(storageGroup, timeSeriesPrefix);
+
+    EnvironmentUtils.stopDaemon();
+    setUp();
+
+    // ensure that storage group in cache is right after recovering.
+    InsertAutoCreate2Tool(storageGroup, timeSeriesPrefix);
+  }
+
+  private void InsertAutoCreate2Tool(String storageGroup, String 
timeSeriesPrefix)
+      throws SQLException {
+    statement.execute("show timeseries");
+    Set<String> resultList = new HashSet<>();
+    try (ResultSet resultSet = statement.getResultSet()) {
+      while (resultSet.next()) {
+        String str = resultSet.getString("timeseries");
+        resultList.add(str);
+      }
+    }
+    Assert.assertFalse(resultList.contains(timeSeriesPrefix + "c"));
+
+    statement.execute("show storage group");
+    resultList.clear();
+    try (ResultSet resultSet = statement.getResultSet()) {
+      while (resultSet.next()) {
+        resultList.add(resultSet.getString("storage group"));
+      }
+    }
+    Assert.assertTrue(resultList.contains(storageGroup));
+  }
 }
diff --git 
a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBCreateTimeseriesIT.java
 
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBCreateTimeseriesIT.java
new file mode 100644
index 0000000..5174419
--- /dev/null
+++ 
b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBCreateTimeseriesIT.java
@@ -0,0 +1,157 @@
+/*
+ * 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.integration;
+
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+import org.apache.iotdb.jdbc.IoTDBSQLException;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Notice that, all test begins with "IoTDB" is integration test. All test 
which will start the
+ * IoTDB server should be defined as integration test.
+ */
+public class IoTDBCreateTimeseriesIT {
+  private Statement statement;
+  private Connection connection;
+
+  @Before
+  public void setUp() throws Exception {
+    EnvironmentUtils.envSetUp();
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    connection = DriverManager.getConnection("jdbc:iotdb://127.0.0.1:6667/", 
"root", "root");
+    statement = connection.createStatement();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    statement.close();
+    connection.close();
+    EnvironmentUtils.cleanEnv();
+  }
+
+  /** Test creating a time series that is a prefix path of an existing time 
series */
+  @Test
+  public void testCreateTimeseries1() throws Exception {
+    String[] timeSeriesArray = {"root.sg1.aa.bb", "root.sg1.aa.bb.cc", 
"root.sg1.aa"};
+
+    for (String timeSeries : timeSeriesArray) {
+      statement.execute(
+          String.format(
+              "create timeseries %s with datatype=INT64, encoding=PLAIN, 
compression=SNAPPY",
+              timeSeries));
+    }
+
+    // ensure that current timeseries in cache is right.
+    createTimeSeries1Tool(timeSeriesArray);
+
+    EnvironmentUtils.stopDaemon();
+    setUp();
+
+    // ensure timeseries in cache is right after recovering.
+    createTimeSeries1Tool(timeSeriesArray);
+  }
+
+  private void createTimeSeries1Tool(String[] timeSeriesArray) throws 
SQLException {
+    boolean hasResult = statement.execute("show timeseries");
+    Assert.assertTrue(hasResult);
+
+    List<String> resultList = new ArrayList<>();
+    try (ResultSet resultSet = statement.getResultSet()) {
+      while (resultSet.next()) {
+        String timeseries = resultSet.getString("timeseries");
+        resultList.add(timeseries);
+      }
+    }
+    Assert.assertEquals(3, resultList.size());
+
+    List<String> collect =
+        resultList.stream()
+            .sorted(Comparator.comparingInt(e -> e.split("\\.").length))
+            .collect(Collectors.toList());
+
+    Assert.assertEquals(timeSeriesArray[2], collect.get(0));
+    Assert.assertEquals(timeSeriesArray[0], collect.get(1));
+    Assert.assertEquals(timeSeriesArray[1], collect.get(2));
+  }
+
+  /** Test if creating a time series will cause the storage group with same 
name to disappear */
+  @Test
+  public void testCreateTimeseries2() throws Exception {
+    String storageGroup = "root.sg1.a.b.c";
+
+    statement.execute(String.format("SET storage group TO %s", storageGroup));
+    try {
+      statement.execute(
+          String.format(
+              "create timeseries %s with datatype=INT64, encoding=PLAIN, 
compression=SNAPPY",
+              storageGroup));
+    } catch (IoTDBSQLException ignored) {
+    }
+
+    // ensure that current storage group in cache is right.
+    createTimeSeries2Tool(storageGroup);
+
+    EnvironmentUtils.stopDaemon();
+    setUp();
+
+    // ensure storage group in cache is right after recovering.
+    createTimeSeries2Tool(storageGroup);
+  }
+
+  private void createTimeSeries2Tool(String storageGroup) throws SQLException {
+    statement.execute("show timeseries");
+    Set<String> resultList = new HashSet<>();
+    try (ResultSet resultSet = statement.getResultSet()) {
+      while (resultSet.next()) {
+        String str = resultSet.getString("timeseries");
+        resultList.add(str);
+      }
+    }
+    Assert.assertFalse(resultList.contains(storageGroup));
+
+    statement.execute("show storage group");
+    resultList.clear();
+    try (ResultSet resultSet = statement.getResultSet()) {
+      while (resultSet.next()) {
+        String res = resultSet.getString("storage group");
+        resultList.add(res);
+      }
+    }
+    Assert.assertTrue(resultList.contains(storageGroup));
+  }
+}
diff --git a/server/src/test/java/org/apache/iotdb/db/metadata/MTreeTest.java 
b/server/src/test/java/org/apache/iotdb/db/metadata/MTreeTest.java
index e7c0b25..14f5e39 100644
--- a/server/src/test/java/org/apache/iotdb/db/metadata/MTreeTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/MTreeTest.java
@@ -22,6 +22,7 @@ import 
org.apache.iotdb.db.exception.metadata.AliasAlreadyExistException;
 import org.apache.iotdb.db.exception.metadata.IllegalPathException;
 import org.apache.iotdb.db.exception.metadata.MetadataException;
 import org.apache.iotdb.db.metadata.mnode.MNode;
+import org.apache.iotdb.db.metadata.mnode.MeasurementMNode;
 import org.apache.iotdb.db.utils.EnvironmentUtils;
 import org.apache.iotdb.tsfile.common.conf.TSFileDescriptor;
 import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
@@ -767,4 +768,30 @@ public class MTreeTest {
       fail(e1.getMessage());
     }
   }
+
+  @Test
+  public void testCreateTimeseries() throws MetadataException {
+    MTree root = new MTree();
+    String sgPath = "root.sg1";
+    root.setStorageGroup(new PartialPath(sgPath));
+
+    root.createTimeseries(
+        new PartialPath("root.sg1.a.b.c"),
+        TSDataType.INT32,
+        TSEncoding.RLE,
+        TSFileDescriptor.getInstance().getConfig().getCompressor(),
+        Collections.emptyMap(),
+        null);
+
+    root.createTimeseries(
+        new PartialPath("root.sg1.a.b"),
+        TSDataType.INT32,
+        TSEncoding.RLE,
+        TSFileDescriptor.getInstance().getConfig().getCompressor(),
+        Collections.emptyMap(),
+        null);
+
+    MNode node = root.getNodeByPath(new PartialPath("root.sg1.a.b"));
+    Assert.assertTrue(node instanceof MeasurementMNode);
+  }
 }
diff --git 
a/server/src/test/java/org/apache/iotdb/db/metadata/MetaUtilsTest.java 
b/server/src/test/java/org/apache/iotdb/db/metadata/MetaUtilsTest.java
index ddd99ac..49d85a2 100644
--- a/server/src/test/java/org/apache/iotdb/db/metadata/MetaUtilsTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/MetaUtilsTest.java
@@ -19,11 +19,13 @@
 package org.apache.iotdb.db.metadata;
 
 import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.metadata.mnode.MNode;
 
 import org.junit.Assert;
 import org.junit.Test;
 
 import java.util.Arrays;
+import java.util.List;
 
 import static org.junit.Assert.assertArrayEquals;
 
@@ -76,4 +78,37 @@ public class MetaUtilsTest {
       Assert.assertEquals("root.sg.d1.'s1' is not a legal path", 
e.getMessage());
     }
   }
+
+  @Test
+  public void testGetMultiFullPaths() {
+    MNode rootNode = new MNode(null, "root");
+
+    // builds the relationship of root.a and root.aa
+    MNode aNode = new MNode(rootNode, "a");
+    rootNode.addChild(aNode.getName(), aNode);
+    MNode aaNode = new MNode(rootNode, "aa");
+    rootNode.addChild(aaNode.getName(), aaNode);
+
+    // builds the relationship of root.a.b and root.aa.bb
+    MNode bNode = new MNode(aNode, "b");
+    aNode.addChild(bNode.getName(), bNode);
+    MNode bbNode = new MNode(aaNode, "bb");
+    aaNode.addChild(bbNode.getName(), bbNode);
+
+    // builds the relationship of root.aa.bb.cc
+    MNode ccNode = new MNode(bbNode, "cc");
+    bbNode.addChild(ccNode.getName(), ccNode);
+
+    List<String> multiFullPaths = MetaUtils.getMultiFullPaths(rootNode);
+    Assert.assertSame(2, multiFullPaths.size());
+
+    multiFullPaths.forEach(
+        fullPath -> {
+          if (fullPath.contains("aa")) {
+            Assert.assertEquals("root.aa.bb.cc", fullPath);
+          } else {
+            Assert.assertEquals("root.a.b", fullPath);
+          }
+        });
+  }
 }
diff --git 
a/server/src/test/java/org/apache/iotdb/db/metadata/mnode/MNodeTest.java 
b/server/src/test/java/org/apache/iotdb/db/metadata/mnode/MNodeTest.java
new file mode 100644
index 0000000..1a58bda
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/mnode/MNodeTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.mnode;
+
+import org.apache.iotdb.db.metadata.MetaUtils;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+
+public class MNodeTest {
+  private static ExecutorService service;
+
+  @Before
+  public void setUp() throws Exception {
+    service =
+        Executors.newFixedThreadPool(
+            Runtime.getRuntime().availableProcessors(),
+            new 
ThreadFactoryBuilder().setDaemon(false).setNameFormat("replaceChild-%d").build());
+  }
+
+  @Test
+  public void testReplaceChild() throws InterruptedException {
+    // after replacing a with c, the timeseries root.a.b becomes root.c.b
+    MNode rootNode = new MNode(null, "root");
+
+    MNode aNode = new MNode(rootNode, "a");
+    rootNode.addChild(aNode.getName(), aNode);
+
+    MNode bNode = new MNode(aNode, "b");
+    aNode.addChild(bNode.getName(), bNode);
+    aNode.addAlias("aliasOfb", bNode);
+
+    for (int i = 0; i < 500; i++) {
+      service.submit(
+          new Thread(() -> rootNode.replaceChild(aNode.getName(), new 
MNode(null, "c"))));
+    }
+
+    if (!service.isShutdown()) {
+      service.shutdown();
+      service.awaitTermination(30, TimeUnit.SECONDS);
+    }
+
+    List<String> multiFullPaths = MetaUtils.getMultiFullPaths(rootNode);
+    assertEquals("root.c.b", multiFullPaths.get(0));
+    assertEquals("root.c.b", 
rootNode.getChild("c").getChild("aliasOfb").getFullPath());
+  }
+}

Reply via email to