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:21 UTC

[iotdb] branch lmh/mppSelectInto created (now fb63d17c6b)

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

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


      at fb63d17c6b tmp save (analyzer for SELECT INTO)

This branch includes the following new commits:

     new df5d9e4eb6 add semantic check
     new 62b2d495b9 tmp save (analyzer for SELECT INTO)
     new bbeca341d5 Merge branch 'lmh/selectInto' into lmh/mppSelectInto
     new fb63d17c6b tmp save (analyzer for SELECT INTO)

The 4 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.



[iotdb] 03/04: Merge branch 'lmh/selectInto' into lmh/mppSelectInto

Posted by hu...@apache.org.
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 bbeca341d5964fc098152d7fc4857a3d6504cd32
Merge: eb96aa72b9 62b2d495b9
Author: Minghui Liu <li...@foxmail.com>
AuthorDate: Mon Oct 10 10:45:39 2022 +0800

    Merge branch 'lmh/selectInto' into lmh/mppSelectInto
    
    # Conflicts:
    #       node-commons/src/main/java/org/apache/iotdb/commons/path/MeasurementPath.java
    #       server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
    #       server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java

 .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4   | 14 +++-
 .../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4  |  1 +
 .../apache/iotdb/commons/conf/IoTDBConstant.java   |  5 ++
 .../apache/iotdb/commons/path/MeasurementPath.java |  4 +
 .../org/apache/iotdb/commons/path/PartialPath.java | 13 +++
 .../apache/iotdb/db/mpp/plan/analyze/Analysis.java | 22 +++++
 .../iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java  | 59 +++++++++++++
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       | 98 ++++++++++++++++++++--
 .../component/AlignByDeviceIntoComponent.java      | 40 +++++++++
 .../component/AlignByTimeIntoComponent.java        | 38 +++++++++
 .../plan/statement/component/IntoComponent.java    | 34 ++++++++
 .../db/mpp/plan/statement/crud/QueryStatement.java | 45 ++++++++++
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    | 20 ++---
 .../tsfile/common/constant/TsFileConstant.java     |  4 +
 14 files changed, 376 insertions(+), 21 deletions(-)

diff --cc node-commons/src/main/java/org/apache/iotdb/commons/conf/IoTDBConstant.java
index 292eebf642,536fb56513..80ae74638b
--- a/node-commons/src/main/java/org/apache/iotdb/commons/conf/IoTDBConstant.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/conf/IoTDBConstant.java
@@@ -19,8 -19,8 +19,9 @@@
  package org.apache.iotdb.commons.conf;
  
  import java.util.HashSet;
 +import java.util.Properties;
  import java.util.Set;
+ import java.util.regex.Pattern;
  
  public class IoTDBConstant {
  
diff --cc node-commons/src/main/java/org/apache/iotdb/commons/path/MeasurementPath.java
index ec57075d68,0de9c3bd43..cf2ceee688
--- a/node-commons/src/main/java/org/apache/iotdb/commons/path/MeasurementPath.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/path/MeasurementPath.java
@@@ -58,10 -57,10 +58,14 @@@ public class MeasurementPath extends Pa
      this.measurementSchema = new MeasurementSchema(getMeasurement(), type);
    }
  
 +  public MeasurementPath(PartialPath path, TSDataType type) {
 +    this(path, new MeasurementSchema(path.getMeasurement(), type), false);
 +  }
 +
+   public MeasurementPath(PartialPath path, TSDataType type, boolean isUnderAlignedEntity) {
+     this(path, new MeasurementSchema(path.getMeasurement(), type), isUnderAlignedEntity);
+   }
+ 
    public MeasurementPath(PartialPath measurementPath, IMeasurementSchema measurementSchema) {
      this(measurementPath, measurementSchema, false);
    }
diff --cc server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
index 757bfa42fb,28639dfd4a..a5140a68e3
--- 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
@@@ -86,6 -84,21 +86,9 @@@ 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 raw path to grouped path in `GROUP BY LEVEL` clause
 -  private Map<Expression, Expression> rawPathToGroupedPathMap;
 -
 -  private boolean isRawDataSource;
 -
+   // map from output column to target into path
+   private Map<String, PartialPath> outputColumnToIntoPathMap;
+ 
    /////////////////////////////////////////////////////////////////////////////////////////////////
    // Query Analysis (used in ALIGN BY DEVICE)
    /////////////////////////////////////////////////////////////////////////////////////////////////
@@@ -107,10 -123,13 +110,13 @@@
  
    // e.g. [s1,s2,s3] is query, but [s1, s3] exists in device1, then device1 -> [1, 3], s1 is 1 but
    // not 0 because device is the first column
 -  private Map<String, List<Integer>> deviceToMeasurementIndexesMap;
 +  private Map<String, List<Integer>> deviceViewInputIndexesMap;
  
 -  private Map<String, Boolean> deviceToIsRawDataSource;
 +  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)
    /////////////////////////////////////////////////////////////////////////////////////////////////
@@@ -396,11 -464,27 +402,27 @@@
      this.expressionTypes.putAll(types);
    }
  
 -  public List<Pair<Expression, String>> getOutputExpressions() {
 -    return outputExpressions;
 +  public Set<Expression> getDeviceViewOutputExpressions() {
 +    return deviceViewOutputExpressions;
    }
  
 -  public void setOutputExpressions(List<Pair<Expression, String>> outputExpressions) {
 -    this.outputExpressions = outputExpressions;
 +  public void setDeviceViewOutputExpressions(Set<Expression> deviceViewOutputExpressions) {
 +    this.deviceViewOutputExpressions = deviceViewOutputExpressions;
    }
+ 
+   public Map<String, PartialPath> getOutputColumnToIntoPathMap() {
+     return outputColumnToIntoPathMap;
+   }
+ 
+   public void setOutputColumnToIntoPathMap(Map<String, PartialPath> outputColumnToIntoPathMap) {
+     this.outputColumnToIntoPathMap = outputColumnToIntoPathMap;
+   }
+ 
+   public Map<String, Map<String, PartialPath>> getDeviceToIntoPathMap() {
+     return deviceToIntoPathMap;
+   }
+ 
+   public void setDeviceToIntoPathMap(Map<String, Map<String, PartialPath>> deviceToIntoPathMap) {
+     this.deviceToIntoPathMap = deviceToIntoPathMap;
+   }
  }
diff --cc server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
index b973187067,baa7a465e3..d0d32de590
--- 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
@@@ -101,18 -93,8 +102,19 @@@ import org.apache.iotdb.db.mpp.plan.sta
  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.query.control.SessionManager;
 +import org.apache.iotdb.db.utils.FileLoaderUtils;
 +import org.apache.iotdb.db.utils.TimePartitionUtils;
 +import org.apache.iotdb.rpc.TSStatusCode;
 +import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
 +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;
  import org.apache.iotdb.tsfile.read.filter.GroupByFilter;
  import org.apache.iotdb.tsfile.read.filter.GroupByMonthFilter;
  import org.apache.iotdb.tsfile.read.filter.basic.Filter;
@@@ -984,6 -989,78 +988,61 @@@ public class AnalyzeVisitor extends Sta
      return partitionFetcher.getDataPartition(sgNameToQueryParamsMap);
    }
  
 -  private OrderByParameter analyzeOrderBy(QueryStatement queryStatement) {
 -    return new OrderByParameter(queryStatement.getSortItemList());
 -  }
 -
 -  private void analyzeExpression(Analysis analysis, Expression expression) {
 -    ExpressionTypeAnalyzer.analyzeExpression(analysis, expression);
 -  }
 -
+   private void analyzeInto(Analysis analysis, List<Pair<Expression, String>> outputExpressions) {
+     Map<String, PartialPath> outputColumnToIntoPathMap = new HashMap<>();
+     List<Expression> outputColumns =
 -        outputExpressions.stream()
 -            .map(Pair::getLeft)
 -            .collect(Collectors.toCollection(ArrayList::new));
++            outputExpressions.stream()
++                    .map(Pair::getLeft)
++                    .collect(Collectors.toCollection(ArrayList::new));
+     AlignByTimeIntoComponent intoComponent =
 -        ((AlignByTimeIntoComponent) ((QueryStatement) analysis.getStatement()).getIntoComponent());
++            ((AlignByTimeIntoComponent) ((QueryStatement) analysis.getStatement()).getIntoComponent());
+     List<PartialPath> intoPaths = intoComponent.getIntoPaths();
+     boolean isAligned = intoComponent.isAligned();
+ 
+     boolean isAllRawSeriesQuery = checkIsAllRawSeriesQuery(outputColumns);
+     if (isAllRawSeriesQuery) {
+       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 paths 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.");
++                "select into: target paths in into clause should be different.");
+       }
+       for (int i = 0; i < outputColumns.size(); i++) {
+         outputColumnToIntoPathMap.put(
 -            outputColumns.get(i).toString(),
 -            constructIntoPath(analysis, outputColumns.get(i), intoPaths.get(i), isAligned));
++                outputColumns.get(i).toString(),
++                constructIntoPath(analysis, outputColumns.get(i), intoPaths.get(i), isAligned));
+       }
+     } else {
+ 
+     }
+     analysis.setOutputColumnToIntoPathMap(outputColumnToIntoPathMap);
+   }
+ 
+   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: ");
+     }
+     if (path.containNode(DOUBLE_COLONS)) {
+       throw new SemanticException("select into: ");
+     }
+     if (LEVELED_PATH_TEMPLATE_PATTERN.matcher(path.getFullPath()).find()) {
+       throw new SemanticException("select into: ");
+     }
+     return new MeasurementPath(path, analysis.getType(outputColumn), isAligned);
+   }
+ 
+   private void analyzeInto(
 -      Analysis analysis,
 -      List<Pair<Expression, String>> outputExpressions,
 -      Map<String, Set<String>> deviceToMeasurementsMap) {
++          Analysis analysis,
++          List<Pair<Expression, String>> outputExpressions,
++          Map<String, Set<String>> deviceToMeasurementsMap) {
+     Map<String, Map<String, PartialPath>> deviceToIntoPathMap = new HashMap<>();
+ 
+     analysis.setDeviceToIntoPathMap(deviceToIntoPathMap);
+   }
+ 
 -  private boolean checkIsAllRawSeriesQuery(List<Expression> expressions) {
 -    for (Expression expression : expressions) {
 -      if (!(expression instanceof TimeSeriesOperand)) {
 -        return true;
 -      }
 -    }
 -    return false;
 -  }
 -
    /**
     * Check datatype consistency in ALIGN BY DEVICE.
     *


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

Posted by hu...@apache.org.
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;
   }
 


[iotdb] 01/04: add semantic check

Posted by hu...@apache.org.
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 df5d9e4eb6454d3042e5d5e98c3d681a9919f605
Author: Minghui Liu <li...@foxmail.com>
AuthorDate: Wed Sep 14 10:19:08 2022 +0800

    add semantic check
---
 .../db/mpp/plan/statement/crud/QueryStatement.java    | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

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 97b355d2e3..1d4490ce3c 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
@@ -280,6 +280,10 @@ public class QueryStatement extends Statement {
     return orderByComponent.getSortItemList();
   }
 
+  public boolean isSelectInto() {
+    return intoComponent != null;
+  }
+
   public void semanticCheck() {
     if (isAggregationQuery()) {
       if (disableAlign()) {
@@ -371,6 +375,21 @@ public class QueryStatement extends Statement {
             "Sorting by device is only supported in ALIGN BY DEVICE queries.");
       }
     }
+
+    if (isSelectInto()) {
+      if (getSeriesLimit() > 0) {
+        throw new SemanticException("select into: slimit clauses are not supported.");
+      }
+      if (getSeriesOffset() > 0) {
+        throw new SemanticException("select into: soffset clauses are not supported.");
+      }
+      if (disableAlign()) {
+        throw new SemanticException("select into: disable align clauses are not supported.");
+      }
+      if (isLastQuery()) {
+        throw new SemanticException("select into: last clauses are not supported.");
+      }
+    }
   }
 
   @Override


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

Posted by hu...@apache.org.
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 62b2d495b9f2b1e7a61a6f54400edb4ee17f1ab1
Author: Minghui Liu <li...@foxmail.com>
AuthorDate: Mon Sep 19 10:22:55 2022 +0800

    tmp save (analyzer for SELECT INTO)
---
 .../apache/iotdb/commons/conf/IoTDBConstant.java   |  5 ++
 .../org/apache/iotdb/commons/path/PartialPath.java | 13 ++++
 .../iotdb/db/metadata/path/MeasurementPath.java    |  4 ++
 .../apache/iotdb/db/mpp/plan/analyze/Analysis.java | 22 +++++++
 .../iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java  | 72 ++++++++++++++++++++++
 .../db/mpp/plan/statement/crud/QueryStatement.java | 14 +++++
 6 files changed, 130 insertions(+)

diff --git a/node-commons/src/main/java/org/apache/iotdb/commons/conf/IoTDBConstant.java b/node-commons/src/main/java/org/apache/iotdb/commons/conf/IoTDBConstant.java
index 5d2c219e60..536fb56513 100644
--- a/node-commons/src/main/java/org/apache/iotdb/commons/conf/IoTDBConstant.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/conf/IoTDBConstant.java
@@ -20,6 +20,7 @@ package org.apache.iotdb.commons.conf;
 
 import java.util.HashSet;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 public class IoTDBConstant {
 
@@ -260,4 +261,8 @@ public class IoTDBConstant {
     V_0_12,
     V_0_13
   }
+
+  // select into
+  public static final Pattern LEVELED_PATH_TEMPLATE_PATTERN = Pattern.compile("\\$\\{\\w+}");
+  public static final String DOUBLE_COLONS = "::";
 }
diff --git a/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java b/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java
index bec99ab45f..f745444798 100644
--- a/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java
@@ -550,6 +550,19 @@ public class PartialPath extends Path implements Comparable<Path>, Cloneable {
     return true;
   }
 
+  public boolean startWith(String otherNode) {
+    return nodes[0].equals(otherNode);
+  }
+
+  public boolean containNode(String otherNode) {
+    for (String node : nodes) {
+      if (node.equals(otherNode)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   @Override
   public String toString() {
     return getFullPath();
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/path/MeasurementPath.java b/server/src/main/java/org/apache/iotdb/db/metadata/path/MeasurementPath.java
index 2525844e31..0de9c3bd43 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/path/MeasurementPath.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/path/MeasurementPath.java
@@ -57,6 +57,10 @@ public class MeasurementPath extends PartialPath {
     this.measurementSchema = new MeasurementSchema(getMeasurement(), type);
   }
 
+  public MeasurementPath(PartialPath path, TSDataType type, boolean isUnderAlignedEntity) {
+    this(path, new MeasurementSchema(path.getMeasurement(), type), isUnderAlignedEntity);
+  }
+
   public MeasurementPath(PartialPath measurementPath, IMeasurementSchema measurementSchema) {
     this(measurementPath, measurementSchema, false);
   }
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 d45d98e22c..28639dfd4a 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
@@ -96,6 +96,9 @@ public class Analysis {
 
   private boolean isRawDataSource;
 
+  // map from output column to target into path
+  private Map<String, PartialPath> outputColumnToIntoPathMap;
+
   /////////////////////////////////////////////////////////////////////////////////////////////////
   // Query Analysis (used in ALIGN BY DEVICE)
   /////////////////////////////////////////////////////////////////////////////////////////////////
@@ -124,6 +127,9 @@ public class Analysis {
 
   private Map<String, Boolean> deviceToIsRawDataSource;
 
+  // map from device name to target into path of each output column
+  private Map<String, Map<String, PartialPath>> deviceToIntoPathMap;
+
   /////////////////////////////////////////////////////////////////////////////////////////////////
   // Query Common Analysis (above DeviceView)
   /////////////////////////////////////////////////////////////////////////////////////////////////
@@ -465,4 +471,20 @@ public class Analysis {
   public void setOutputExpressions(List<Pair<Expression, String>> outputExpressions) {
     this.outputExpressions = outputExpressions;
   }
+
+  public Map<String, PartialPath> getOutputColumnToIntoPathMap() {
+    return outputColumnToIntoPathMap;
+  }
+
+  public void setOutputColumnToIntoPathMap(Map<String, PartialPath> outputColumnToIntoPathMap) {
+    this.outputColumnToIntoPathMap = outputColumnToIntoPathMap;
+  }
+
+  public Map<String, Map<String, PartialPath>> getDeviceToIntoPathMap() {
+    return deviceToIntoPathMap;
+  }
+
+  public void setDeviceToIntoPathMap(Map<String, Map<String, PartialPath>> deviceToIntoPathMap) {
+    this.deviceToIntoPathMap = deviceToIntoPathMap;
+  }
 }
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 a5dace95a8..baa7a465e3 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
@@ -49,6 +49,7 @@ 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.Ordering;
@@ -92,6 +93,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.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.read.filter.GroupByFilter;
 import org.apache.iotdb.tsfile.read.filter.GroupByMonthFilter;
@@ -117,6 +119,8 @@ import java.util.TimeZone;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkState;
+import static org.apache.iotdb.commons.conf.IoTDBConstant.DOUBLE_COLONS;
+import static org.apache.iotdb.commons.conf.IoTDBConstant.LEVELED_PATH_TEMPLATE_PATTERN;
 import static org.apache.iotdb.commons.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD;
 import static org.apache.iotdb.commons.conf.IoTDBConstant.ONE_LEVEL_PATH_WILDCARD;
 import static org.apache.iotdb.db.metadata.MetadataConstant.ALL_RESULT_NODES;
@@ -238,6 +242,8 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
                 deviceToTransformExpressions,
                 deviceToMeasurementsMap);
 
+        analyzeInto(analysis, outputExpressions, deviceToMeasurementsMap);
+
         if (queryStatement.hasHaving()) {
           List<PartialPath> measurementNotExistDevices = new ArrayList<>();
           for (PartialPath device : deviceList) {
@@ -417,6 +423,8 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
           analysis.setRawPathToGroupedPathMap(rawPathToGroupedPathMap);
         }
 
+        analyzeInto(analysis, outputExpressions);
+
         // true if nested expressions and UDFs exist in aggregation function
         // i.e. select sum(s1 + 1) from root.sg.d1
         boolean isHasRawDataInputAggregation = false;
@@ -989,6 +997,70 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
     ExpressionTypeAnalyzer.analyzeExpression(analysis, expression);
   }
 
+  private void analyzeInto(Analysis analysis, List<Pair<Expression, String>> outputExpressions) {
+    Map<String, PartialPath> outputColumnToIntoPathMap = new HashMap<>();
+    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();
+
+    boolean isAllRawSeriesQuery = checkIsAllRawSeriesQuery(outputColumns);
+    if (isAllRawSeriesQuery) {
+      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.");
+      }
+      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 < outputColumns.size(); i++) {
+        outputColumnToIntoPathMap.put(
+            outputColumns.get(i).toString(),
+            constructIntoPath(analysis, outputColumns.get(i), intoPaths.get(i), isAligned));
+      }
+    } else {
+
+    }
+    analysis.setOutputColumnToIntoPathMap(outputColumnToIntoPathMap);
+  }
+
+  private PartialPath constructIntoPath(
+      Analysis analysis, Expression outputColumn, PartialPath path, boolean isAligned) {
+    if (!path.startWith(SQLConstant.ROOT)) {
+      throw new SemanticException("select into: ");
+    }
+    if (path.containNode(DOUBLE_COLONS)) {
+      throw new SemanticException("select into: ");
+    }
+    if (LEVELED_PATH_TEMPLATE_PATTERN.matcher(path.getFullPath()).find()) {
+      throw new SemanticException("select into: ");
+    }
+    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);
+  }
+
+  private boolean checkIsAllRawSeriesQuery(List<Expression> expressions) {
+    for (Expression expression : expressions) {
+      if (!(expression instanceof TimeSeriesOperand)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Check datatype consistency in ALIGN BY DEVICE.
    *
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 1d4490ce3c..f6f9578f59 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,6 +27,8 @@ 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;
@@ -238,6 +240,10 @@ public class QueryStatement extends Statement {
     return groupByTimeComponent != null;
   }
 
+  public boolean isAlignByTime() {
+    return resultSetFormat == ResultSetFormat.ALIGN_BY_TIME;
+  }
+
   public boolean isAlignByDevice() {
     return resultSetFormat == ResultSetFormat.ALIGN_BY_DEVICE;
   }
@@ -389,6 +395,14 @@ 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");
+      }
     }
   }