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

haonan pushed a commit to branch rc/1.2.0
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/rc/1.2.0 by this push:
     new a05fbfd3d7a [To rel/1.2] Support multiple creation of views & Fix 
illegal view creation (#10061)
a05fbfd3d7a is described below

commit a05fbfd3d7a9463442da580c764bfa943fad7931
Author: 橘子 <[email protected]>
AuthorDate: Wed Jun 7 19:22:28 2023 +0800

    [To rel/1.2] Support multiple creation of views & Fix illegal view creation 
(#10061)
---
 .../view/IoTDBCreateAndShowViewIT.java             | 162 +++++++++++++++++++--
 .../metadata/view/UnsupportedViewException.java}   |  24 ++-
 .../view/ViewContainsAggregationException.java}    |  13 +-
 .../iotdb/db/metadata/view/ViewPathType.java       |   8 +-
 .../apache/iotdb/db/metadata/view/ViewPaths.java   |  26 ++++
 .../iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java  |  60 ++++++--
 .../iotdb/db/mpp/plan/analyze/SelectIntoUtils.java |   7 +
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       |  63 +++++++-
 .../metadata/view/AlterLogicalViewStatement.java   |  11 ++
 .../metadata/view/CreateLogicalViewStatement.java  |  51 ++++++-
 10 files changed, 375 insertions(+), 50 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/view/IoTDBCreateAndShowViewIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/db/it/schema/view/IoTDBCreateAndShowViewIT.java
similarity index 61%
rename from 
integration-test/src/test/java/org/apache/iotdb/db/it/view/IoTDBCreateAndShowViewIT.java
rename to 
integration-test/src/test/java/org/apache/iotdb/db/it/schema/view/IoTDBCreateAndShowViewIT.java
index fb04ad91a08..5ab968aaf8f 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/db/it/view/IoTDBCreateAndShowViewIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/schema/view/IoTDBCreateAndShowViewIT.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.iotdb.db.it.view;
+package org.apache.iotdb.db.it.schema.view;
 
 import org.apache.iotdb.db.mpp.common.header.ColumnHeaderConstant;
 import org.apache.iotdb.it.env.EnvFactory;
@@ -25,6 +25,7 @@ import org.apache.iotdb.itbase.category.ClusterIT;
 import org.apache.iotdb.itbase.category.LocalStandaloneIT;
 
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
@@ -32,6 +33,7 @@ import org.junit.runner.RunWith;
 
 import java.sql.Connection;
 import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -46,7 +48,7 @@ import static org.junit.Assert.fail;
 @Category({LocalStandaloneIT.class, ClusterIT.class})
 public class IoTDBCreateAndShowViewIT {
 
-  private static String[] sqls =
+  private static final String[] SQLs =
       new String[] {
         "CREATE DATABASE root.db;",
         "CREATE DATABASE root.myview;",
@@ -60,8 +62,23 @@ public class IoTDBCreateAndShowViewIT {
         "CREATE VIEW root.myview.d02(s01, s02) AS SELECT s01, s02 FROM 
root.db.d02;",
         "CREATE VIEW root.cal_view.avg AS SELECT (s01+s02)/2 FROM 
root.db.d01;",
         "CREATE VIEW root.cal_view(multiple, divide) AS SELECT s01*s02, 
s01/s02 FROM root.db.d02;",
-        "CREATE VIEW root.cal_view(agg_max1, agg_max2) AS SELECT 
MAX_VALUE(s01) FROM root.db.d01, root.db.d02 ;",
         "CREATE VIEW root.cal_view.cast_view AS SELECT CAST(s01 as TEXT) FROM 
root.db.d01;",
+        "CREATE VIEW root.multi_view.all_in_one(${2}_${3}) AS SELECT * FROM 
root.db.**;",
+        "CREATE VIEW root.copy_view.${2}(${3}) AS SELECT * FROM root.db.**;"
+      };
+
+  private static final String[] unsupportedSQLs =
+      new String[] {
+        "CREATE VIEW root.myview.nested_view AS root.myview.d01.s01;",
+        "CREATE VIEW root.agg_view(agg_avg1, agg_avg2) AS SELECT AVG(s01)+1 
FROM root.db.d01, root.db.d02;",
+        "CREATE VIEW root.agg_view(agg_max1, agg_max2) AS SELECT 
MAX_VALUE(s01) FROM root.db.d01, root.db.d02;",
+        "CREATE VIEW root.myview.illegal_view AS root.myview.d01.s01 + 1;",
+        "CREATE VIEW root.multi_view($illegal_name) AS root.db.d01.s01;",
+        "CREATE VIEW root.multi_view.multi_nodes(${3}.${2}) AS SELECT * FROM 
root.db.**;",
+        "CREATE VIEW root.copy_view.$illegal_char(${3}) AS SELECT * FROM 
root.db.**;",
+        "CREATE VIEW root.copy_view.mismatched_count(${3}) AS SELECT * FROM 
root.db.**;",
+        "CREATE VIEW root.repeated_view(a, a) AS SELECT s01, s02 FROM 
root.db.d01;",
+        "CREATE VIEW root.repeated_view.abc, root.repeated_view.abc  AS SELECT 
s01, s02 FROM root.db.d01;",
       };
 
   @BeforeClass
@@ -78,7 +95,7 @@ public class IoTDBCreateAndShowViewIT {
 
   // region Test show timesereis
   @Test
-  public void showOriginTimeseries() {
+  public void testShowOriginTimeseries() {
 
     Set<String> retSet =
         new HashSet<>(
@@ -127,7 +144,7 @@ public class IoTDBCreateAndShowViewIT {
   }
 
   @Test
-  public void showAliasViewsWithShowTimeseries() {
+  public void testShowAliasViewsWithShowTimeseries() {
 
     Set<String> retSet =
         new HashSet<>(
@@ -176,7 +193,7 @@ public class IoTDBCreateAndShowViewIT {
   }
 
   @Test
-  public void showViewsWithCalculationWithShowTimeseries() {
+  public void testShowViewsWithCalculationWithShowTimeseries() {
 
     Set<String> retSet =
         new HashSet<>(
@@ -184,14 +201,110 @@ public class IoTDBCreateAndShowViewIT {
                 
"root.cal_view.avg,null,root.cal_view,DOUBLE,null,null,null,null,logical;",
                 
"root.cal_view.multiple,null,root.cal_view,DOUBLE,null,null,null,null,logical;",
                 
"root.cal_view.divide,null,root.cal_view,DOUBLE,null,null,null,null,logical;",
-                
"root.cal_view.agg_max1,null,root.cal_view,INT32,null,null,null,null,logical;",
-                
"root.cal_view.agg_max2,null,root.cal_view,INT32,null,null,null,null,logical;",
                 
"root.cal_view.cast_view,null,root.cal_view,TEXT,null,null,null,null,logical;"));
 
     try (Connection connection = EnvFactory.getEnv().getConnection();
         Statement statement = connection.createStatement()) {
 
-      ResultSet resultSet = statement.executeQuery("SHOW TiMESERIES 
root.cal_view.*;");
+      ResultSet resultSet = statement.executeQuery("SHOW TiMESERIES 
root.cal_view.**;");
+      int count = 0;
+      while (resultSet.next()) {
+        String ans =
+            resultSet.getString(ColumnHeaderConstant.TIMESERIES)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.ALIAS)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.DATABASE)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.DATATYPE)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.ENCODING)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.COMPRESSION)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.TAGS)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.ATTRIBUTES)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.VIEW_TYPE)
+                + ";";
+
+        System.out.println("actual result:" + ans);
+        assertTrue(retSet.contains(ans));
+        count++;
+      }
+      assertEquals(retSet.size(), count);
+      resultSet.close();
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void testShowViewsWithMultiCreationWithShowTimeseriesPart01() {
+
+    Set<String> retSet =
+        new HashSet<>(
+            Arrays.asList(
+                
"root.multi_view.all_in_one.d01_s01,null,root.multi_view,INT32,null,null,null,null,logical;",
+                
"root.multi_view.all_in_one.d01_s02,null,root.multi_view,INT32,null,null,null,null,logical;",
+                
"root.multi_view.all_in_one.d02_s01,null,root.multi_view,INT32,null,null,null,null,logical;",
+                
"root.multi_view.all_in_one.d02_s02,null,root.multi_view,INT32,null,null,null,null,logical;"));
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+
+      ResultSet resultSet = statement.executeQuery("SHOW TiMESERIES 
root.multi_view.**;");
+      int count = 0;
+      while (resultSet.next()) {
+        String ans =
+            resultSet.getString(ColumnHeaderConstant.TIMESERIES)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.ALIAS)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.DATABASE)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.DATATYPE)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.ENCODING)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.COMPRESSION)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.TAGS)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.ATTRIBUTES)
+                + ","
+                + resultSet.getString(ColumnHeaderConstant.VIEW_TYPE)
+                + ";";
+
+        System.out.println("actual result:" + ans);
+        assertTrue(retSet.contains(ans));
+        count++;
+      }
+      assertEquals(retSet.size(), count);
+      resultSet.close();
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void testShowViewsWithMultiCreationWithShowTimeseriesPart02() {
+
+    Set<String> retSet =
+        new HashSet<>(
+            Arrays.asList(
+                
"root.copy_view.d01.s01,null,root.copy_view,INT32,null,null,null,null,logical;",
+                
"root.copy_view.d01.s02,null,root.copy_view,INT32,null,null,null,null,logical;",
+                
"root.copy_view.d02.s01,null,root.copy_view,INT32,null,null,null,null,logical;",
+                
"root.copy_view.d02.s02,null,root.copy_view,INT32,null,null,null,null,logical;"));
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+
+      ResultSet resultSet = statement.executeQuery("SHOW TiMESERIES 
root.copy_view.**;");
       int count = 0;
       while (resultSet.next()) {
         String ans =
@@ -229,7 +342,7 @@ public class IoTDBCreateAndShowViewIT {
 
   // region Test Show View
   @Test
-  public void showAllViewsWithShowView() {
+  public void testShowAllViewsWithShowView() {
 
     Set<String> retSet =
         new HashSet<>(
@@ -241,9 +354,15 @@ public class IoTDBCreateAndShowViewIT {
                 
"root.cal_view.avg,root.cal_view,DOUBLE,null,null,logical,(root.db.d01.s01 + 
root.db.d01.s02) / 2;",
                 
"root.cal_view.multiple,root.cal_view,DOUBLE,null,null,logical,root.db.d02.s01 
* root.db.d02.s02;",
                 
"root.cal_view.divide,root.cal_view,DOUBLE,null,null,logical,root.db.d02.s01 / 
root.db.d02.s02;",
-                
"root.cal_view.agg_max1,root.cal_view,INT32,null,null,logical,max_value(root.db.d01.s01);",
-                
"root.cal_view.agg_max2,root.cal_view,INT32,null,null,logical,max_value(root.db.d02.s01);",
-                
"root.cal_view.cast_view,root.cal_view,TEXT,null,null,logical,cast(type=TEXT)(root.db.d01.s01);"));
+                
"root.cal_view.cast_view,root.cal_view,TEXT,null,null,logical,cast(type=TEXT)(root.db.d01.s01);",
+                
"root.multi_view.all_in_one.d01_s01,root.multi_view,INT32,null,null,logical,root.db.d01.s01;",
+                
"root.multi_view.all_in_one.d01_s02,root.multi_view,INT32,null,null,logical,root.db.d01.s02;",
+                
"root.multi_view.all_in_one.d02_s01,root.multi_view,INT32,null,null,logical,root.db.d02.s01;",
+                
"root.multi_view.all_in_one.d02_s02,root.multi_view,INT32,null,null,logical,root.db.d02.s02;",
+                
"root.copy_view.d01.s01,root.copy_view,INT32,null,null,logical,root.db.d01.s01;",
+                
"root.copy_view.d01.s02,root.copy_view,INT32,null,null,logical,root.db.d01.s02;",
+                
"root.copy_view.d02.s01,root.copy_view,INT32,null,null,logical,root.db.d02.s01;",
+                
"root.copy_view.d02.s02,root.copy_view,INT32,null,null,logical,root.db.d02.s02;"));
 
     try (Connection connection = EnvFactory.getEnv().getConnection();
         Statement statement = connection.createStatement()) {
@@ -280,11 +399,26 @@ public class IoTDBCreateAndShowViewIT {
   }
   // endregion
 
+  // region unsupported SQLs
+  @Test
+  public void testUnsupportedSQLs() {
+    for (String unsupportedSQL : unsupportedSQLs) {
+      try (Connection connection = EnvFactory.getEnv().getConnection();
+          Statement statement = connection.createStatement()) {
+        statement.execute(String.format(unsupportedSQL));
+        Assert.fail(String.format("SQL [%s] should fail but no exception 
thrown.", unsupportedSQL));
+      } catch (SQLException ignored) {
+      }
+    }
+  }
+
+  // endregion
+
   private static void createSchema() {
     try (Connection connection = EnvFactory.getEnv().getConnection();
         Statement statement = connection.createStatement()) {
 
-      for (String sql : sqls) {
+      for (String sql : SQLs) {
         statement.execute(sql);
       }
     } catch (Exception e) {
diff --git 
a/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java 
b/server/src/main/java/org/apache/iotdb/db/exception/metadata/view/UnsupportedViewException.java
similarity index 52%
copy from 
server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java
copy to 
server/src/main/java/org/apache/iotdb/db/exception/metadata/view/UnsupportedViewException.java
index 668adbb07d4..b4428af09b3 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/exception/metadata/view/UnsupportedViewException.java
@@ -16,11 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.iotdb.db.exception.metadata.view;
 
-package org.apache.iotdb.db.metadata.view;
+import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.rpc.TSStatusCode;
 
-public enum ViewPathType {
-  FULL_PATH_LIST,
-  PATHS_GROUP,
-  QUERY_STATEMENT
+public class UnsupportedViewException extends MetadataException {
+
+  private static final String VIEW_IS_UNSUPPORTED = "View unsupported, 
because: %s";
+
+  public UnsupportedViewException(String message) {
+    super(
+        String.format(VIEW_IS_UNSUPPORTED, message),
+        TSStatusCode.UNSUPPORTED_OPERATION.getStatusCode());
+  }
+
+  public UnsupportedViewException(String message, boolean isUserException) {
+    super(
+        String.format(VIEW_IS_UNSUPPORTED, message),
+        TSStatusCode.UNSUPPORTED_OPERATION.getStatusCode(),
+        isUserException);
+  }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java 
b/server/src/main/java/org/apache/iotdb/db/exception/metadata/view/ViewContainsAggregationException.java
similarity index 64%
copy from 
server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java
copy to 
server/src/main/java/org/apache/iotdb/db/exception/metadata/view/ViewContainsAggregationException.java
index 668adbb07d4..50cf8a8839b 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/exception/metadata/view/ViewContainsAggregationException.java
@@ -16,11 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.iotdb.db.exception.metadata.view;
 
-package org.apache.iotdb.db.metadata.view;
+public class ViewContainsAggregationException extends UnsupportedViewException 
{
 
-public enum ViewPathType {
-  FULL_PATH_LIST,
-  PATHS_GROUP,
-  QUERY_STATEMENT
+  private static final String VIEW_CONTAINS_AGGREGATION_FUNCTION =
+      "This view contains aggregation function(s) named [%s]";
+
+  public ViewContainsAggregationException(String namesOfAggregationFunctions) {
+    super(String.format(VIEW_CONTAINS_AGGREGATION_FUNCTION, 
namesOfAggregationFunctions), true);
+  }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java 
b/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java
index 668adbb07d4..e34f6f87bfe 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPathType.java
@@ -20,7 +20,13 @@
 package org.apache.iotdb.db.metadata.view;
 
 public enum ViewPathType {
+  /** Example: root.db.d01.s01, root.db.d02.s01, root.db.d02.s02 */
   FULL_PATH_LIST,
+  /** Example: root.db(d01.s01, d02.s02, status) */
   PATHS_GROUP,
-  QUERY_STATEMENT
+  /** Example: SELECT s01, s02 FROM root.db.*; */
+  QUERY_STATEMENT,
+
+  /** Example: root.db.view(${2}_temperature) */
+  BATCH_GENERATION,
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPaths.java 
b/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPaths.java
index a0d86d1496b..3a969841166 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPaths.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/view/ViewPaths.java
@@ -20,8 +20,12 @@
 package org.apache.iotdb.db.metadata.view;
 
 import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.db.exception.metadata.view.UnsupportedViewException;
+import 
org.apache.iotdb.db.exception.metadata.view.ViewContainsAggregationException;
 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.expression.visitor.CollectAggregationExpressionsVisitor;
+import org.apache.iotdb.tsfile.utils.Pair;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -87,9 +91,31 @@ public class ViewPaths {
       }
     } else if (this.viewPathType == ViewPathType.QUERY_STATEMENT) {
       // no nothing. expressions should be set by setExpressionsList
+    } else if (this.viewPathType == ViewPathType.BATCH_GENERATION) {
+      // source paths has no intoItem. target paths should not be converted to 
expression.
     }
   }
 
+  /**
+   * Check all expression in this list, ensure that the views created using 
them are legal. Check
+   * follows above rules: 1. A legal view can NOT contain aggregation 
functions.
+   *
+   * @return If all check passed, return true; else return false with failure 
message.
+   */
+  public static Pair<Boolean, UnsupportedViewException> checkExpressionList(
+      List<Expression> expressionsList) {
+    CollectAggregationExpressionsVisitor collectAggExpVisitor =
+        new CollectAggregationExpressionsVisitor();
+
+    for (Expression expression : expressionsList) {
+      List<Expression> aggList = collectAggExpVisitor.process(expression, 
null);
+      if (aggList.size() > 0) {
+        return new Pair<>(false, new 
ViewContainsAggregationException(aggList.get(0).toString()));
+      }
+    }
+    return new Pair<>(true, null);
+  }
+
   public void setExpressionsList(List<Expression> expressionsList) {
     this.expressionsList = expressionsList;
   }
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 a96bd165a59..b20d445e3ba 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
@@ -46,6 +46,7 @@ import 
org.apache.iotdb.db.engine.storagegroup.TsFileResourceStatus;
 import org.apache.iotdb.db.exception.LoadFileException;
 import org.apache.iotdb.db.exception.VerifyMetadataException;
 import 
org.apache.iotdb.db.exception.metadata.template.TemplateImcompatibeException;
+import org.apache.iotdb.db.exception.metadata.view.UnsupportedViewException;
 import org.apache.iotdb.db.exception.sql.MeasurementNotExistException;
 import org.apache.iotdb.db.exception.sql.SemanticException;
 import org.apache.iotdb.db.exception.sql.StatementAnalyzeException;
@@ -3170,6 +3171,8 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
     analysis.setWhereExpression(whereExpression);
   }
 
+  // region view
+
   /**
    * Compute how many paths exist, get the schema tree and the number of 
existed paths.
    *
@@ -3304,13 +3307,30 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
               "The path " + checkResult.right + " is illegal."));
       return;
     }
-    if (createLogicalViewStatement.getSourceExpressionList().size()
-        != createLogicalViewStatement.getTargetPathList().size()) {
+    // make sure there are no redundant paths in targets. Please note that 
redundant paths in source
+    // are legal!
+    List<PartialPath> targetPathList = 
createLogicalViewStatement.getTargetPathList();
+    Set<String> targetStringSet = new HashSet<>();
+    for (PartialPath path : targetPathList) {
+      boolean repeatPathNotExist = targetStringSet.add(path.toString());
+      if (!repeatPathNotExist) {
+        analysis.setFinishQueryAfterAnalyze(true);
+        analysis.setFailStatus(
+            RpcUtils.getStatus(
+                TSStatusCode.ILLEGAL_PATH.getStatusCode(),
+                String.format("Path [%s] is redundant in target paths.", 
path)));
+        return;
+      }
+    }
+    if (createLogicalViewStatement.getSourceExpressionList().size() != 
targetPathList.size()) {
       analysis.setFinishQueryAfterAnalyze(true);
       analysis.setFailStatus(
           RpcUtils.getStatus(
               TSStatusCode.UNSUPPORTED_OPERATION.getStatusCode(),
-              "The number of target and source paths are miss matched! Please 
check your SQL."));
+              String.format(
+                  "The number of target paths (%d) and sources (%d) are miss 
matched! Please check your SQL.",
+                  createLogicalViewStatement.getTargetPathList().size(),
+                  
createLogicalViewStatement.getSourceExpressionList().size())));
       return;
     }
     // make sure all paths are NOt under any template
@@ -3344,22 +3364,31 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
         if (queryAnalysisPair.right.isFinishQueryAfterAnalyze()) {
           return analysis;
         } else if (queryAnalysisPair.left != null) {
-          
createLogicalViewStatement.setSourceExpressions(queryAnalysisPair.left);
+          try {
+            
createLogicalViewStatement.setSourceExpressions(queryAnalysisPair.left);
+          } catch (UnsupportedViewException e) {
+            analysis.setFinishQueryAfterAnalyze(true);
+            analysis.setFailStatus(RpcUtils.getStatus(e.getErrorCode(), 
e.getMessage()));
+            return analysis;
+          }
         }
       }
+    }
 
-      // check target paths; check source expressions.
-      checkPathsInCreateLogicalView(analysis, createLogicalViewStatement);
-      if (analysis.isFinishQueryAfterAnalyze()) {
-        return analysis;
-      }
+    // use source and into item to generate target views
+    createLogicalViewStatement.parseIntoItemIfNecessary();
 
-      // make sure there is no view in source
-      List<Expression> sourceExpressionList = 
createLogicalViewStatement.getSourceExpressionList();
-      checkViewsInSource(analysis, sourceExpressionList, context);
-      if (analysis.isFinishQueryAfterAnalyze()) {
-        return analysis;
-      }
+    // check target paths; check source expressions.
+    checkPathsInCreateLogicalView(analysis, createLogicalViewStatement);
+    if (analysis.isFinishQueryAfterAnalyze()) {
+      return analysis;
+    }
+
+    // make sure there is no view in source
+    List<Expression> sourceExpressionList = 
createLogicalViewStatement.getSourceExpressionList();
+    checkViewsInSource(analysis, sourceExpressionList, context);
+    if (analysis.isFinishQueryAfterAnalyze()) {
+      return analysis;
     }
 
     // set schema partition info, this info will be used to split logical plan 
node.
@@ -3388,4 +3417,5 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
     
analysis.setRespDatasetHeader(DatasetHeaderFactory.getShowLogicalViewHeader());
     return analysis;
   }
+  // endregion view
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/SelectIntoUtils.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/SelectIntoUtils.java
index 46b076bc8f3..ede0384dfcb 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/SelectIntoUtils.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/SelectIntoUtils.java
@@ -26,6 +26,7 @@ import org.apache.iotdb.db.mpp.common.schematree.ISchemaTree;
 import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.utils.TypeInferenceUtils;
+import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.utils.Pair;
 
@@ -99,6 +100,12 @@ public class SelectIntoUtils {
       resNode = matcher.replaceFirst(sourceNodes[index]);
       matcher = LEVELED_PATH_TEMPLATE_PATTERN.matcher(resNode);
     }
+    if (!TsFileConstant.NODE_NAME_PATTERN.matcher(resNode).matches()) {
+      throw new SemanticException(
+          String.format(
+              "Parsed node name %s is illegal, unquoted node name can only 
consist of digits, characters and underscore, or start or end with wildcard",
+              resNode));
+    }
     return resNode;
   }
 
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 57bf1fcee8e..12d7f31d7c4 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
@@ -1006,7 +1006,8 @@ public class ASTVisitor extends 
IoTDBSqlParserBaseVisitor<Statement> {
     parseViewTargetPaths(
         ctx.viewTargetPaths(),
         createLogicalViewStatement::setTargetFullPaths,
-        createLogicalViewStatement::setTargetPathsGroup);
+        createLogicalViewStatement::setTargetPathsGroup,
+        createLogicalViewStatement::setTargetIntoItem);
     // parse source
     parseViewSourcePaths(
         ctx.viewSourcePaths(),
@@ -1069,7 +1070,11 @@ public class ASTVisitor extends 
IoTDBSqlParserBaseVisitor<Statement> {
     parseViewTargetPaths(
         ctx.viewTargetPaths(),
         alterLogicalViewStatement::setTargetFullPaths,
-        alterLogicalViewStatement::setTargetPathsGroup);
+        alterLogicalViewStatement::setTargetPathsGroup,
+        alterLogicalViewStatement::setTargetIntoItem);
+    if (alterLogicalViewStatement.getIntoItem() != null) {
+      throw new SemanticException("Can not use char '$' or into item in alter 
view statement.");
+    }
     // parse source
     parseViewSourcePaths(
         ctx.viewSourcePaths(),
@@ -1080,7 +1085,27 @@ public class ASTVisitor extends 
IoTDBSqlParserBaseVisitor<Statement> {
     return alterLogicalViewStatement;
   }
 
-  // parse suffix paths in logical view
+  // parse suffix paths in logical view with into item
+  private PartialPath 
parseViewPrefixPathWithInto(IoTDBSqlParser.PrefixPathContext ctx) {
+    List<IoTDBSqlParser.NodeNameContext> nodeNames = ctx.nodeName();
+    String[] path = new String[nodeNames.size() + 1];
+    path[0] = ctx.ROOT().getText();
+    for (int i = 0; i < nodeNames.size(); i++) {
+      path[i + 1] = parseNodeStringInIntoPath(nodeNames.get(i).getText());
+    }
+    return new PartialPath(path);
+  }
+
+  private PartialPath 
parseViewSuffixPatWithInto(IoTDBSqlParser.ViewSuffixPathsContext ctx) {
+    List<IoTDBSqlParser.NodeNameWithoutWildcardContext> nodeNamesWithoutStar =
+        ctx.nodeNameWithoutWildcard();
+    String[] nodeList = new String[nodeNamesWithoutStar.size()];
+    for (int i = 0; i < nodeNamesWithoutStar.size(); i++) {
+      nodeList[i] = 
parseNodeStringInIntoPath(nodeNamesWithoutStar.get(i).getText());
+    }
+    return new PartialPath(nodeList);
+  }
+
   private PartialPath 
parseViewSuffixPath(IoTDBSqlParser.ViewSuffixPathsContext ctx) {
     List<IoTDBSqlParser.NodeNameWithoutWildcardContext> nodeNamesWithoutStar =
         ctx.nodeNameWithoutWildcard();
@@ -1095,7 +1120,8 @@ public class ASTVisitor extends 
IoTDBSqlParserBaseVisitor<Statement> {
   private void parseViewTargetPaths(
       IoTDBSqlParser.ViewTargetPathsContext ctx,
       Consumer<List<PartialPath>> setTargetFullPaths,
-      BiConsumer<PartialPath, List<PartialPath>> setTargetPathsGroup) {
+      BiConsumer<PartialPath, List<PartialPath>> setTargetPathsGroup,
+      Consumer<IntoItem> setTargetIntoItem) {
     // full paths
     if (ctx.fullPath() != null && ctx.fullPath().size() > 0) {
       List<IoTDBSqlParser.FullPathContext> fullPathContextList = 
ctx.fullPath();
@@ -1110,13 +1136,34 @@ public class ASTVisitor extends 
IoTDBSqlParserBaseVisitor<Statement> {
         && ctx.viewSuffixPaths() != null
         && ctx.viewSuffixPaths().size() > 0) {
       IoTDBSqlParser.PrefixPathContext prefixPathContext = ctx.prefixPath();
-      PartialPath prefixPath = parsePrefixPath(prefixPathContext);
       List<IoTDBSqlParser.ViewSuffixPathsContext> suffixPathContextList = 
ctx.viewSuffixPaths();
       List<PartialPath> suffixPathList = new ArrayList<>();
-      for (IoTDBSqlParser.ViewSuffixPathsContext suffixPathContext : 
suffixPathContextList) {
-        suffixPathList.add(parseViewSuffixPath(suffixPathContext));
+      PartialPath prefixPath = null;
+      boolean isMultipleCreating = false;
+      try {
+        prefixPath = parsePrefixPath(prefixPathContext);
+        for (IoTDBSqlParser.ViewSuffixPathsContext suffixPathContext : 
suffixPathContextList) {
+          suffixPathList.add(parseViewSuffixPath(suffixPathContext));
+        }
+      } catch (SemanticException e) {
+        // there is '$', '{', '}' in this statement
+        isMultipleCreating = true;
+        suffixPathList.clear();
+      }
+      if (!isMultipleCreating) {
+        setTargetPathsGroup.accept(prefixPath, suffixPathList);
+      } else {
+        prefixPath = parseViewPrefixPathWithInto(prefixPathContext);
+        for (IoTDBSqlParser.ViewSuffixPathsContext suffixPathContext : 
suffixPathContextList) {
+          suffixPathList.add(parseViewSuffixPatWithInto(suffixPathContext));
+        }
+        List<String> intoMeasurementList = new ArrayList<>();
+        for (PartialPath path : suffixPathList) {
+          intoMeasurementList.add(path.toString());
+        }
+        IntoItem intoItem = new IntoItem(prefixPath, intoMeasurementList, 
false);
+        setTargetIntoItem.accept(intoItem);
       }
-      setTargetPathsGroup.accept(prefixPath, suffixPathList);
     }
   }
 
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/view/AlterLogicalViewStatement.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/view/AlterLogicalViewStatement.java
index 899d4f0b3be..fc7507e2b45 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/view/AlterLogicalViewStatement.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/view/AlterLogicalViewStatement.java
@@ -27,6 +27,7 @@ import 
org.apache.iotdb.db.mpp.plan.statement.IConfigStatement;
 import org.apache.iotdb.db.mpp.plan.statement.Statement;
 import org.apache.iotdb.db.mpp.plan.statement.StatementType;
 import org.apache.iotdb.db.mpp.plan.statement.StatementVisitor;
+import org.apache.iotdb.db.mpp.plan.statement.component.IntoItem;
 import org.apache.iotdb.db.mpp.plan.statement.crud.QueryStatement;
 
 import java.util.List;
@@ -39,6 +40,7 @@ public class AlterLogicalViewStatement extends Statement 
implements IConfigState
   // the paths of sources
   private ViewPaths sourcePaths;
   private QueryStatement queryStatement;
+  private IntoItem intoItem;
 
   public AlterLogicalViewStatement() {
     super();
@@ -101,6 +103,15 @@ public class AlterLogicalViewStatement extends Statement 
implements IConfigState
     this.targetPaths.setSuffixOfPathsGroup(suffixPaths);
     this.targetPaths.generateFullPathsFromPathsGroup();
   }
+
+  public void setTargetIntoItem(IntoItem intoItem) {
+    this.targetPaths.setViewPathType(ViewPathType.BATCH_GENERATION);
+    this.intoItem = intoItem;
+  }
+
+  public IntoItem getIntoItem() {
+    return this.intoItem;
+  }
   // endregion
 
   @Override
diff --git 
a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/view/CreateLogicalViewStatement.java
 
b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/view/CreateLogicalViewStatement.java
index e70c5772345..9ecd71fce8d 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/view/CreateLogicalViewStatement.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/view/CreateLogicalViewStatement.java
@@ -21,15 +21,22 @@ package 
org.apache.iotdb.db.mpp.plan.statement.metadata.view;
 
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.schema.view.viewExpression.ViewExpression;
+import org.apache.iotdb.db.exception.metadata.view.UnsupportedViewException;
 import org.apache.iotdb.db.metadata.view.ViewPathType;
 import org.apache.iotdb.db.metadata.view.ViewPaths;
+import org.apache.iotdb.db.mpp.plan.analyze.SelectIntoUtils;
 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.StatementType;
 import org.apache.iotdb.db.mpp.plan.statement.StatementVisitor;
+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.crud.QueryStatement;
 import org.apache.iotdb.tsfile.utils.Pair;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** CREATE LOGICAL VIEW statement. */
@@ -37,6 +44,7 @@ public class CreateLogicalViewStatement extends Statement {
 
   // the paths of this view
   private ViewPaths targetPaths;
+  private IntoItem batchGenerationItem;
 
   // the paths of sources
   private ViewPaths sourcePaths;
@@ -113,8 +121,16 @@ public class CreateLogicalViewStatement extends Statement {
    *
    * @param expressionList
    */
-  public void setSourceExpressions(List<Expression> expressionList) {
-    this.sourcePaths.setExpressionsList(expressionList);
+  public void setSourceExpressions(List<Expression> expressionList)
+      throws UnsupportedViewException {
+    // check expressions, make sure no aggregation function expression
+    Pair<Boolean, UnsupportedViewException> checkResult =
+        ViewPaths.checkExpressionList(expressionList);
+    if (checkResult.left) {
+      this.sourcePaths.setExpressionsList(expressionList);
+    } else {
+      throw checkResult.right;
+    }
   }
 
   // set target paths
@@ -138,6 +154,37 @@ public class CreateLogicalViewStatement extends Statement {
     this.viewExpression = viewExpression;
   }
 
+  public void setTargetIntoItem(IntoItem intoItem) {
+    this.targetPaths.setViewPathType(ViewPathType.BATCH_GENERATION);
+    this.batchGenerationItem = intoItem;
+  }
+
+  public void parseIntoItemIfNecessary() {
+    if (this.batchGenerationItem != null) {
+      List<Expression> sourceExpressionList = this.getSourceExpressionList();
+      IntoComponent intoComponent =
+          new 
IntoComponent(Collections.singletonList(this.batchGenerationItem));
+      intoComponent.validate(sourceExpressionList);
+      IntoComponent.IntoPathIterator intoPathIterator = 
intoComponent.getIntoPathIterator();
+      List<PartialPath> targetPathsList = new ArrayList<>();
+      for (Expression sourceColumn : sourceExpressionList) {
+        PartialPath deviceTemplate = intoPathIterator.getDeviceTemplate();
+        String measurementTemplate = intoPathIterator.getMeasurementTemplate();
+
+        if (sourceColumn instanceof TimeSeriesOperand) {
+          PartialPath sourcePath = ((TimeSeriesOperand) 
sourceColumn).getPath();
+          targetPathsList.add(
+              SelectIntoUtils.constructTargetPath(sourcePath, deviceTemplate, 
measurementTemplate));
+        } else {
+          throw new RuntimeException(
+              new UnsupportedViewException(
+                  "Cannot create views using data sources with calculated 
expressions while using into item."));
+        }
+      }
+      this.targetPaths.setFullPathList(targetPathsList);
+    }
+  }
+
   // endregion
 
   // region Interfaces for checking


Reply via email to