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

qiaojialin pushed a commit to branch rel/0.13
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/rel/0.13 by this push:
     new cf99d8e803 [To rel/0.13][IOTDB-5133] Export schema tool (#8378)
cf99d8e803 is described below

commit cf99d8e803c3ec7bb58ab2f828b26237fd66457a
Author: Chen YZ <[email protected]>
AuthorDate: Sun Dec 11 21:10:46 2022 +0800

    [To rel/0.13][IOTDB-5133] Export schema tool (#8378)
---
 .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4   |   7 +-
 .../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4  |   4 +
 docs/UserGuide/Maintenance-Tools/MLogLoad-Tool.md  |   7 +-
 docs/UserGuide/Operate-Metadata/Export-Schema.md   |  69 +++++
 .../UserGuide/Maintenance-Tools/MLogLoad-Tool.md   |   5 +-
 .../zh/UserGuide/Operate-Metadata/Export-Schema.md |  68 +++++
 .../iotdb/db/integration/IoTDBExportSchemaIT.java  | 291 +++++++++++++++++++++
 .../assembly/resources/tools/mlog/exportSchema.bat |  62 +++++
 .../assembly/resources/tools/mlog/exportSchema.sh  |  48 ++++
 .../org/apache/iotdb/db/metadata/MManager.java     |  82 ++++++
 .../org/apache/iotdb/db/metadata/mtree/MTree.java  | 129 +++++++++
 .../db/metadata/mtree/traverser/Traverser.java     |   6 +-
 .../apache/iotdb/db/qp/constant/SQLConstant.java   |   1 +
 .../apache/iotdb/db/qp/executor/PlanExecutor.java  |  12 +
 .../org/apache/iotdb/db/qp/logical/Operator.java   |   4 +-
 .../db/qp/logical/sys/ExportSchemaOperator.java    |  49 ++++
 .../iotdb/db/qp/physical/sys/ExportSchemaPlan.java |  45 ++++
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    |   7 +
 .../apache/iotdb/db/tools/mlog/ExportSchema.java   | 162 ++++++++++++
 19 files changed, 1052 insertions(+), 6 deletions(-)

diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 
b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
index 7996345033..3bcfc7c2a0 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
@@ -63,7 +63,7 @@ utilityStatement
     : merge | fullMerge | flush | clearCache | settle
     | setSystemStatus | showVersion | showFlushInfo | showLockInfo | 
showQueryResource
     | showQueryProcesslist | killQuery | grantWatermarkEmbedding | 
revokeWatermarkEmbedding
-    | loadConfiguration | loadTimeseries | loadFile | removeFile | unloadFile;
+    | loadConfiguration | loadTimeseries | loadFile | removeFile | unloadFile 
| exportSchema;
 
 
 /**
@@ -720,6 +720,11 @@ unloadFile
     : UNLOAD srcFileName=STRING_LITERAL dstFileDir=STRING_LITERAL
     ;
 
+// Export Schema
+exportSchema
+    : EXPORT SCHEMA dstDir=STRING_LITERAL
+    ;
+
 
 /**
  * 6. Common Clauses
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 
b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
index 1dd1c4ff48..21d74741a4 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
@@ -218,6 +218,10 @@ EXPLAIN
     : E X P L A I N
     ;
 
+EXPORT
+    : E X P O R T
+    ;
+
 FILL
     : F I L L
     ;
diff --git a/docs/UserGuide/Maintenance-Tools/MLogLoad-Tool.md 
b/docs/UserGuide/Maintenance-Tools/MLogLoad-Tool.md
index a5146a6093..90f171b36d 100644
--- a/docs/UserGuide/Maintenance-Tools/MLogLoad-Tool.md
+++ b/docs/UserGuide/Maintenance-Tools/MLogLoad-Tool.md
@@ -23,8 +23,11 @@
 
 ### Introduction
 
-The MLogLoad tool  is used to load the metadata from `mlog.bin` and tags and 
attributes from `tlog.txt` into the running IoTDB.
-Data to be loaded that conflicts with metadata in the running IoTDB will be 
skipped.
+The MLogLoad tool is used to load the metadata from `mlog.bin` and tags and 
attributes from `tlog.txt` into the running IoTDB.
+Metadata operation in `mlog.bin` will be replayed.
+Metedata to be loaded that conflicts with metadata in the running IoTDB will 
be skipped.
+
+If there is already metadata in the running IoTDB, to avoid replaying the 
delete operation to affect the existing metadata, it needs to be loaded using 
`mlog.bin` and `tlog.txt` obtained from the [export metadata 
operation](https://iotdb.apache.org/UserGuide/V0.13.x/Operate-Metadata/Export-Schema.html).
 
 ### How to Use
 
diff --git a/docs/UserGuide/Operate-Metadata/Export-Schema.md 
b/docs/UserGuide/Operate-Metadata/Export-Schema.md
new file mode 100644
index 0000000000..785ca1922f
--- /dev/null
+++ b/docs/UserGuide/Operate-Metadata/Export-Schema.md
@@ -0,0 +1,69 @@
+<!--
+
+    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.
+
+-->
+
+## Export Schema
+
+The schema export operation exports the information about store group, 
timeseries, and schema template in the current IoTDB in the form of `mlog.bin` 
and `tlog.txt` to the specified directory.
+
+The exported `mlog.bin` and `tlog.txt` files can be incrementally loaded into 
an IoTDB instance.
+
+### Export Schema with SQL
+
+```
+EXPORT SCHEMA '<path/dir>' 
+```
+
+### Export Schema with Script
+
+Linux/MacOS
+
+> ./exportSchema.sh -d /yourpath/data/system/schema -o /yourpath/targetDir
+
+Windows
+
+> ./exportSchema.bat-d /yourpath/data/system/schema -o /yourpath/targetDir
+
+
+Source directory and the export destination directory need to be specified 
when exporting metadata using scripting.
+```
+usage: ExportSchema -d <source directory path> -o <target directory path>
+       [-help]
+ -d <source directory path>   Need to specify a source directory path
+ -o <target directory path>   Need to specify a target directory path
+ -help,--help                 Display help information
+```
+
+### Q&A
+
+* Cannot find or load the main class ExportSchema
+    * It may be because the environment variable $IOTDB_HOME is not set, 
please set the environment variable and try again
+* Encounter an error, because: File ... already exist.
+    * There is already a mlog.bin or tlog.txt file in the target directory, 
please check the target directory and try again
+* Encounter an error, because: ... does not exist or is not a directory.
+    * The source directory path does not exist or is not a directory, please 
check the source directory and try again
+* Encounter an error, because: ... is not a valid directory.
+    * The source directory is not the schema directory in IoTDB, please check 
the target directory and try again
+
+
+### Load Schema
+
+Please refer to [MLogLoad 
Tool](https://iotdb.apache.org/UserGuide/V0.13.x/Maintenance-Tools/MLogLoad-Tool.html)
+
diff --git a/docs/zh/UserGuide/Maintenance-Tools/MLogLoad-Tool.md 
b/docs/zh/UserGuide/Maintenance-Tools/MLogLoad-Tool.md
index ecad5072fc..27c3ef4570 100644
--- a/docs/zh/UserGuide/Maintenance-Tools/MLogLoad-Tool.md
+++ b/docs/zh/UserGuide/Maintenance-Tools/MLogLoad-Tool.md
@@ -23,9 +23,12 @@
 
 ### 工具介绍
 
-MLogLoad 工具用于将 `mlog.bin` 中的元数据和 `tlog.txt`   中的标签和属性加载到正在运行的 IoTDB 中。
+MLogLoad 工具用于将重放 `mlog.bin` 中和 `tlog.txt`   中的元数据操作,将元数据信息加载到正在运行的 IoTDB 中。
 与正在运行的 IoTDB 中的元数据存在冲突的待加载数据将会被跳过。
 
+若正在运行的 IoTDB 中已有元数据,为避免重放删除操作影响元数据,建议使用
+[导出元数据操作](https://iotdb.apache.org/zh/UserGuide/V0.13.x/Operate-Metadata/Export-Schema.html)
 归档得到的 `mlog.bin` 和 `tlog.txt` 进行增量加载。
+
 ### 使用方法
 
 Linux/MacOS
diff --git a/docs/zh/UserGuide/Operate-Metadata/Export-Schema.md 
b/docs/zh/UserGuide/Operate-Metadata/Export-Schema.md
new file mode 100644
index 0000000000..6198338b67
--- /dev/null
+++ b/docs/zh/UserGuide/Operate-Metadata/Export-Schema.md
@@ -0,0 +1,68 @@
+<!--
+
+    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.
+
+-->
+
+## 元数据导出操作
+
+元数据导出操作会以 mlog.bin 和 tlog.txt 的形式将当前 IoTDB 中的存储组、时间序列、元数据模板信息进行归档,并导出到指定目录中。
+
+导出的 mlog.bin 和 tlog.txt 文件可以增量的方式加载到已有元数据的 IoTDB 实例中。
+
+### 使用 SQL 方式导出元数据
+
+元数据导出的 SQL 语句如下所示:
+```
+EXPORT SCHEMA '<path/dir>' 
+```
+
+### 使用脚本方式导出元数据
+
+Linux/MacOS
+
+> ./exportSchema.sh -d /yourpath/data/system/schema -o /yourpath/targetDir
+
+Windows
+
+> ./exportSchema.bat-d /yourpath/data/system/schema -o /yourpath/targetDir
+
+使用脚本方式导出元数据时候,需要指定 IoTDB 的元数据文件目录与导出目标目录:
+```
+usage: ExportSchema -d <source directory path> -o <target directory path>
+       [-help]
+ -d <source directory path>   Need to specify a source directory path
+ -o <target directory path>   Need to specify a target directory path
+ -help,--help                 Display help information
+```
+
+### 常见问题
+
+* 找不到或无法加载主类 ExportSchema
+    * 可能是由于未设置环境变量 $IOTDB_HOME,请设置环境变量之后重试
+* Encounter an error, because: File ... already exist.
+    * 目标目录下已有 mlog.bin 或者 tlog.txt 文件,请检查目标目录之后重试
+* Encounter an error, because: ... does not exist or is not a directory.
+    * 源目录路径不存在或不是目录,请检查源目录之后重试
+* Encounter an error, because: ... is not a valid directory.
+    * 源目录不是 IoTDB 中的元数据文件目录,请检查目标目录之后重试
+
+## 元数据加载操作
+
+参考 [MLog 
加载工具](https://iotdb.apache.org/zh/UserGuide/V0.13.x/Maintenance-Tools/MLogLoad-Tool.html)
+
diff --git 
a/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBExportSchemaIT.java
 
b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBExportSchemaIT.java
new file mode 100644
index 0000000000..b2deab90db
--- /dev/null
+++ 
b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBExportSchemaIT.java
@@ -0,0 +1,291 @@
+/*
+ * 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.metadata.MetadataConstant;
+import org.apache.iotdb.db.tools.mlog.MLogLoader;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.itbase.category.LocalStandaloneTest;
+import org.apache.iotdb.jdbc.Config;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashSet;
+import java.util.Set;
+
+@Category({LocalStandaloneTest.class})
+public class IoTDBExportSchemaIT {
+  private Connection connection;
+  private File targetDir = new File("target/tmp/exportSchema");
+
+  private Set<String> showTimeseriesResultSet = new HashSet<>();
+  private Set<String> showDevicesResultSet = new HashSet<>();
+  private Set<String> showTemplatesResultSet = new HashSet<>();
+  private Set<String> showPathsSetSchemaTemplateT1ResultSet = new HashSet<>();
+  private Set<String> showPathsUsingSchemaTemplateT1ResultSet = new 
HashSet<>();
+  private Set<String> showPathsSetSchemaTemplateT2ResultSet = new HashSet<>();
+  private Set<String> showPathsUsingSchemaTemplateT2ResultSet = new 
HashSet<>();
+
+  @Before
+  public void setUp() throws Exception {
+    EnvironmentUtils.envSetUp();
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    connection =
+        DriverManager.getConnection(Config.IOTDB_URL_PREFIX + 
"127.0.0.1:6667/", "root", "root");
+    prepareSchema();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    connection.close();
+    EnvironmentUtils.cleanEnv();
+    FileUtils.deleteDirectory(targetDir);
+  }
+
+  private void prepareSchema() {
+    try (Statement statement = connection.createStatement()) {
+      // create storage group
+      statement.execute("CREATE STORAGE GROUP root.ln");
+      statement.execute("CREATE STORAGE GROUP root.sgcc");
+      // create time series
+      statement.execute(
+          "create timeseries root.ln.wf01.wt01.status with 
datatype=BOOLEAN,encoding=PLAIN;");
+      statement.execute("create timeseries root.ln.wf01.wt01.temperature FLOAT 
encoding=RLE;");
+      statement.execute("create timeseries root.sgcc.wf03.wt01.status BOOLEAN 
encoding=PLAIN;");
+      statement.execute(
+          "CREATE ALIGNED TIMESERIES root.ln.wf01.GPS(latitude FLOAT 
encoding=PLAIN compressor=SNAPPY, longitude FLOAT encoding=PLAIN 
compressor=SNAPPY);");
+      statement.execute(
+          "CREATE ALIGNED TIMESERIES root.ln.wf01.GPS(status BOOLEAN 
encoding=PLAIN compressor=SNAPPY);");
+      statement.execute(
+          "create timeseries root.turbine.d1.s1(temprature) with 
datatype=FLOAT, encoding=RLE, compression=SNAPPY tags(tag1=v1, tag2=v2) 
attributes(attr1=v1, attr2=v2);");
+      // create template
+      statement.execute(
+          "create schema template t1 (temperature FLOAT encoding=RLE, status 
BOOLEAN encoding=PLAIN compression=SNAPPY);");
+      statement.execute(
+          "create schema template t2 aligned (lat FLOAT encoding=Gorilla, lon 
FLOAT encoding=Gorilla);");
+      statement.execute("set schema template t1 to root.sg1.d1;");
+      statement.execute("set schema template t2 to root.sg1.d2;");
+      statement.execute("set schema template t2 to root.sg1.d3;");
+      statement.execute("create timeseries of schema template on 
root.sg1.d1;");
+      statement.execute("create timeseries of schema template on 
root.sg1.d2;");
+      // upsert
+      statement.execute(
+          "ALTER timeseries root.ln.wf01.wt01.temperature UPSERT 
ALIAS=newAlias TAGS(tag2=newV2, tag3=v3) ATTRIBUTES(attr3=v3, attr4=v4)");
+      // delete timeseries
+      statement.execute("delete timeseries root.ln.wf01.wt01.status;");
+      // delete storage group
+      statement.execute("delete storage group root.sgcc;");
+    } catch (SQLException e) {
+      Assert.fail(e.getMessage());
+    }
+  }
+
+  private void saveExpectedResult() {
+    Set<String>[] sets =
+        new Set[] {
+          showTimeseriesResultSet,
+          showDevicesResultSet,
+          showTemplatesResultSet,
+          showPathsSetSchemaTemplateT1ResultSet,
+          showPathsSetSchemaTemplateT2ResultSet,
+          showPathsUsingSchemaTemplateT1ResultSet,
+          showPathsUsingSchemaTemplateT2ResultSet
+        };
+    String[] queries =
+        new String[] {
+          "show timeseries",
+          "show devices",
+          "show schema templates",
+          "show paths set schema template t1",
+          "show paths set schema template t2",
+          "show paths using schema template t1",
+          "show paths using schema template t2"
+        };
+    try (Statement statement = connection.createStatement()) {
+      for (int i = 0; i < queries.length; i++) {
+        statement.execute(queries[i]);
+        try (ResultSet resultSet = statement.getResultSet()) {
+          int cnt = resultSet.getMetaData().getColumnCount();
+          while (resultSet.next()) {
+            StringBuilder stringBuilder = new StringBuilder();
+            for (int j = 1; j <= cnt; j++) {
+              stringBuilder.append(resultSet.getString(j)).append(",");
+            }
+            sets[i].add(stringBuilder.toString());
+          }
+        }
+      }
+    } catch (SQLException e) {
+      Assert.fail(e.getMessage());
+    }
+  }
+
+  private void checkExpectedResult() {
+    Set<String>[] sets =
+        new Set[] {
+          showTimeseriesResultSet,
+          showDevicesResultSet,
+          showTemplatesResultSet,
+          showPathsSetSchemaTemplateT1ResultSet,
+          showPathsSetSchemaTemplateT2ResultSet,
+          showPathsUsingSchemaTemplateT1ResultSet,
+          showPathsUsingSchemaTemplateT2ResultSet
+        };
+    String[] queries =
+        new String[] {
+          "show timeseries",
+          "show devices",
+          "show schema templates",
+          "show paths set schema template t1",
+          "show paths set schema template t2",
+          "show paths using schema template t1",
+          "show paths using schema template t2"
+        };
+    try (Statement statement = connection.createStatement()) {
+      for (int i = 0; i < queries.length; i++) {
+        statement.execute(queries[i]);
+        try (ResultSet resultSet = statement.getResultSet()) {
+          int cnt = resultSet.getMetaData().getColumnCount();
+          while (resultSet.next()) {
+            StringBuilder stringBuilder = new StringBuilder();
+            for (int j = 1; j <= cnt; j++) {
+              stringBuilder.append(resultSet.getString(j)).append(",");
+            }
+            Assert.assertTrue(sets[i].contains(stringBuilder.toString()));
+          }
+        }
+      }
+    } catch (SQLException e) {
+      Assert.fail(e.getMessage());
+    }
+  }
+
+  private void cleanAndRestart() throws Exception {
+    connection.close();
+    EnvironmentUtils.cleanEnv();
+    EnvironmentUtils.envSetUp();
+    connection =
+        DriverManager.getConnection(Config.IOTDB_URL_PREFIX + 
"127.0.0.1:6667/", "root", "root");
+  }
+
+  @Test
+  public void testExportSchemaFail() throws Exception {
+    File file = new File(targetDir, MetadataConstant.METADATA_LOG);
+    if (!targetDir.exists()) {
+      targetDir.mkdirs();
+    }
+    file.createNewFile();
+
+    try (Statement statement = connection.createStatement()) {
+      statement.execute(String.format("export schema '%s'", 
targetDir.getAbsolutePath()));
+      Assert.fail("Expect failure but success");
+    } catch (SQLException e) {
+      Assert.assertTrue(e.getMessage().contains("already exist"));
+    }
+  }
+
+  @Test
+  public void testExportSchemaAndLoadToEmptyIoTDB() throws Exception {
+    saveExpectedResult();
+
+    try (Statement statement = connection.createStatement()) {
+      statement.execute(String.format("export schema '%s'", 
targetDir.getAbsolutePath()));
+    } catch (SQLException e) {
+      Assert.fail(e.getMessage());
+    }
+
+    File[] files = targetDir.listFiles();
+    Assert.assertNotEquals(0, files.length);
+    cleanAndRestart();
+    // load mlog
+    MLogLoader.main(
+        new String[] {
+          "-mlog",
+          targetDir.getAbsolutePath() + "/" + MetadataConstant.METADATA_LOG,
+          "-tlog",
+          targetDir.getAbsolutePath() + "/" + MetadataConstant.TAG_LOG
+        });
+    checkExpectedResult();
+  }
+
+  @Test
+  public void testExportSchemaAndLoadToNonEmptyIoTDB() throws Exception {
+    saveExpectedResult();
+
+    try (Statement statement = connection.createStatement()) {
+      statement.execute(String.format("export schema '%s'", 
targetDir.getAbsolutePath()));
+    } catch (SQLException e) {
+      Assert.fail(e.getMessage());
+    }
+
+    File[] files = targetDir.listFiles();
+    Assert.assertNotEquals(0, files.length);
+    cleanAndRestart();
+    // create some timeseries
+    try (Statement statement = connection.createStatement()) {
+      statement.execute("create timeseries root.sgcc.wf03.wt01.status BOOLEAN 
encoding=PLAIN;");
+      statement.execute("create timeseries root.ln.d1.s1 FLOAT encoding=RLE;");
+      statement.execute("create timeseries root.sg1.d1.s1 FLOAT 
encoding=RLE;");
+      statement.execute("show timeseries");
+      try (ResultSet resultSet = statement.getResultSet()) {
+        int cnt = resultSet.getMetaData().getColumnCount();
+        while (resultSet.next()) {
+          StringBuilder stringBuilder = new StringBuilder();
+          for (int j = 1; j <= cnt; j++) {
+            stringBuilder.append(resultSet.getString(j)).append(",");
+          }
+          showTimeseriesResultSet.add(stringBuilder.toString());
+        }
+      }
+      statement.execute("show devices");
+      try (ResultSet resultSet = statement.getResultSet()) {
+        int cnt = resultSet.getMetaData().getColumnCount();
+        while (resultSet.next()) {
+          StringBuilder stringBuilder = new StringBuilder();
+          for (int j = 1; j <= cnt; j++) {
+            stringBuilder.append(resultSet.getString(j)).append(",");
+          }
+          showDevicesResultSet.add(stringBuilder.toString());
+        }
+      }
+    } catch (SQLException e) {
+      Assert.fail(e.getMessage());
+    }
+    // load mlog
+    MLogLoader.main(
+        new String[] {
+          "-mlog",
+          targetDir.getAbsolutePath() + "/" + MetadataConstant.METADATA_LOG,
+          "-tlog",
+          targetDir.getAbsolutePath() + "/" + MetadataConstant.TAG_LOG
+        });
+    checkExpectedResult();
+  }
+}
diff --git a/server/src/assembly/resources/tools/mlog/exportSchema.bat 
b/server/src/assembly/resources/tools/mlog/exportSchema.bat
new file mode 100644
index 0000000000..5a3afd5107
--- /dev/null
+++ b/server/src/assembly/resources/tools/mlog/exportSchema.bat
@@ -0,0 +1,62 @@
+@REM
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM     http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM
+
+
+@echo off
+echo ````````````````````````
+echo Starting Exporting Schema
+echo ````````````````````````
+
+if "%OS%" == "Windows_NT" setlocal
+
+pushd %~dp0..\..
+if NOT DEFINED IOTDB_HOME set IOTDB_HOME=%CD%
+popd
+
+if NOT DEFINED MAIN_CLASS set 
MAIN_CLASS=org.apache.iotdb.db.tools.mlog.ExportSchema
+if NOT DEFINED JAVA_HOME goto :err
+
+@REM 
-----------------------------------------------------------------------------
+@REM ***** CLASSPATH library setting *****
+@REM Ensure that any user defined CLASSPATH variables are not used on startup
+set CLASSPATH="%IOTDB_HOME%\lib\*"
+
+goto okClasspath
+
+:append
+set CLASSPATH=%CLASSPATH%;%1
+goto :eof
+
+@REM 
-----------------------------------------------------------------------------
+:okClasspath
+
+"%JAVA_HOME%\bin\java" -cp "%CLASSPATH%" %MAIN_CLASS% %*
+
+goto finally
+
+
+:err
+echo JAVA_HOME environment variable must be set!
+pause
+
+
+@REM 
-----------------------------------------------------------------------------
+:finally
+
+ENDLOCAL
\ No newline at end of file
diff --git a/server/src/assembly/resources/tools/mlog/exportSchema.sh 
b/server/src/assembly/resources/tools/mlog/exportSchema.sh
new file mode 100755
index 0000000000..518f587643
--- /dev/null
+++ b/server/src/assembly/resources/tools/mlog/exportSchema.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# 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.
+#
+
+echo ---------------------
+echo Starting Exporting Schema
+echo ---------------------
+
+if [ -z "${IOTDB_HOME}" ]; then
+  export IOTDB_HOME="$(cd "`dirname "$0"`"/../..; pwd)"
+fi
+
+if [ -n "$JAVA_HOME" ]; then
+    for java in "$JAVA_HOME"/bin/amd64/java "$JAVA_HOME"/bin/java; do
+        if [ -x "$java" ]; then
+            JAVA="$java"
+            break
+        fi
+    done
+else
+    JAVA=java
+fi
+
+CLASSPATH=""
+for f in ${IOTDB_HOME}/lib/*.jar; do
+  CLASSPATH=${CLASSPATH}":"$f
+done
+
+MAIN_CLASS=org.apache.iotdb.db.tools.mlog.ExportSchema
+
+"$JAVA" -cp "$CLASSPATH" "$MAIN_CLASS" "$@"
+exit $?
\ No newline at end of file
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 0c18e4fda9..8c05c0f8dd 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
@@ -103,6 +103,8 @@ import 
org.apache.iotdb.tsfile.write.schema.TimeseriesSchema;
 
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.LoadingCache;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.slf4j.Logger;
@@ -1336,6 +1338,86 @@ public class MManager {
     return mtree.getMeasurementPathsWithAlias(pathPattern, limit, offset, 
isPrefixMatch);
   }
 
+  public void exportSchema(File dir) throws IOException, MetadataException {
+    File tagTargetFile = new File(dir, MetadataConstant.TAG_LOG);
+    File mlogTargetFile = new File(dir, MetadataConstant.METADATA_LOG);
+    if (!dir.exists()) {
+      dir.mkdirs();
+    } else {
+      if (!dir.isDirectory()) {
+        throw new IOException(String.format("%s is not a directory.", 
dir.getAbsolutePath()));
+      }
+      if (mlogTargetFile.exists() || tagTargetFile.exists()) {
+        List<String> existedPath = new ArrayList<>();
+        if (mlogTargetFile.exists()) {
+          existedPath.add(mlogTargetFile.getAbsolutePath());
+          if (tagTargetFile.exists()) {
+            existedPath.add(tagTargetFile.getAbsolutePath());
+          }
+          throw new IOException(
+              String.format(
+                  "File %s already exist%s.",
+                  StringUtils.join(existedPath.toArray(), ","),
+                  existedPath.size() == 1 ? "s" : ""));
+        }
+      }
+    }
+
+    try (MLogWriter mLogWriter =
+        new MLogWriter(dir.getAbsolutePath(), MetadataConstant.METADATA_LOG)) {
+      // export storage group
+      for (PartialPath sg : mtree.getAllStorageGroupPaths()) {
+        mLogWriter.setStorageGroup(sg);
+      }
+      // export template
+      for (Map.Entry<String, Template> entry : 
templateManager.getTemplateMap().entrySet()) {
+        if (entry.getValue().isDirectAligned()) {
+          List<List<String>> measurements = Collections.singletonList(new 
ArrayList<>());
+          List<List<TSDataType>> dataTypes = Collections.singletonList(new 
ArrayList<>());
+          List<List<TSEncoding>> encodings = Collections.singletonList(new 
ArrayList<>());
+          List<List<CompressionType>> compressions = 
Collections.singletonList(new ArrayList<>());
+          entry
+              .getValue()
+              .getSchemaMap()
+              .values()
+              .forEach(
+                  i -> {
+                    measurements.get(0).add(i.getMeasurementId());
+                    dataTypes.get(0).add(i.getType());
+                    encodings.get(0).add(i.getEncodingType());
+                    compressions.get(0).add(i.getCompressor());
+                  });
+          mLogWriter.createSchemaTemplate(
+              new CreateTemplatePlan(
+                  entry.getKey(), measurements, dataTypes, encodings, 
compressions));
+        } else {
+          List<List<String>> measurements = new ArrayList<>();
+          List<List<TSDataType>> dataTypes = new ArrayList<>();
+          List<List<TSEncoding>> encodings = new ArrayList<>();
+          List<List<CompressionType>> compressions = new ArrayList<>();
+          entry
+              .getValue()
+              .getSchemaMap()
+              .values()
+              .forEach(
+                  i -> {
+                    
measurements.add(Collections.singletonList(i.getMeasurementId()));
+                    dataTypes.add(Collections.singletonList(i.getType()));
+                    
encodings.add(Collections.singletonList(i.getEncodingType()));
+                    
compressions.add(Collections.singletonList(i.getCompressor()));
+                  });
+          mLogWriter.createSchemaTemplate(
+              new CreateTemplatePlan(
+                  entry.getKey(), measurements, dataTypes, encodings, 
compressions));
+        }
+      }
+      // export timeseries
+      mtree.exportSchema(mLogWriter);
+      // export tag
+      FileUtils.copyFile(new File(config.getSchemaDir(), 
MetadataConstant.TAG_LOG), tagTargetFile);
+    }
+  }
+
   public List<ShowTimeSeriesResult> showTimeseries(ShowTimeSeriesPlan plan, 
QueryContext context)
       throws MetadataException {
     // show timeseries with index
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 81cf106b6d..55728eb9ac 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
@@ -63,8 +63,11 @@ import org.apache.iotdb.db.metadata.template.Template;
 import org.apache.iotdb.db.metadata.utils.MetaFormatUtils;
 import org.apache.iotdb.db.metadata.utils.MetaUtils;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.sys.CreateAlignedTimeSeriesPlan;
+import org.apache.iotdb.db.qp.physical.sys.CreateTimeSeriesPlan;
 import org.apache.iotdb.db.qp.physical.sys.MNodePlan;
 import org.apache.iotdb.db.qp.physical.sys.MeasurementMNodePlan;
+import org.apache.iotdb.db.qp.physical.sys.SetTemplatePlan;
 import org.apache.iotdb.db.qp.physical.sys.ShowDevicesPlan;
 import org.apache.iotdb.db.qp.physical.sys.ShowTimeSeriesPlan;
 import org.apache.iotdb.db.qp.physical.sys.StorageGroupMNodePlan;
@@ -108,6 +111,7 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static java.util.stream.Collectors.toList;
@@ -230,6 +234,131 @@ public class MTree implements Serializable {
     }
   }
 
+  public void exportSchema(MLogWriter mLogWriter) throws MetadataException, 
IOException {
+    PartialPath[] entityPath = new PartialPath[1];
+    List<IMeasurementSchema> alignedMeasurementSchemas = new ArrayList<>();
+    Traverser collector =
+        new Traverser(root, new PartialPath("root.**")) {
+          IEntityMNode entityNode;
+
+          @Override
+          protected boolean processInternalMatchedMNode(IMNode node, int idx, 
int level)
+              throws MetadataException {
+            extractTemplateIfSet(node);
+            return false;
+          }
+
+          @Override
+          protected boolean processFullMatchedMNode(IMNode node, int idx, int 
level)
+              throws MetadataException {
+            if (!node.isMeasurement()) {
+              extractTemplateIfSet(node);
+              return false;
+            }
+            collectMeasurement(node.getAsMeasurementMNode());
+            return true;
+          }
+
+          private void collectMeasurement(IMeasurementMNode node) throws 
MetadataException {
+            try {
+              IMeasurementSchema measurementSchema = node.getSchema();
+              IEntityMNode entityMNode = getParentEntityMNodeIfExist();
+              checkAndWriteCreateAlignedTimeseries(entityMNode);
+              if (!entityMNode.isAligned()) {
+                CreateTimeSeriesPlan createTimeSeriesPlan =
+                    new CreateTimeSeriesPlan(
+                        getCurrentPartialPath(node),
+                        measurementSchema.getType(),
+                        measurementSchema.getEncodingType(),
+                        measurementSchema.getCompressor(),
+                        measurementSchema.getProps(),
+                        null,
+                        null,
+                        node.getAlias());
+                createTimeSeriesPlan.setTagOffset(node.getOffset());
+                mLogWriter.createTimeseries(createTimeSeriesPlan);
+              } else {
+                alignedMeasurementSchemas.add(measurementSchema);
+              }
+            } catch (IOException e) {
+              e.printStackTrace();
+            }
+          }
+
+          private void checkAndWriteCreateAlignedTimeseries(IEntityMNode 
curEntityMNode)
+              throws IllegalPathException, IOException {
+            if (curEntityMNode != entityNode) {
+              if (!alignedMeasurementSchemas.isEmpty()) {
+                CreateAlignedTimeSeriesPlan createAlignedTimeSeriesPlan =
+                    new CreateAlignedTimeSeriesPlan(
+                        entityPath[0],
+                        alignedMeasurementSchemas.stream()
+                            .map(IMeasurementSchema::getMeasurementId)
+                            .collect(Collectors.toList()),
+                        alignedMeasurementSchemas.stream()
+                            .map(IMeasurementSchema::getType)
+                            .collect(Collectors.toList()),
+                        alignedMeasurementSchemas.stream()
+                            .map(IMeasurementSchema::getEncodingType)
+                            .collect(Collectors.toList()),
+                        alignedMeasurementSchemas.stream()
+                            .map(IMeasurementSchema::getCompressor)
+                            .collect(Collectors.toList()),
+                        null);
+                
mLogWriter.createAlignedTimeseries(createAlignedTimeSeriesPlan);
+              }
+              if (curEntityMNode.isAligned()) {
+                entityPath[0] = getCurrentPartialPath(curEntityMNode);
+              }
+              // clear
+              entityNode = curEntityMNode;
+              alignedMeasurementSchemas.clear();
+            }
+          }
+
+          private IEntityMNode getParentEntityMNodeIfExist() {
+            return traverseContext.peek().getAsEntityMNode();
+          }
+
+          private void extractTemplateIfSet(IMNode node) {
+            if (node.getSchemaTemplate() != null) {
+              try {
+                mLogWriter.setSchemaTemplate(
+                    new SetTemplatePlan(
+                        node.getSchemaTemplate().getName(),
+                        getCurrentPartialPath(node).getFullPath()));
+                if (node.isUseTemplate()) {
+                  
mLogWriter.setUsingSchemaTemplate(getCurrentPartialPath(node));
+                }
+              } catch (IOException | IllegalPathException e) {
+                logger.error(e.getMessage());
+              }
+            }
+          }
+        };
+    collector.setShouldTraverseTemplate(false);
+    collector.traverse();
+    if (!alignedMeasurementSchemas.isEmpty()) {
+      CreateAlignedTimeSeriesPlan createAlignedTimeSeriesPlan =
+          new CreateAlignedTimeSeriesPlan(
+              entityPath[0],
+              alignedMeasurementSchemas.stream()
+                  .map(IMeasurementSchema::getMeasurementId)
+                  .collect(Collectors.toList()),
+              alignedMeasurementSchemas.stream()
+                  .map(IMeasurementSchema::getType)
+                  .collect(Collectors.toList()),
+              alignedMeasurementSchemas.stream()
+                  .map(IMeasurementSchema::getEncodingType)
+                  .collect(Collectors.toList()),
+              alignedMeasurementSchemas.stream()
+                  .map(IMeasurementSchema::getCompressor)
+                  .collect(Collectors.toList()),
+              null);
+      mLogWriter.createAlignedTimeseries(createAlignedTimeSeriesPlan);
+    }
+  }
+
   public static MTree deserializeFrom(File mtreeSnapshot) {
     try (MLogReader mLogReader = new MLogReader(mtreeSnapshot)) {
       return new MTree(deserializeFromReader(mLogReader));
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 85b31a4403..004b178c2e 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
@@ -292,8 +292,12 @@ public abstract class Traverser {
     StringBuilder builder =
         nodes.hasNext() ? new StringBuilder(nodes.next().getName()) : new 
StringBuilder();
     while (nodes.hasNext()) {
+      IMNode node = nodes.next();
+      if (node == currentNode) {
+        break;
+      }
       builder.append(TsFileConstant.PATH_SEPARATOR);
-      builder.append(nodes.next().getName());
+      builder.append(node.getName());
     }
     if (builder.length() != 0) {
       builder.append(TsFileConstant.PATH_SEPARATOR);
diff --git 
a/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java 
b/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
index d63595d5e9..145d237e64 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
@@ -199,6 +199,7 @@ public class SQLConstant {
   public static final int TOK_SCHEMA_TEMPLATE_SHOW_PATHS_USING = 122;
   public static final int TOK_SHOW_QUERY_RESOURCE = 123;
   public static final int TOK_SCHEMA_TEMPLATE_DEACTIVATE = 124;
+  public static final int TOK_EXPORT_SCHEMA = 125;
 
   public static final Map<Integer, String> tokenNames = new HashMap<>();
 
diff --git 
a/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java 
b/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
index 1345cf89f6..ad26411834 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
@@ -105,6 +105,7 @@ import 
org.apache.iotdb.db.qp.physical.sys.DropContinuousQueryPlan;
 import org.apache.iotdb.db.qp.physical.sys.DropFunctionPlan;
 import org.apache.iotdb.db.qp.physical.sys.DropTemplatePlan;
 import org.apache.iotdb.db.qp.physical.sys.DropTriggerPlan;
+import org.apache.iotdb.db.qp.physical.sys.ExportSchemaPlan;
 import org.apache.iotdb.db.qp.physical.sys.FlushPlan;
 import org.apache.iotdb.db.qp.physical.sys.KillQueryPlan;
 import org.apache.iotdb.db.qp.physical.sys.LoadConfigurationPlan;
@@ -430,12 +431,23 @@ public class PlanExecutor implements IPlanExecutor {
       case PAUSE_ARCHIVING:
         operatePauseArchiving((PauseArchivingPlan) plan);
         return true;
+      case EXPORT_SCHEMA:
+        return exportSchema((ExportSchemaPlan) plan);
       default:
         throw new UnsupportedOperationException(
             String.format("operation %s is not supported", 
plan.getOperatorName()));
     }
   }
 
+  private boolean exportSchema(ExportSchemaPlan plan) throws 
QueryProcessException {
+    try {
+      IoTDB.metaManager.exportSchema(plan.getTargetDir());
+    } catch (MetadataException | IOException e) {
+      throw new QueryProcessException(e.getMessage(), e);
+    }
+    return true;
+  }
+
   private boolean createTemplate(CreateTemplatePlan createTemplatePlan)
       throws QueryProcessException {
     try {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java 
b/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
index 5708665b83..37813eff8d 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
@@ -200,6 +200,8 @@ public abstract class Operator {
 
     SET_ARCHIVING,
     PAUSE_ARCHIVING,
-    SHOW_ARCHIVING
+    SHOW_ARCHIVING,
+
+    EXPORT_SCHEMA
   }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/ExportSchemaOperator.java
 
b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/ExportSchemaOperator.java
new file mode 100644
index 0000000000..033680c778
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/ExportSchemaOperator.java
@@ -0,0 +1,49 @@
+/*
+ * 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.qp.logical.sys;
+
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.qp.constant.SQLConstant;
+import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.sys.ExportSchemaPlan;
+import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
+
+import java.io.File;
+
+public class ExportSchemaOperator extends Operator {
+
+  private final File targetDir;
+
+  public ExportSchemaOperator(File targetDir) {
+    super(SQLConstant.TOK_EXPORT_SCHEMA);
+    this.targetDir = targetDir;
+    this.operatorType = OperatorType.EXPORT_SCHEMA;
+  }
+
+  public File getTargetDir() {
+    return targetDir;
+  }
+
+  @Override
+  public PhysicalPlan generatePhysicalPlan(PhysicalGenerator generator)
+      throws QueryProcessException {
+    return new ExportSchemaPlan(targetDir, OperatorType.EXPORT_SCHEMA);
+  }
+}
diff --git 
a/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/ExportSchemaPlan.java
 
b/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/ExportSchemaPlan.java
new file mode 100644
index 0000000000..309a1db152
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/ExportSchemaPlan.java
@@ -0,0 +1,45 @@
+/*
+ * 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.qp.physical.sys;
+
+import org.apache.iotdb.db.metadata.path.PartialPath;
+import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+public class ExportSchemaPlan extends PhysicalPlan {
+  private final File targetDir;
+
+  public ExportSchemaPlan(File targetDir, Operator.OperatorType operatorType) {
+    super(operatorType);
+    this.targetDir = targetDir;
+  }
+
+  public File getTargetDir() {
+    return targetDir;
+  }
+
+  @Override
+  public List<? extends PartialPath> getPaths() {
+    return Collections.emptyList();
+  }
+}
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 bae92be936..78870d3e26 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
@@ -2302,6 +2302,13 @@ public class IoTDBSqlVisitor extends 
IoTDBSqlParserBaseVisitor<Operator> {
         new File(parseFilePath(ctx.dstFileDir.getText())));
   }
 
+  // Export Schema
+
+  @Override
+  public Operator visitExportSchema(IoTDBSqlParser.ExportSchemaContext ctx) {
+    return new ExportSchemaOperator(new 
File(parseFilePath(ctx.dstDir.getText())));
+  }
+
   /** 6. Common Clauses */
 
   // IoTDB Objects
diff --git 
a/server/src/main/java/org/apache/iotdb/db/tools/mlog/ExportSchema.java 
b/server/src/main/java/org/apache/iotdb/db/tools/mlog/ExportSchema.java
new file mode 100644
index 0000000000..4ab6701bad
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/tools/mlog/ExportSchema.java
@@ -0,0 +1,162 @@
+/*
+ * 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.tools.mlog;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.metadata.MManager;
+import org.apache.iotdb.db.metadata.MetadataConstant;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class ExportSchema {
+  private static final Logger logger = 
LoggerFactory.getLogger(ExportSchema.class);
+
+  private static final String EXPORT_PREFIX = "ExportSchema";
+
+  private static final String SOURCE_DIR_ARGS = "d";
+  private static final String SOURCE_DIR_NAME = "source directory path";
+
+  private static final String TARGET_DIR_ARGS = "o";
+  private static final String TARGET_DIR_NAME = "target directory path";
+
+  private static final String HELP_ARGS = "help";
+
+  private final String sourceDir;
+  private final String targetDir;
+
+  private ExportSchema(String sourceDir, String targetDir) {
+    this.sourceDir = sourceDir;
+    this.targetDir = targetDir;
+  }
+
+  /**
+   * create the commandline options.
+   *
+   * @return object Options
+   */
+  public static Options createOptions() {
+    Options options = new Options();
+
+    Option sourceDir =
+        Option.builder(SOURCE_DIR_ARGS)
+            .required()
+            .argName(SOURCE_DIR_NAME)
+            .hasArg()
+            .desc("Need to specify a source directory path")
+            .build();
+    options.addOption(sourceDir);
+
+    Option targetDir =
+        Option.builder(TARGET_DIR_ARGS)
+            .required()
+            .argName(TARGET_DIR_NAME)
+            .hasArg()
+            .desc("Need to specify a target directory path")
+            .build();
+    options.addOption(targetDir);
+
+    Option opHelp =
+        Option.builder(HELP_ARGS)
+            .longOpt(HELP_ARGS)
+            .hasArg(false)
+            .desc("Display help information")
+            .build();
+    options.addOption(opHelp);
+
+    return options;
+  }
+
+  public static void main(String[] args) {
+    //    args =
+    //        new String[] {
+    //          "-d",
+    //          "/Users/chenyanze/Desktop/exportSchema",
+    //          "-o",
+    //          
"/Users/chenyanze/projects/JavaProjects/iotdb/iotdb/data/system/schema"
+    //        };
+    Options options = createOptions();
+    HelpFormatter hf = new HelpFormatter();
+    hf.setOptionComparator(null);
+    CommandLine commandLine;
+    CommandLineParser parser = new DefaultParser();
+    if (args == null || args.length == 0) {
+      logger.warn("Too few params input, please check the following hint.");
+      hf.printHelp(EXPORT_PREFIX, options, true);
+      return;
+    }
+    try {
+      commandLine = parser.parse(options, args);
+    } catch (ParseException e) {
+      logger.error("Parse error: {}", e.getMessage());
+      hf.printHelp(EXPORT_PREFIX, options, true);
+      return;
+    }
+    if (commandLine.hasOption(HELP_ARGS)) {
+      hf.printHelp(EXPORT_PREFIX, options, true);
+      return;
+    }
+
+    String sourceDir = commandLine.getOptionValue(SOURCE_DIR_ARGS);
+    String targetDir = commandLine.getOptionValue(TARGET_DIR_ARGS);
+    File srcDir = new File(sourceDir);
+    if (!srcDir.exists() || !srcDir.isDirectory()) {
+      logger.error(
+          "Encounter an error, because: {} does not exist or is not a 
directory.", sourceDir);
+    } else {
+      File[] files =
+          srcDir.listFiles(
+              (dir, name) ->
+                  MetadataConstant.METADATA_LOG.equals(name)
+                      || MetadataConstant.TAG_LOG.equals(name));
+      if (files == null || files.length != 2) {
+        logger.error("Encounter an error, because: {} is not a valid 
directory.", sourceDir);
+      } else {
+        ExportSchema exportSchema = new ExportSchema(sourceDir, targetDir);
+        try {
+          exportSchema.export();
+        } catch (Exception e) {
+          logger.error("Encounter an error, because: {} ", e.getMessage());
+        } finally {
+          exportSchema.clear();
+        }
+      }
+    }
+  }
+
+  private void export() throws Exception {
+
+    IoTDBDescriptor.getInstance().getConfig().setSchemaDir(sourceDir);
+    MManager.getInstance().init();
+    MManager.getInstance().exportSchema(new File(targetDir));
+  }
+
+  public void clear() {
+    MManager.getInstance().clear();
+  }
+}

Reply via email to