You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by jf...@apache.org on 2020/06/12 10:45:32 UTC

[incubator-iotdb] branch feature/IOTDB-742-structured-logical-types created (now b69225d)

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

jfeinauer pushed a change to branch feature/IOTDB-742-structured-logical-types
in repository https://gitbox.apache.org/repos/asf/incubator-iotdb.git.


      at b69225d  IOTDB-742 Add support for structured / logical types works now on insert.

This branch includes the following new commits:

     new b69225d  IOTDB-742 Add support for structured / logical types works now on insert.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[incubator-iotdb] 01/01: IOTDB-742 Add support for structured / logical types works now on insert.

Posted by jf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/IOTDB-742-structured-logical-types
in repository https://gitbox.apache.org/repos/asf/incubator-iotdb.git

commit b69225d96d57db0c82fc7006028887bceb4452d5
Author: julian <j....@pragmaticminds.de>
AuthorDate: Fri Jun 12 12:45:09 2020 +0200

    IOTDB-742 Add support for structured / logical types works now on insert.
---
 server/pom.xml                                     |   6 +
 .../iotdb/db/metadata/structured/MapType.java      |  96 +++++++++++
 .../db/metadata/structured/PrimitiveType.java      |  94 +++++++++++
 .../iotdb/db/metadata/structured/SManager.java     | 117 +++++++++++++
 .../db/metadata/structured/StructuredType.java     |  34 ++++
 .../apache/iotdb/db/qp/executor/PlanExecutor.java  |   3 +
 .../db/integration/IoTDBInsertStructuredIT.java    | 181 +++++++++++++++++++++
 .../iotdb/db/metadata/structured/SManagerTest.java |  39 +++++
 .../db/metadata/structured/StructuredTypeTest.java |  28 ++++
 9 files changed, 598 insertions(+)

diff --git a/server/pom.xml b/server/pom.xml
index 85968df..6de21a2 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -180,6 +180,12 @@
             <artifactId>oauth2-oidc-sdk</artifactId>
             <version>8.3</version>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>3.15.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/structured/MapType.java b/server/src/main/java/org/apache/iotdb/db/metadata/structured/MapType.java
new file mode 100644
index 0000000..00bb810
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/structured/MapType.java
@@ -0,0 +1,96 @@
+/*
+ * 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 ag [...]
+ */
+
+package org.apache.iotdb.db.metadata.structured;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class MapType implements StructuredType {
+
+    private final Map<String, StructuredType> children;
+
+    public MapType() {
+        this(new HashMap<>());
+    }
+
+    public MapType(Map<String, StructuredType> children) {
+        this.children = children;
+    }
+
+    @Override
+    public boolean isPrimitive() {
+        return false;
+    }
+
+    @Override
+    public TSDataType getPrimitiveType() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public TSEncoding getEncoding() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public CompressionType getCompression() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isArray() {
+        return false;
+    }
+
+    @Override
+    public StructuredType getItem(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isMap() {
+        return true;
+    }
+
+    @Override
+    public Set<String> getKeySet() {
+        return this.children.keySet();
+    }
+
+    @Override
+    public StructuredType getChild(String name) {
+        return this.children.get(name);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MapType mapType = (MapType) o;
+        return Objects.equals(children, mapType.children);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(children);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{\n");
+        for (Map.Entry<String, StructuredType> entry : this.children.entrySet()) {
+            builder.append("\"" + entry.getKey() + "\": " + entry.getValue().toString() + ",\n");
+        }
+        builder.append("}\n");
+        return builder.toString();
+    }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/structured/PrimitiveType.java b/server/src/main/java/org/apache/iotdb/db/metadata/structured/PrimitiveType.java
new file mode 100644
index 0000000..a78ac67
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/structured/PrimitiveType.java
@@ -0,0 +1,94 @@
+/*
+ * 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 ag [...]
+ */
+
+package org.apache.iotdb.db.metadata.structured;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+
+import java.util.Objects;
+import java.util.Set;
+
+public class PrimitiveType implements StructuredType {
+
+    private final TSDataType type;
+    private final TSEncoding encoding;
+    private final CompressionType compression;
+
+    public PrimitiveType(TSDataType type, TSEncoding encoding, CompressionType compression) {
+        this.type = type;
+        this.encoding = encoding;
+        this.compression = compression;
+    }
+
+    @Override
+    public boolean isPrimitive() {
+        return true;
+    }
+
+    @Override
+    public TSDataType getPrimitiveType() {
+        return this.type;
+    }
+
+    @Override
+    public TSEncoding getEncoding() {
+        return this.encoding;
+    }
+
+    @Override
+    public CompressionType getCompression() {
+        return this.compression;
+    }
+
+    @Override
+    public boolean isArray() {
+        return false;
+    }
+
+    @Override
+    public StructuredType getItem(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isMap() {
+        return false;
+    }
+
+    @Override
+    public Set<String> getKeySet() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StructuredType getChild(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PrimitiveType that = (PrimitiveType) o;
+        return type == that.type &&
+                getEncoding() == that.getEncoding() &&
+                getCompression() == that.getCompression();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, getEncoding(), getCompression());
+    }
+
+    @Override
+    public String toString() {
+        return "PrimitiveType{" +
+                "type=" + type +
+                ", encoding=" + encoding +
+                ", compression=" + compression +
+                '}';
+    }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/structured/SManager.java b/server/src/main/java/org/apache/iotdb/db/metadata/structured/SManager.java
new file mode 100644
index 0000000..668dc7c
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/structured/SManager.java
@@ -0,0 +1,117 @@
+/*
+ * 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 ag [...]
+ */
+
+package org.apache.iotdb.db.metadata.structured;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.sun.tools.corba.se.idl.InvalidArgument;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.qp.physical.crud.InsertPlan;
+import org.apache.iotdb.db.utils.CommonUtils;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class takes responsibility of Managing all known and relevant structured-type information.
+ */
+public class SManager {
+
+    private static final Logger logger = LoggerFactory.getLogger(SManager.class);
+
+    private static final SManager INSTANCE = new SManager();
+
+    private final Map<String, StructuredType> registeredTypes;
+
+    public static SManager getInstance() {
+        return SManager.INSTANCE;
+    }
+
+    public SManager() {
+        registeredTypes = new HashMap<>();
+    }
+
+    public void register(String name, StructuredType type) {
+        this.registeredTypes.put(name, type);
+    }
+
+    /**
+     * Removes all structured type information and
+     * transforms the plan in a primitive plan
+     * that is handled downstream without any changes.
+     */
+    public InsertPlan translate(InsertPlan plan) {
+        for (TSDataType type : plan.getTypes()) {
+            if (type != null) {
+                throw new IllegalArgumentException("Plan already has type information");
+            }
+        }
+        List<String> measurements = new ArrayList<>();
+        List<TSDataType> types = new ArrayList<>();
+        List<Object> newValues = new ArrayList<>();
+        // First, we check if it contains a structured type
+        Object[] values = plan.getValues();
+        for (int i = 0; i < values.length; i++) {
+            Object value = values[i];
+            if (value instanceof String && ((String) value).contains("::")) {
+                String str = StringUtils.strip(((String) value), "\"");
+                str = StringEscapeUtils.unescapeJava(str);
+                logger.info("Contains structured type for measurement '{}', rewriting...", plan.getMeasurements()[i]);
+                // Now first parse the thing
+                String jsonString = str.substring(0, str.indexOf("::"));
+                String structName = str.substring(str.indexOf("::") + 2);
+
+                // Lookup the type
+                if (!this.registeredTypes.containsKey(structName)) {
+                    throw new IllegalArgumentException("Plan references the Unknown Type '" + structName + "'!");
+                }
+
+                StructuredType type = this.registeredTypes.get(structName);
+
+                // Now we have all type information and can use the type to get all elements
+                if (type.isMap()) {
+
+                    JSONObject map = JSON.parseObject(jsonString);
+
+                    for (String key : type.getKeySet()) {
+                        if (!(type.getChild(key) instanceof PrimitiveType)) {
+                            throw new NotImplementedException("No nesting is supported currently!");
+                        }
+                        // Fetch the value
+                        if (!map.containsKey(key)) {
+                            throw new IllegalArgumentException("Value String misses the requested field '" + key + "'");
+                        }
+                        measurements.add(plan.getMeasurements()[i] + "." + key);
+                        TSDataType primitiveType = type.getChild(key).getPrimitiveType();
+                        types.add(primitiveType);
+                        try {
+                            newValues.add(CommonUtils.parseValue(primitiveType, map.getString(key)));
+                        } catch (QueryProcessException e) {
+                            throw new IllegalArgumentException("Error parsing given structured Object", e);
+                        }
+                    }
+                } else {
+                    throw new NotImplementedException("Only type Map is supported currently!");
+                }
+            } else {
+                // Do inference here
+                measurements.add(plan.getMeasurements()[i]);
+                types.add(plan.getTypes()[i]);
+                newValues.add(value);
+            }
+        }
+        return new InsertPlan(plan.getDeviceId(), plan.getTime(), measurements.toArray(new String[0]), types.toArray(new TSDataType[0]), newValues.toArray(new Object[0]));
+    }
+
+
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/structured/StructuredType.java b/server/src/main/java/org/apache/iotdb/db/metadata/structured/StructuredType.java
new file mode 100644
index 0000000..73900a8
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/structured/StructuredType.java
@@ -0,0 +1,34 @@
+/*
+ * 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 ag [...]
+ */
+
+package org.apache.iotdb.db.metadata.structured;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+
+import java.util.Set;
+
+/**
+ * Describes a Structured Type.
+ */
+public interface StructuredType {
+
+    boolean isPrimitive();
+
+    TSDataType getPrimitiveType();
+    TSEncoding getEncoding();
+    CompressionType getCompression();
+
+    boolean isArray();
+
+    StructuredType getItem(int index);
+
+    boolean isMap();
+
+    Set<String> getKeySet();
+
+    StructuredType getChild(String name);
+
+}
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 2069163..1d16f46 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
@@ -80,6 +80,7 @@ import org.apache.iotdb.db.metadata.mnode.InternalMNode;
 import org.apache.iotdb.db.metadata.mnode.MeasurementMNode;
 import org.apache.iotdb.db.metadata.mnode.MNode;
 import org.apache.iotdb.db.metadata.mnode.StorageGroupMNode;
+import org.apache.iotdb.db.metadata.structured.SManager;
 import org.apache.iotdb.db.qp.logical.Operator.OperatorType;
 import org.apache.iotdb.db.qp.logical.sys.AuthorOperator;
 import org.apache.iotdb.db.qp.logical.sys.AuthorOperator.AuthorType;
@@ -889,6 +890,8 @@ public class PlanExecutor implements IPlanExecutor {
   @Override
   public void insert(InsertPlan insertPlan) throws QueryProcessException {
     try {
+      // First do structured types resolution
+      insertPlan = SManager.getInstance().translate(insertPlan);
       MeasurementSchema[] schemas = getSeriesSchemas(insertPlan);
       insertPlan.setSchemasAndTransferType(schemas);
       StorageEngine.getInstance().insert(insertPlan);
diff --git a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBInsertStructuredIT.java b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBInsertStructuredIT.java
new file mode 100644
index 0000000..8f8cd44
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBInsertStructuredIT.java
@@ -0,0 +1,181 @@
+/*
+ * 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.structured.MapType;
+import org.apache.iotdb.db.metadata.structured.PrimitiveType;
+import org.apache.iotdb.db.metadata.structured.SManager;
+import org.apache.iotdb.db.metadata.structured.StructuredType;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.assertj.core.api.WithAssertions;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.sql.*;
+import java.util.HashMap;
+
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+public class IoTDBInsertStructuredIT implements WithAssertions {
+
+  private static String[] sqls = new String[]{
+  };
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvironmentUtils.closeStatMonitor();
+    EnvironmentUtils.envSetUp();
+
+    insertData();
+
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvironmentUtils.cleanEnv();
+  }
+
+  private static void insertData() throws ClassNotFoundException {
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection = DriverManager
+        .getConnection(Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+         Statement statement = connection.createStatement()) {
+
+      for (String sql : sqls) {
+        statement.execute(sql);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Test
+  public void showTimeseries() throws ClassNotFoundException {
+    String[] retArray = new String[]{
+        "root.sg1.d1.\"coordinates.lat\",null,root.sg1,DOUBLE,GORILLA,SNAPPY,",
+        "root.sg1.d1.\"coordinates.long\",null,root.sg1,DOUBLE,GORILLA,SNAPPY,",
+    };
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection = DriverManager
+        .getConnection(Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+         Statement statement = connection.createStatement()) {
+
+      // Prepare type
+      SManager.getInstance().register("gps", gpsType());
+
+      // Insert value
+      statement.execute("INSERT INTO root.sg1.d1 (timestamp, coordinates) VALUES (NOW(), \"{\\\"lat\\\":40.0, \\\"long\\\":20.0}::gps\")");
+
+      boolean hasResultSet = statement.execute(
+          "SHOW TIMESERIES");
+      Assert.assertTrue(hasResultSet);
+
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+        StringBuilder header = new StringBuilder();
+        for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+          header.append(resultSetMetaData.getColumnName(i)).append(",");
+        }
+        Assert.assertEquals("timeseries,alias,storage group,dataType,encoding,compression,", header.toString());
+        Assert.assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(1));
+
+        int cnt = 0;
+        while (resultSet.next()) {
+          StringBuilder builder = new StringBuilder();
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            builder.append(resultSet.getString(i)).append(",");
+          }
+          Assert.assertEquals(retArray[cnt], builder.toString());
+          cnt++;
+        }
+        Assert.assertEquals(retArray.length, cnt);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void insertMissingField_fails() throws ClassNotFoundException {
+    String[] retArray = new String[]{
+            "root.sg1.d1.\"coordinates.lat\",null,root.sg1,DOUBLE,GORILLA,SNAPPY,",
+            "root.sg1.d1.\"coordinates.long\",null,root.sg1,DOUBLE,GORILLA,SNAPPY,",
+    };
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection = DriverManager
+            .getConnection(Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+         Statement statement = connection.createStatement()) {
+
+      // Prepare type
+      SManager.getInstance().register("gps", gpsType());
+
+      // Insert value
+      assertThatThrownBy(() -> statement.execute("INSERT INTO root.sg1.d1 (timestamp, coordinates) VALUES (NOW(), \"{\\\"lat\\\":40.0}::gps\")"))
+              .hasMessage("500: Value String misses the requested field 'long'");
+
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void insertUnknownType_fails() throws ClassNotFoundException {
+    String[] retArray = new String[]{
+            "root.sg1.d1.\"coordinates.lat\",null,root.sg1,DOUBLE,GORILLA,SNAPPY,",
+            "root.sg1.d1.\"coordinates.long\",null,root.sg1,DOUBLE,GORILLA,SNAPPY,",
+    };
+
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection = DriverManager
+            .getConnection(Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+         Statement statement = connection.createStatement()) {
+
+      // Prepare type
+      SManager.getInstance().register("gps", gpsType());
+
+      // Insert value
+      assertThatThrownBy(() -> statement.execute("INSERT INTO root.sg1.d1 (timestamp, coordinates) VALUES (NOW(), \"{\\\"lat\\\":40.0}::unknown_type\")"))
+              .hasMessage("500: Plan references the Unknown Type 'unknown_type'!");
+
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private StructuredType gpsType() {
+    HashMap<String, StructuredType> children = new HashMap<>();
+    children.put("lat", new PrimitiveType(TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.SNAPPY));
+    children.put("long", new PrimitiveType(TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.SNAPPY));
+
+    return new MapType(children);
+  }
+
+}
diff --git a/server/src/test/java/org/apache/iotdb/db/metadata/structured/SManagerTest.java b/server/src/test/java/org/apache/iotdb/db/metadata/structured/SManagerTest.java
new file mode 100644
index 0000000..ec1747a
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/structured/SManagerTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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 ag [...]
+ */
+
+package org.apache.iotdb.db.metadata.structured;
+
+import org.apache.iotdb.db.qp.physical.crud.InsertPlan;
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.junit.Test;
+
+import java.util.HashMap;
+
+import static org.junit.Assert.*;
+
+public class SManagerTest {
+
+    @Test
+    public void translateSimplePlan() {
+        SManager sManager = new SManager();
+
+        sManager.register("gps", gpsType());
+
+        InsertPlan plan = sManager.translate(new InsertPlan("root.sg1.d1", 0, new String[]{"gps"}, new String[]{"{ \"lat\" : 40.0, \"long\" : 20.0}::gps"}));
+
+        assertArrayEquals(new String[]{ "gps.lat", "gps.long" }, plan.getMeasurements());
+        assertArrayEquals(new TSDataType[]{ TSDataType.DOUBLE, TSDataType.DOUBLE }, plan.getTypes());
+        assertArrayEquals(new Object[]{ 40.0, 20.0 }, plan.getValues());
+    }
+
+    private StructuredType gpsType() {
+        HashMap<String, StructuredType> children = new HashMap<>();
+        children.put("lat", new PrimitiveType(TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.SNAPPY));
+        children.put("long", new PrimitiveType(TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.SNAPPY));
+
+        return new MapType(children);
+    }
+}
\ No newline at end of file
diff --git a/server/src/test/java/org/apache/iotdb/db/metadata/structured/StructuredTypeTest.java b/server/src/test/java/org/apache/iotdb/db/metadata/structured/StructuredTypeTest.java
new file mode 100644
index 0000000..e029ef7
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/structured/StructuredTypeTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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 ag [...]
+ */
+
+package org.apache.iotdb.db.metadata.structured;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.junit.Test;
+
+import java.util.HashMap;
+
+import static org.junit.Assert.*;
+
+public class StructuredTypeTest {
+
+    @Test
+    public void describeGPS() {
+        HashMap<String, StructuredType> children = new HashMap<>();
+        children.put("latitude", new PrimitiveType(TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.SNAPPY));
+        children.put("longitude", new PrimitiveType(TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.SNAPPY));
+
+        MapType gpsStructure = new MapType(children);
+
+        System.out.println(gpsStructure.toString());
+    }
+}
\ No newline at end of file