You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by hu...@apache.org on 2022/10/11 09:48:25 UTC

[iotdb] 04/04: tmp save (analyzer for SELECT INTO)

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

hui pushed a commit to branch lmh/mppSelectInto
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit fb63d17c6b28c0a1e4d88fdd6fc21cb44b2d256c
Author: Minghui Liu <li...@foxmail.com>
AuthorDate: Mon Oct 10 22:26:47 2022 +0800

    tmp save (analyzer for SELECT INTO)
---
 .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4   |   6 +-
 .../db/metadata/path/PatternTreeMapFactory.java    |   8 +
 .../apache/iotdb/db/mpp/plan/analyze/Analysis.java |  34 +--
 .../iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java  | 233 ++++++++++++++++++---
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       |  42 ++--
 .../component/AlignByDeviceIntoComponent.java      |  40 ----
 .../component/AlignByTimeIntoComponent.java        |  38 ----
 .../plan/statement/component/IntoComponent.java    |  25 ++-
 .../db/mpp/plan/statement/component/IntoItem.java  |  73 +++++++
 .../db/mpp/plan/statement/crud/QueryStatement.java |  10 -
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    |  14 +-
 11 files changed, 354 insertions(+), 169 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 d49d19b11e..591de9e067 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
@@ -381,7 +381,7 @@ selectStatement
 
 intoClause
     : INTO ALIGNED? intoPath (COMMA intoPath)*
-    | INTO ALIGNED? intoDeviceAndMeasurement (COMMA intoDeviceAndMeasurement)*
+    | INTO intoItem (COMMA intoItem)*
     ;
 
 intoPath
@@ -389,8 +389,8 @@ intoPath
     | nodeNameInIntoPath (DOT nodeNameInIntoPath)* #suffixPathInIntoPath
     ;
 
-intoDeviceAndMeasurement
-    : intoPath LR_BRACKET nodeNameInIntoPath (COMMA nodeNameInIntoPath)* RR_BRACKET
+intoItem
+    : ALIGNED? intoPath LR_BRACKET nodeNameInIntoPath (COMMA nodeNameInIntoPath)* RR_BRACKET
     ;
 
 nodeNameInIntoPath
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/path/PatternTreeMapFactory.java b/server/src/main/java/org/apache/iotdb/db/metadata/path/PatternTreeMapFactory.java
index fe2e55869e..e1ce413315 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/path/PatternTreeMapFactory.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/path/PatternTreeMapFactory.java
@@ -49,6 +49,14 @@ public class PatternTreeMapFactory {
         HashSet::new, (mod, set) -> set.add(mod), null, ModsSerializer.getInstance());
   }
 
+  public static PatternTreeMap<String, StringSerializer> getIntoPathPatternTreeMap() {
+    return new PatternTreeMap<>(
+        HashSet::new,
+        (columnName, set) -> set.add(columnName),
+        (columnName, set) -> set.remove(columnName),
+        StringSerializer.getInstance());
+  }
+
   public static class ModsSerializer implements PathPatternNode.Serializer<Modification> {
 
     @Override
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
index a5140a68e3..119f67654a 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
@@ -24,6 +24,8 @@ import org.apache.iotdb.common.rpc.thrift.TSchemaNode;
 import org.apache.iotdb.commons.partition.DataPartition;
 import org.apache.iotdb.commons.partition.SchemaPartition;
 import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.path.PatternTreeMap;
+import org.apache.iotdb.db.metadata.path.PatternTreeMapFactory;
 import org.apache.iotdb.db.metadata.template.Template;
 import org.apache.iotdb.db.mpp.common.NodeRef;
 import org.apache.iotdb.db.mpp.common.header.DatasetHeader;
@@ -86,9 +88,6 @@ public class Analysis {
   // map from grouped path name to list of input aggregation in `GROUP BY LEVEL` clause
   private Map<Expression, Set<Expression>> groupByLevelExpressions;
 
-  // map from output column to target into path
-  private Map<String, PartialPath> outputColumnToIntoPathMap;
-
   /////////////////////////////////////////////////////////////////////////////////////////////////
   // Query Analysis (used in ALIGN BY DEVICE)
   /////////////////////////////////////////////////////////////////////////////////////////////////
@@ -114,9 +113,6 @@ public class Analysis {
 
   private Set<Expression> deviceViewOutputExpressions;
 
-  // map from device name to target into path of each output column
-  private Map<String, Map<String, PartialPath>> deviceToIntoPathMap;
-
   /////////////////////////////////////////////////////////////////////////////////////////////////
   // Query Common Analysis (above DeviceView)
   /////////////////////////////////////////////////////////////////////////////////////////////////
@@ -143,6 +139,14 @@ public class Analysis {
   // header of result dataset
   private DatasetHeader respDatasetHeader;
 
+  /////////////////////////////////////////////////////////////////////////////////////////////////
+  // SELECT INTO Analysis (used in ALIGN BY DEVICE)
+  /////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private PatternTreeMap<String, PatternTreeMapFactory.StringSerializer> intoPathPatternTreeMap;
+
+  private Map<PartialPath, Boolean> intoDeviceToAlignedMap;
+
   /////////////////////////////////////////////////////////////////////////////////////////////////
   // Schema Query Analysis
   /////////////////////////////////////////////////////////////////////////////////////////////////
@@ -410,19 +414,21 @@ public class Analysis {
     this.deviceViewOutputExpressions = deviceViewOutputExpressions;
   }
 
-  public Map<String, PartialPath> getOutputColumnToIntoPathMap() {
-    return outputColumnToIntoPathMap;
+  public PatternTreeMap<String, PatternTreeMapFactory.StringSerializer>
+      getIntoPathPatternTreeMap() {
+    return intoPathPatternTreeMap;
   }
 
-  public void setOutputColumnToIntoPathMap(Map<String, PartialPath> outputColumnToIntoPathMap) {
-    this.outputColumnToIntoPathMap = outputColumnToIntoPathMap;
+  public void setIntoPathPatternTreeMap(
+      PatternTreeMap<String, PatternTreeMapFactory.StringSerializer> intoPathPatternTreeMap) {
+    this.intoPathPatternTreeMap = intoPathPatternTreeMap;
   }
 
-  public Map<String, Map<String, PartialPath>> getDeviceToIntoPathMap() {
-    return deviceToIntoPathMap;
+  public Map<PartialPath, Boolean> getIntoDeviceToAlignedMap() {
+    return intoDeviceToAlignedMap;
   }
 
-  public void setDeviceToIntoPathMap(Map<String, Map<String, PartialPath>> deviceToIntoPathMap) {
-    this.deviceToIntoPathMap = deviceToIntoPathMap;
+  public void setIntoDeviceToAlignedMap(Map<PartialPath, Boolean> intoDeviceToAlignedMap) {
+    this.intoDeviceToAlignedMap = intoDeviceToAlignedMap;
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
index d0d32de590..2731e84907 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
@@ -29,6 +29,7 @@ import org.apache.iotdb.commons.partition.SchemaPartition;
 import org.apache.iotdb.commons.path.MeasurementPath;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.path.PathPatternTree;
+import org.apache.iotdb.commons.path.PatternTreeMap;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.engine.storagegroup.TsFileResource;
 import org.apache.iotdb.db.engine.storagegroup.TsFileResourceStatus;
@@ -38,6 +39,7 @@ import org.apache.iotdb.db.exception.metadata.template.TemplateImcompatibeExcept
 import org.apache.iotdb.db.exception.sql.MeasurementNotExistException;
 import org.apache.iotdb.db.exception.sql.SemanticException;
 import org.apache.iotdb.db.exception.sql.StatementAnalyzeException;
+import org.apache.iotdb.db.metadata.path.PatternTreeMapFactory;
 import org.apache.iotdb.db.metadata.template.Template;
 import org.apache.iotdb.db.mpp.common.MPPQueryContext;
 import org.apache.iotdb.db.mpp.common.header.ColumnHeader;
@@ -56,9 +58,10 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.OrderByParameter;
 import org.apache.iotdb.db.mpp.plan.statement.Statement;
 import org.apache.iotdb.db.mpp.plan.statement.StatementNode;
 import org.apache.iotdb.db.mpp.plan.statement.StatementVisitor;
-import org.apache.iotdb.db.mpp.plan.statement.component.AlignByTimeIntoComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.FillComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.GroupByTimeComponent;
+import org.apache.iotdb.db.mpp.plan.statement.component.IntoComponent;
+import org.apache.iotdb.db.mpp.plan.statement.component.IntoItem;
 import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
 import org.apache.iotdb.db.mpp.plan.statement.component.ResultColumn;
 import org.apache.iotdb.db.mpp.plan.statement.component.SortItem;
@@ -102,6 +105,7 @@ import org.apache.iotdb.db.mpp.plan.statement.metadata.template.ShowSchemaTempla
 import org.apache.iotdb.db.mpp.plan.statement.sys.ExplainStatement;
 import org.apache.iotdb.db.mpp.plan.statement.sys.ShowVersionStatement;
 import org.apache.iotdb.db.mpp.plan.statement.sys.sync.ShowPipeSinkTypeStatement;
+import org.apache.iotdb.db.qp.constant.SQLConstant;
 import org.apache.iotdb.db.query.control.SessionManager;
 import org.apache.iotdb.db.utils.FileLoaderUtils;
 import org.apache.iotdb.db.utils.TimePartitionUtils;
@@ -111,7 +115,6 @@ import org.apache.iotdb.tsfile.file.header.ChunkHeader;
 import org.apache.iotdb.tsfile.file.metadata.IChunkMetadata;
 import org.apache.iotdb.tsfile.file.metadata.TimeseriesMetadata;
 import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
-import org.apache.iotdb.db.qp.constant.SQLConstant;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
 import org.apache.iotdb.tsfile.read.TsFileSequenceReader;
@@ -140,6 +143,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.regex.Matcher;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkState;
@@ -233,6 +237,8 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
 
         analyzeDeviceToSource(analysis, queryStatement);
         analyzeDeviceView(analysis, queryStatement, outputExpressions);
+
+        analyzeInto(analysis, queryStatement, deviceSet, outputExpressions);
       } else {
         outputExpressions = analyzeSelect(analysis, queryStatement, schemaTree);
 
@@ -251,6 +257,8 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
         analyzeSourceTransform(analysis, queryStatement);
 
         analyzeSource(analysis, queryStatement);
+
+        analyzeInto(analysis, queryStatement, outputExpressions);
       }
 
       analyzeGroupBy(analysis, queryStatement);
@@ -988,40 +996,212 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
     return partitionFetcher.getDataPartition(sgNameToQueryParamsMap);
   }
 
-  private void analyzeInto(Analysis analysis, List<Pair<Expression, String>> outputExpressions) {
-    Map<String, PartialPath> outputColumnToIntoPathMap = new HashMap<>();
+  private void analyzeInto(
+      Analysis analysis,
+      QueryStatement queryStatement,
+      Set<PartialPath> deviceSet,
+      List<Pair<Expression, String>> outputExpressions) {
     List<Expression> outputColumns =
-            outputExpressions.stream()
-                    .map(Pair::getLeft)
-                    .collect(Collectors.toCollection(ArrayList::new));
-    AlignByTimeIntoComponent intoComponent =
-            ((AlignByTimeIntoComponent) ((QueryStatement) analysis.getStatement()).getIntoComponent());
-    List<PartialPath> intoPaths = intoComponent.getIntoPaths();
-    boolean isAligned = intoComponent.isAligned();
-
+        outputExpressions.stream()
+            .map(Pair::getLeft)
+            .collect(Collectors.toCollection(ArrayList::new));
     boolean isAllRawSeriesQuery = checkIsAllRawSeriesQuery(outputColumns);
+
+    IntoComponent intoComponent = queryStatement.getIntoComponent();
+    List<IntoItem> intoItems = intoComponent.getIntoItems();
+
+    Map<PartialPath, PartialPath> sourceDeviceToTargetDeviceMap = new HashMap<>();
+    List<PartialPath> sourceDevices = new ArrayList<>(deviceSet);
+    if (intoComponent.isDeviceExistPlaceholder()) {
+      if (intoItems.size() > 1) {
+        throw new SemanticException("");
+      }
+
+      PartialPath deviceTemplate = intoItems.get(0).getIntoDevice();
+      for (int i = 0; i < sourceDevices.size(); i++) {
+        PartialPath sourceDevice = sourceDevices.get(i);
+        PartialPath targetDevice = constructIntoDevice(sourceDevice, deviceTemplate);
+        if (sourceDeviceToTargetDeviceMap.containsKey(sourceDevice)
+            && !sourceDeviceToTargetDeviceMap.get(sourceDevice).equals(targetDevice)) {
+          throw new SemanticException("");
+        }
+        sourceDeviceToTargetDeviceMap.put(sourceDevices.get(i), intoItems.get(i).getIntoDevice());
+      }
+    } else {
+      if (intoItems.size() != sourceDevices.size()) {
+        throw new SemanticException("");
+      }
+
+      for (int i = 0; i < sourceDevices.size(); i++) {
+        PartialPath sourceDevice = sourceDevices.get(i);
+        PartialPath targetDevice = intoItems.get(i).getIntoDevice();
+        if (sourceDeviceToTargetDeviceMap.containsKey(sourceDevice)
+            && !sourceDeviceToTargetDeviceMap.get(sourceDevice).equals(targetDevice)) {
+          throw new SemanticException("");
+        }
+        sourceDeviceToTargetDeviceMap.put(sourceDevices.get(i), intoItems.get(i).getIntoDevice());
+      }
+    }
+
+    PatternTreeMap<String, PatternTreeMapFactory.StringSerializer> intoPathPatternTreeMap =
+        PatternTreeMapFactory.getIntoPathPatternTreeMap();
+
     if (isAllRawSeriesQuery) {
+
+    } else {
+      // disable placeholder
+      for (IntoItem intoItem : intoItems) {
+        if (intoItem.isMeasurementsExistPlaceholder()) {
+          throw new SemanticException(
+              "select into: placeholders can only be used in raw timeseries data queries.");
+        }
+      }
+
+      List<PartialPath> intoPaths =
+          intoItems.stream()
+              .map(IntoItem::getIntoPaths)
+              .flatMap(List::stream)
+              .collect(Collectors.toList());
+
+      // check quantity consistency
       if (intoPaths.size() != outputColumns.size()) {
         throw new SemanticException(
-                "select into: the number of source paths and the number of target paths should be the same.");
+            "select into: the number of source columns and the number of target paths should be the same.");
       }
-      if (intoPaths.size() > new HashSet<>(intoPaths).size()) {
-        throw new SemanticException(
-                "select into: target paths in into clause should be different.");
+
+      for (int i = 0; i < intoPaths.size(); i++) {
+        if (intoPathPatternTreeMap.getOverlapped(intoPaths.get(i)).size() > 1) {
+          throw new SemanticException(
+              "select into: target paths in into clause should be different.");
+        }
+        intoPathPatternTreeMap.append(intoPaths.get(i), outputColumns.get(i).toString());
       }
-      for (int i = 0; i < outputColumns.size(); i++) {
-        outputColumnToIntoPathMap.put(
-                outputColumns.get(i).toString(),
-                constructIntoPath(analysis, outputColumns.get(i), intoPaths.get(i), isAligned));
+    }
+    analyzeIntoDevices(analysis, intoItems);
+    analysis.setIntoPathPatternTreeMap(intoPathPatternTreeMap);
+  }
+
+  private PartialPath constructIntoDevice(PartialPath sourceDevice, PartialPath deviceTemplate) {
+    String[] sourceNodes = sourceDevice.getNodes();
+    String[] templateNodes = deviceTemplate.getNodes();
+
+    List<String> targetNodes = new ArrayList<>();
+    for (int nodeIndex = 0; nodeIndex < templateNodes.length; nodeIndex++) {
+      String curNode = templateNodes[nodeIndex];
+      if (curNode.equals(DOUBLE_COLONS)) {
+        if (nodeIndex != templateNodes.length - 1) {
+          throw new SemanticException("");
+        }
+        // copy
+        for (; nodeIndex < sourceNodes.length; nodeIndex++) {
+          targetNodes.add(sourceNodes[nodeIndex]);
+        }
+        break;
       }
+
+      Matcher m = LEVELED_PATH_TEMPLATE_PATTERN.matcher(curNode);
+      String resNode = curNode;
+      while (m.find()) {
+        String param = m.group();
+        int index;
+        try {
+          index = Integer.parseInt(param.substring(2, param.length() - 1).trim());
+        } catch (NumberFormatException e) {
+          throw new SemanticException("select into: the i of ${i} should be an integer.");
+        }
+        if (index < 1 || index >= sourceNodes.length) {
+          throw new SemanticException(
+              "select into: the i of ${i} should be greater than 0 and equal to or less than the length of queried path prefix.");
+        }
+      }
+      targetNodes.add(resNode);
+    }
+    return new PartialPath(targetNodes.toArray(new String[0]));
+  }
+
+  private void analyzeInto(
+      Analysis analysis,
+      QueryStatement queryStatement,
+      List<Pair<Expression, String>> outputExpressions) {
+    if (queryStatement.getIntoComponent() == null) {
+      return;
+    }
+
+    List<Expression> outputColumns =
+        outputExpressions.stream()
+            .map(Pair::getLeft)
+            .collect(Collectors.toCollection(ArrayList::new));
+    boolean isAllRawSeriesQuery = checkIsAllRawSeriesQuery(outputColumns);
+
+    IntoComponent intoComponent = queryStatement.getIntoComponent();
+    List<IntoItem> intoItems = intoComponent.getIntoItems();
+
+    PatternTreeMap<String, PatternTreeMapFactory.StringSerializer> intoPathPatternTreeMap =
+        PatternTreeMapFactory.getIntoPathPatternTreeMap();
+
+    if (isAllRawSeriesQuery) {
+
     } else {
+      // disable placeholder
+      for (IntoItem intoItem : intoItems) {
+        if (intoItem.isDeviceExistPlaceholder() || intoItem.isMeasurementsExistPlaceholder()) {
+          throw new SemanticException(
+              "select into: placeholders can only be used in raw timeseries data queries.");
+        }
+      }
 
+      List<PartialPath> intoPaths =
+          intoItems.stream()
+              .map(IntoItem::getIntoPaths)
+              .flatMap(List::stream)
+              .collect(Collectors.toList());
+
+      // check quantity consistency
+      if (intoPaths.size() != outputColumns.size()) {
+        throw new SemanticException(
+            "select into: the number of source columns and the number of target paths should be the same.");
+      }
+
+      for (int i = 0; i < intoPaths.size(); i++) {
+        intoPathPatternTreeMap.append(intoPaths.get(i), outputColumns.get(i).toString());
+        if (intoPathPatternTreeMap.getOverlapped(intoPaths.get(i)).size() > 1) {
+          throw new SemanticException(
+              "select into: target paths in into clause should be different.");
+        }
+      }
     }
-    analysis.setOutputColumnToIntoPathMap(outputColumnToIntoPathMap);
+    analyzeIntoDevices(analysis, intoItems);
+    analysis.setIntoPathPatternTreeMap(intoPathPatternTreeMap);
+  }
+
+  private void analyzeIntoDevices(Analysis analysis, List<IntoItem> intoItems) {
+    Map<PartialPath, Boolean> intoDeviceToAlignedMap = new HashMap<>();
+    for (IntoItem intoItem : intoItems) {
+      PartialPath devicePath = intoItem.getIntoDevice();
+      boolean isAligned = intoItem.isAligned();
+      if (intoDeviceToAlignedMap.containsKey(devicePath)
+          && intoDeviceToAlignedMap.get(devicePath) != isAligned) {
+        throw new SemanticException(
+            String.format(
+                "select into: inconsistent alignment property specified for device '%s'.",
+                devicePath));
+      }
+      intoDeviceToAlignedMap.put(devicePath, isAligned);
+    }
+    analysis.setIntoDeviceToAlignedMap(intoDeviceToAlignedMap);
+  }
+
+  private boolean checkIsAllRawSeriesQuery(List<Expression> expressions) {
+    for (Expression expression : expressions) {
+      if (!(expression instanceof TimeSeriesOperand)) {
+        return true;
+      }
+    }
+    return false;
   }
 
   private PartialPath constructIntoPath(
-          Analysis analysis, Expression outputColumn, PartialPath path, boolean isAligned) {
+      Analysis analysis, Expression outputColumn, PartialPath path, boolean isAligned) {
     if (!path.startWith(SQLConstant.ROOT)) {
       throw new SemanticException("select into: ");
     }
@@ -1034,15 +1214,6 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
     return new MeasurementPath(path, analysis.getType(outputColumn), isAligned);
   }
 
-  private void analyzeInto(
-          Analysis analysis,
-          List<Pair<Expression, String>> outputExpressions,
-          Map<String, Set<String>> deviceToMeasurementsMap) {
-    Map<String, Map<String, PartialPath>> deviceToIntoPathMap = new HashMap<>();
-
-    analysis.setDeviceToIntoPathMap(deviceToIntoPathMap);
-  }
-
   /**
    * Check datatype consistency in ALIGN BY DEVICE.
    *
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
index 38391e156d..c4f9ddf867 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
@@ -63,14 +63,14 @@ import org.apache.iotdb.db.mpp.plan.expression.unary.LogicNotExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.NegationExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.RegularExpression;
 import org.apache.iotdb.db.mpp.plan.statement.Statement;
-import org.apache.iotdb.db.mpp.plan.statement.component.AlignByDeviceIntoComponent;
-import org.apache.iotdb.db.mpp.plan.statement.component.AlignByTimeIntoComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.FillComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.FillPolicy;
 import org.apache.iotdb.db.mpp.plan.statement.component.FromComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.GroupByLevelComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.GroupByTimeComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.HavingCondition;
+import org.apache.iotdb.db.mpp.plan.statement.component.IntoComponent;
+import org.apache.iotdb.db.mpp.plan.statement.component.IntoItem;
 import org.apache.iotdb.db.mpp.plan.statement.component.OrderByComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
 import org.apache.iotdb.db.mpp.plan.statement.component.ResultColumn;
@@ -1369,31 +1369,27 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
   // parse INTO clause
 
   private void parseIntoClause(IoTDBSqlParser.IntoClauseContext ctx) {
-    boolean isAligned = ctx.ALIGNED() != null;
-    if (ctx.intoDeviceAndMeasurement().size() > 0) {
-      List<Pair<PartialPath, List<PartialPath>>> intoDeviceAndMeasurementList = new ArrayList<>();
-      for (IoTDBSqlParser.IntoDeviceAndMeasurementContext intoDeviceAndMeasurementContext :
-          ctx.intoDeviceAndMeasurement()) {
-        PartialPath intoDevice = parseIntoPath(intoDeviceAndMeasurementContext.intoPath());
-        List<PartialPath> intoMeasurements =
-            intoDeviceAndMeasurementContext.nodeNameInIntoPath().stream()
-                .map(
-                    nodeNameInIntoPathContext ->
-                        new PartialPath(parseNodeNameInIntoPath(nodeNameInIntoPathContext), false))
-                .collect(Collectors.toList());
-        intoDeviceAndMeasurementList.add(new Pair<>(intoDevice, intoMeasurements));
-      }
-      queryStatement.setIntoComponent(
-          new AlignByDeviceIntoComponent(intoDeviceAndMeasurementList, isAligned));
-    } else {
-      List<PartialPath> intoPaths = new ArrayList<>();
-      for (IoTDBSqlParser.IntoPathContext intoPathContext : ctx.intoPath()) {
-        intoPaths.add(parseIntoPath(intoPathContext));
+    if (ctx.intoItem().size() > 0) {
+      List<IntoItem> intoItems = new ArrayList<>();
+      for (IoTDBSqlParser.IntoItemContext intoItemContext : ctx.intoItem()) {
+        intoItems.add(parseIntoItem(intoItemContext));
       }
-      queryStatement.setIntoComponent(new AlignByTimeIntoComponent(intoPaths, isAligned));
+      queryStatement.setIntoComponent(new IntoComponent(intoItems));
+    } else {
+      throw new SemanticException("The syntax of SELECT INTO statement has changed from v0.14");
     }
   }
 
+  private IntoItem parseIntoItem(IoTDBSqlParser.IntoItemContext intoItemContext) {
+    boolean isAligned = intoItemContext.ALIGNED() != null;
+    PartialPath intoDevice = parseIntoPath(intoItemContext.intoPath());
+    List<String> intoMeasurements =
+        intoItemContext.nodeNameInIntoPath().stream()
+            .map(this::parseNodeNameInIntoPath)
+            .collect(Collectors.toList());
+    return new IntoItem(intoDevice, intoMeasurements, isAligned);
+  }
+
   private PartialPath parseIntoPath(IoTDBSqlParser.IntoPathContext intoPathContext) {
     if (intoPathContext instanceof IoTDBSqlParser.FullPathInIntoPathContext) {
       return parseFullPathInIntoPath((IoTDBSqlParser.FullPathInIntoPathContext) intoPathContext);
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/AlignByDeviceIntoComponent.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/AlignByDeviceIntoComponent.java
deleted file mode 100644
index 4cf1b14b78..0000000000
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/AlignByDeviceIntoComponent.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.mpp.plan.statement.component;
-
-import org.apache.iotdb.commons.path.PartialPath;
-import org.apache.iotdb.tsfile.utils.Pair;
-
-import java.util.List;
-
-public class AlignByDeviceIntoComponent extends IntoComponent {
-
-  private final List<Pair<PartialPath, List<PartialPath>>> intoDeviceAndMeasurementList;
-
-  public AlignByDeviceIntoComponent(
-      List<Pair<PartialPath, List<PartialPath>>> intoDeviceAndMeasurementList, boolean isAligned) {
-    super(isAligned);
-    this.intoDeviceAndMeasurementList = intoDeviceAndMeasurementList;
-  }
-
-  public List<Pair<PartialPath, List<PartialPath>>> getIntoDeviceAndMeasurementList() {
-    return intoDeviceAndMeasurementList;
-  }
-}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/AlignByTimeIntoComponent.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/AlignByTimeIntoComponent.java
deleted file mode 100644
index c45a1c082d..0000000000
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/AlignByTimeIntoComponent.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.mpp.plan.statement.component;
-
-import org.apache.iotdb.commons.path.PartialPath;
-
-import java.util.List;
-
-public class AlignByTimeIntoComponent extends IntoComponent {
-
-  private final List<PartialPath> intoPaths;
-
-  public AlignByTimeIntoComponent(List<PartialPath> intoPaths, boolean isAligned) {
-    super(isAligned);
-    this.intoPaths = intoPaths;
-  }
-
-  public List<PartialPath> getIntoPaths() {
-    return intoPaths;
-  }
-}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/IntoComponent.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/IntoComponent.java
index 1e22c6fb9e..1eaa3ff073 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/IntoComponent.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/IntoComponent.java
@@ -19,16 +19,29 @@
 
 package org.apache.iotdb.db.mpp.plan.statement.component;
 
+import org.apache.iotdb.db.mpp.plan.statement.StatementNode;
+
+import java.util.List;
+
 /** This class maintains information of {@code INTO} clause. */
-public abstract class IntoComponent {
+public class IntoComponent extends StatementNode {
 
-  protected final boolean isAligned;
+  private final List<IntoItem> intoItems;
+
+  public IntoComponent(List<IntoItem> intoItems) {
+    this.intoItems = intoItems;
+  }
 
-  protected IntoComponent(boolean isAligned) {
-    this.isAligned = isAligned;
+  public List<IntoItem> getIntoItems() {
+    return intoItems;
   }
 
-  public boolean isAligned() {
-    return isAligned;
+  public boolean isDeviceExistPlaceholder() {
+    for (IntoItem intoItem : intoItems) {
+      if (intoItem.isDeviceExistPlaceholder()) {
+        return true;
+      }
+    }
+    return false;
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/IntoItem.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/IntoItem.java
new file mode 100644
index 0000000000..4efafe3cef
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/IntoItem.java
@@ -0,0 +1,73 @@
+/*
+ * 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.mpp.plan.statement.component;
+
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.db.mpp.plan.statement.StatementNode;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.apache.iotdb.commons.conf.IoTDBConstant.DOUBLE_COLONS;
+import static org.apache.iotdb.commons.conf.IoTDBConstant.LEVELED_PATH_TEMPLATE_PATTERN;
+
+public class IntoItem extends StatementNode {
+
+  private final PartialPath intoDevice;
+  private final List<String> intoMeasurements;
+  private final boolean isAligned;
+
+  public IntoItem(PartialPath intoDevice, List<String> intoMeasurements, boolean isAligned) {
+    this.intoDevice = intoDevice;
+    this.intoMeasurements = intoMeasurements;
+    this.isAligned = isAligned;
+  }
+
+  public PartialPath getIntoDevice() {
+    return intoDevice;
+  }
+
+  public List<String> getIntoMeasurements() {
+    return intoMeasurements;
+  }
+
+  public boolean isAligned() {
+    return isAligned;
+  }
+
+  public boolean isDeviceExistPlaceholder() {
+    return intoDevice.containNode(DOUBLE_COLONS)
+        || LEVELED_PATH_TEMPLATE_PATTERN.matcher(intoDevice.getFullPath()).find();
+  }
+
+  public boolean isMeasurementsExistPlaceholder() {
+    for (String measurement : intoMeasurements) {
+      if (measurement.equals(DOUBLE_COLONS)
+          || LEVELED_PATH_TEMPLATE_PATTERN.matcher(measurement).find()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public List<PartialPath> getIntoPaths() {
+    return intoMeasurements.stream().map(intoDevice::concatNode).collect(Collectors.toList());
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
index ddab00ec44..d0c05930be 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
@@ -27,8 +27,6 @@ import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.statement.Statement;
 import org.apache.iotdb.db.mpp.plan.statement.StatementVisitor;
-import org.apache.iotdb.db.mpp.plan.statement.component.AlignByDeviceIntoComponent;
-import org.apache.iotdb.db.mpp.plan.statement.component.AlignByTimeIntoComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.FillComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.FromComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.GroupByLevelComponent;
@@ -399,14 +397,6 @@ public class QueryStatement extends Statement {
       if (isLastQuery()) {
         throw new SemanticException("select into: last clauses are not supported.");
       }
-      if (isAlignByDevice() && intoComponent instanceof AlignByTimeIntoComponent) {
-        throw new SemanticException(
-            "select into: target path is illegal, expected: full path or suffix path");
-      }
-      if (isAlignByTime() && intoComponent instanceof AlignByDeviceIntoComponent) {
-        throw new SemanticException(
-            "select into: target path is illegal, expected: target device and measurements");
-      }
     }
   }
 
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 767d499d3d..ae7d4d96bb 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
@@ -1271,25 +1271,31 @@ public class IoTDBSqlVisitor extends IoTDBSqlParserBaseVisitor<Operator> {
 
   private SelectIntoOperator parseAndConstructSelectIntoOperator(
       IoTDBSqlParser.SelectStatementContext ctx) {
+    IoTDBSqlParser.IntoClauseContext intoClauseContext = ctx.intoClause();
+    if (intoClauseContext.intoItem().size() > 0) {
+      throw new SQLParserException(
+          "The new syntax of SELECT INTO statement will be supported starting from v0.14");
+    }
+
     if (queryOp.getFromComponent().getPrefixPaths().size() != 1) {
       throw new SQLParserException(
           "select into: the number of prefix paths in the from clause should be 1.");
     }
-
     int sourcePathsCount = queryOp.getSelectComponent().getResultColumns().size();
-    if (sourcePathsCount != ctx.intoClause().intoPath().size()) {
+    if (sourcePathsCount != intoClauseContext.intoPath().size()) {
       throw new SQLParserException(
           "select into: the number of source paths and the number of target paths should be the same.");
     }
 
     SelectIntoOperator selectIntoOperator = new SelectIntoOperator();
     selectIntoOperator.setQueryOperator(queryOp);
+
     List<PartialPath> intoPaths = new ArrayList<>();
     for (int i = 0; i < sourcePathsCount; ++i) {
-      intoPaths.add(parseIntoPath(ctx.intoClause().intoPath(i)));
+      intoPaths.add(parseIntoPath(intoClauseContext.intoPath(i)));
     }
     selectIntoOperator.setIntoPaths(intoPaths);
-    selectIntoOperator.setIntoPathsAligned(ctx.intoClause().ALIGNED() != null);
+    selectIntoOperator.setIntoPathsAligned(intoClauseContext.ALIGNED() != null);
     return selectIntoOperator;
   }