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

caogaofei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 8aaaf8ea0c1 [feat](query) Implement column name match when query in 
TableModel (#14928)
8aaaf8ea0c1 is described below

commit 8aaaf8ea0c1bffbf65f17657c034fabc03aa76d2
Author: Weihao Li <[email protected]>
AuthorDate: Fri Mar 14 07:33:48 2025 +0800

    [feat](query) Implement column name match when query in TableModel (#14928)
---
 .../it/query/recent/IoTDBColumnsMatchTableIT.java  | 354 ++++++++
 .../relational/analyzer/ExpressionAnalyzer.java    |   6 +
 .../relational/analyzer/StatementAnalyzer.java     | 910 ++++++++++++++++++++-
 .../relational/planner/TableLogicalPlanner.java    |  13 +-
 .../plan/relational/planner/node/OutputNode.java   |   2 +
 .../plan/relational/sql/ast/AstVisitor.java        |   4 +
 .../plan/relational/sql/ast/Columns.java           |  96 +++
 .../plan/relational/sql/ast/SingleColumn.java      |  21 +
 .../queryengine/plan/relational/sql/ast/Trim.java  |   2 +-
 .../plan/relational/sql/parser/AstBuilder.java     |  11 +
 .../relational/sql/util/ExpressionFormatter.java   |   6 +
 .../db/relational/grammar/sql/RelationalSql.g4     |   1 +
 12 files changed, 1420 insertions(+), 6 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBColumnsMatchTableIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBColumnsMatchTableIT.java
new file mode 100644
index 00000000000..e6a3ddae29d
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBColumnsMatchTableIT.java
@@ -0,0 +1,354 @@
+/*
+ * 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.relational.it.query.recent;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBColumnsMatchTableIT {
+  private static final String DATABASE_NAME = "test";
+  private static final String[] createSqls =
+      new String[] {
+        "CREATE DATABASE " + DATABASE_NAME,
+        "USE " + DATABASE_NAME,
+        "CREATE TABLE table1(device_id STRING TAG, s1 INT32 FIELD, s2 INT64 
FIELD)",
+        "INSERT INTO table1(time,device_id,s1,s2) values(1,'d1',1,10)",
+        "INSERT INTO table1(time,device_id,s1,s2) values(2,'d2',2,20)",
+        "INSERT INTO table1(time,device_id,s1) values(3,'d3',3)"
+      };
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    
EnvFactory.getEnv().getConfig().getCommonConfig().setEnableCrossSpaceCompaction(false);
+    EnvFactory.getEnv().initClusterEnvironment();
+    prepareTableData(createSqls);
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  @Test
+  public void columnsTest() {
+
+    // case 1: match all columns
+    String[] expectedHeader = new String[] {"time", "device_id", "s1", "s2"};
+    String[] retArray =
+        new String[] {
+          "1970-01-01T00:00:00.001Z,d1,1,10,",
+          "1970-01-01T00:00:00.002Z,d2,2,20,",
+          "1970-01-01T00:00:00.003Z,d3,3,null,"
+        };
+    tableResultSetEqualTest(
+        "SELECT COLUMNS(*) FROM table1 order by time", expectedHeader, 
retArray, DATABASE_NAME);
+
+    // case 2: match columns which name starts with 's'
+    expectedHeader = new String[] {"s1", "s2"};
+    retArray = new String[] {"1,10,", "2,20,", "3,null,"};
+    tableResultSetEqualTest(
+        "SELECT COLUMNS('^s.*') FROM table1 order by s1", expectedHeader, 
retArray, DATABASE_NAME);
+
+    // case 3: cannot match any column
+    tableAssertTestFail(
+        "SELECT COLUMNS('^b.*') FROM table1",
+        "701: No matching columns found that match regex '^b.*'",
+        DATABASE_NAME);
+
+    // case 4: invalid input of regex
+    tableAssertTestFail(
+        "SELECT COLUMNS('*b') FROM table1 order by s1", "701: Invalid regex 
'*b'", DATABASE_NAME);
+  }
+
+  @Test
+  public void aliasTest() {
+    // case 1: normal test
+    String[] expectedHeader = new String[] {"series1", "series2"};
+    String[] retArray = new String[] {"1,10,", "2,20,", "3,null,"};
+    tableResultSetEqualTest(
+        "SELECT COLUMNS('^s(.*)') AS \"series$1\" FROM table1 order by 
series1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    expectedHeader = new String[] {"s", "s", "s", "s"};
+    retArray =
+        new String[] {
+          "1970-01-01T00:00:00.001Z,d1,1,10,",
+          "1970-01-01T00:00:00.002Z,d2,2,20,",
+          "1970-01-01T00:00:00.003Z,d3,3,null,"
+        };
+    tableResultSetEqualTest(
+        "SELECT COLUMNS(*) AS s FROM table1 order by 1", expectedHeader, 
retArray, DATABASE_NAME);
+
+    // case 2: no according reference group
+    tableAssertTestFail(
+        "SELECT COLUMNS('^s.*') AS \"series$1\" FROM table1", "701: No group 
1", DATABASE_NAME);
+
+    // case 3: alias grammar error
+    tableAssertTestFail(
+        "SELECT COLUMNS('^s.*') AS 'series$1' FROM table1",
+        "700: line 1:27: mismatched input ''series$1''. Expecting: 
<identifier>",
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void usedWithExpressionTest1() {
+    // case 1: select min value for each column
+    String[] expectedHeader =
+        new String[] {"_col0_time", "_col1_device_id", "_col2_s1", "_col3_s2"};
+    String[] retArray = new String[] {"1970-01-01T00:00:00.001Z,d1,1,10,"};
+    tableResultSetEqualTest(
+        "SELECT min(COLUMNS(*)) FROM table1", expectedHeader, retArray, 
DATABASE_NAME);
+
+    // case 2: multi columns() in different selectItem,
+    expectedHeader =
+        new String[] {
+          "_col0_time", "_col1_device_id", "_col2_s1", "_col3_s2", "_col4_s1", 
"_col5_s2"
+        };
+    retArray = new String[] {"1970-01-01T00:00:00.001Z,d1,1,10,1,10,"};
+    tableResultSetEqualTest(
+        "SELECT min(COLUMNS(*)), min(COLUMNS('^s.*')) FROM table1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    // case 3: multi columns() in one expression, columns() are same
+    expectedHeader = new String[] {"_col0_s1", "_col1_s2"};
+    retArray = new String[] {"4,30,"};
+    tableResultSetEqualTest(
+        "SELECT min(COLUMNS('^s.*')) + max(COLUMNS('^s.*')) FROM table1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    // case 4: multi columns() in one expression, columns() are different
+    tableAssertTestFail(
+        "SELECT min(COLUMNS('^s.*')) + max(COLUMNS('^t.*')) FROM table1",
+        "701: Multiple different COLUMNS in the same expression are not 
supported",
+        DATABASE_NAME);
+
+    // case 5: get last row of Data
+    expectedHeader =
+        new String[] {"_col0", "_col1_time", "_col2_device_id", "_col3_s1", 
"_col4_s2"};
+    retArray = new String[] 
{"1970-01-01T00:00:00.003Z,1970-01-01T00:00:00.003Z,d3,3,null,"};
+    tableResultSetEqualTest(
+        "SELECT last(time), last_by(COLUMNS(*), time) FROM table1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    // case 6: get last non-null value of each column, and according time
+    expectedHeader =
+        new String[] {
+          "_col0_time",
+          "_col1_device_id",
+          "_col2_s1",
+          "_col3_s2",
+          "_col4_time",
+          "_col5_device_id",
+          "_col6_s1",
+          "_col7_s2"
+        };
+    retArray =
+        new String[] {
+          
"1970-01-01T00:00:00.003Z,d3,3,20,1970-01-01T00:00:00.003Z,1970-01-01T00:00:00.003Z,1970-01-01T00:00:00.003Z,1970-01-01T00:00:00.002Z,"
+        };
+    tableResultSetEqualTest(
+        "SELECT last(COLUMNS(*)), last_by(time,COLUMNS(*)) FROM table1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    expectedHeader =
+        new String[] {
+          "test", "test", "test", "test", "_col4_time", "_col5_device_id", 
"_col6_s1", "_col7_s2"
+        };
+    tableResultSetEqualTest(
+        "SELECT last(COLUMNS(*)) as test, last_by(time,COLUMNS(*)) FROM 
table1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  // used with all supported Expressions
+  @Test
+  public void usedWithExpressionTest2() {
+    String[] expectedHeader =
+        new String[] {
+          "_col0_s1",
+          "_col1_s2",
+          "_col2_s1",
+          "_col3_s2",
+          "_col4_s1",
+          "_col5_s2",
+          "_col6_s1",
+          "_col7_s2"
+        };
+    String[] retArray =
+        new String[] {
+          "2,20,-1,-10,true,false,false,true,",
+          "4,40,-2,-20,true,false,true,true,",
+          "6,null,-3,null,false,null,true,false,"
+        };
+    tableResultSetEqualTest(
+        "select columns('s.*')+columns('s.*'), -columns('s.*'),columns('s.*') 
between 1 and 2,if(columns('s.*')>1,true,false) from table1 order by s1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    tableAssertTestFail(
+        "select columns(*).s1 from table1",
+        "701: Columns are not supported in DereferenceExpression",
+        DATABASE_NAME);
+
+    expectedHeader = new String[] {"time", "device_id", "s1", "s2"};
+    retArray = new String[] {"1970-01-01T00:00:00.002Z,d2,2,20,"};
+    tableResultSetEqualTest(
+        "select * from table1 where columns('s.*') > any(select s1 from 
table1) order by s1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    expectedHeader =
+        new String[] {
+          "_col0_s1",
+          "_col1_s2",
+          "_col2_s1",
+          "_col3_time",
+          "_col4_device_id",
+          "_col5_s1",
+          "_col6_s2",
+          "_col7_time",
+          "_col8_device_id",
+          "_col9_s1",
+          "_col10_s2",
+          "_col11_time",
+          "_col12_device_id",
+          "_col13_s1",
+          "_col14_s2"
+        };
+    retArray =
+        new String[] {
+          
"1,10,1,true,true,true,true,true,true,true,true,false,false,false,false,",
+          
"2,20,2,true,true,true,true,true,true,true,true,false,false,false,false,",
+          
"3,null,3,true,true,true,null,true,true,true,false,false,false,false,true,"
+        };
+    tableResultSetEqualTest(
+        "select trim(cast(columns('s.*') as 
String)),COALESCE(columns('s1.*'),1), columns(*) in (columns(*)),columns(*) is 
not null, columns(*) is null from table1 order by s1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    expectedHeader =
+        new String[] {"_col0_device_id", "_col1_time", "_col2_device_id", 
"_col3_s1", "_col4_s2"};
+    retArray =
+        new String[] {
+          "true,true,true,true,true,", "true,true,true,true,true,", 
"true,true,true,true,null,"
+        };
+    tableResultSetEqualTest(
+        "select columns('d.*') like 'd_',columns(*)=columns(*) from table1 
order by s1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    tableAssertTestFail(
+        "SELECT CASE columns('s.*') WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 
'many' END from table1",
+        "701: CASE operand type does not match WHEN clause operand type: INT64 
vs INT32",
+        DATABASE_NAME);
+
+    expectedHeader = new String[] {"_col0_s1"};
+    retArray = new String[] {"one,", "two,", "many,"};
+    tableResultSetEqualTest(
+        "SELECT CASE columns('s1.*') WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 
'many' END from table1 order by s1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    expectedHeader = new String[] {"_col0_s1", "_col1_s2"};
+    retArray = new String[] {"one,many,", "two,many,", "many,many,"};
+    tableResultSetEqualTest(
+        "select case when columns('s.*') = 1 then 'one' when columns('s.*')=2 
then 'two' else 'many' end from table1 order by s1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void usedInWhereTest() {
+    // case 1: normal test
+    String[] expectedHeader = new String[] {"time", "device_id", "s1", "s2"};
+    String[] retArray = new String[] {"1970-01-01T00:00:00.002Z,d2,2,20,"};
+    tableResultSetEqualTest(
+        "SELECT * FROM table1 WHERE COLUMNS('^s.*') > 1", expectedHeader, 
retArray, DATABASE_NAME);
+
+    expectedHeader = new String[] {"time", "device_id", "s1", "s2"};
+    retArray =
+        new String[] {"1970-01-01T00:00:00.002Z,d2,2,20,", 
"1970-01-01T00:00:00.003Z,d3,3,null,"};
+    tableResultSetEqualTest(
+        "SELECT * FROM table1 WHERE COLUMNS('^s1.*') > 1 order by time",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    // case 3: cannot match any column
+    tableAssertTestFail(
+        "SELECT * FROM table1 WHERE COLUMNS('^b.*') > 1",
+        "701: No matching columns found that match regex '^b.*'",
+        DATABASE_NAME);
+
+    // case 4: invalid input of regex
+    tableAssertTestFail(
+        "SELECT * FROM table1 WHERE COLUMNS('*b') > 1", "701: Invalid regex 
'*b'", DATABASE_NAME);
+  }
+
+  @Test
+  public void otherExceptionTest() {
+    // cannot be used in HAVING clause
+    tableAssertTestFail(
+        "SELECT device_id, count(s1) FROM table1 HAVING COLUMNS('device_id') > 
1",
+        "701: Columns only support to be used in SELECT and WHERE clause",
+        DATABASE_NAME);
+
+    // cannot be used for columns without name
+    tableAssertTestFail(
+        "SELECT COLUMNS(*) FROM (SELECT s1,s1+1 from table1)",
+        "701: Unknown ColumnName",
+        DATABASE_NAME);
+    tableAssertTestFail(
+        "SELECT COLUMNS('s1') FROM (SELECT s1,s1+1 from table1)",
+        "701: Unknown ColumnName",
+        DATABASE_NAME);
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
index 1798a0ed0b7..8bf9fcc2940 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
@@ -43,6 +43,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.BinaryLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.BooleanLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Cast;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CoalesceExpression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Columns;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentDatabase;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentTime;
@@ -1150,6 +1151,11 @@ public class ExpressionAnalyzer {
           String.format("not yet implemented: %s", node.getClass().getName()));
     }
 
+    @Override
+    protected Type visitColumns(Columns node, 
StackableAstVisitorContext<Context> context) {
+      throw new SemanticException("Columns only support to be used in SELECT 
and WHERE clause");
+    }
+
     private Type getOperator(
         StackableAstVisitorContext<Context> context,
         Expression node,
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
index 4da3311814c..8619f5b89eb 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
@@ -55,7 +55,14 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AllColumns;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AllRows;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AlterDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AlterPipe;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ArithmeticBinaryExpression;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ArithmeticUnaryExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AstVisitor;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.BetweenPredicate;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Cast;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CoalesceExpression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Columns;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDevice;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateDB;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CreateIndex;
@@ -77,6 +84,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DropPipePlugin;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DropTable;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DropTopic;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Except;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExistsPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Explain;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExplainAnalyze;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
@@ -88,32 +96,43 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupBy;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupingElement;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GroupingSets;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Identifier;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.IfExpression;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InListExpression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Insert;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertRow;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertRows;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InsertTablet;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Intersect;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.IsNotNullPredicate;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.IsNullPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Join;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.JoinCriteria;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.JoinOn;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.JoinUsing;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LikePredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Limit;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Literal;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LoadTsFile;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LogicalExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LongLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.NaturalJoin;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Node;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.NotExpression;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.NullIfExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Offset;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.OrderBy;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.PipeEnriched;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Property;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.QualifiedName;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.QuantifiedComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Query;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.QuerySpecification;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Relation;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RenameColumn;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RenameTable;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Row;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SearchedCaseExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Select;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SelectItem;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetOperation;
@@ -127,6 +146,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowPipes;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowSubscriptions;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowTables;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowTopics;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SimpleCaseExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SimpleGroupBy;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SingleColumn;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SortItem;
@@ -140,11 +160,13 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TableFunctionArgu
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TableFunctionInvocation;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TableFunctionTableArgument;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TableSubquery;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Trim;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Union;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Update;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.UpdateAssignment;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Use;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Values;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.WhenClause;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.With;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.WithQuery;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.WrappedInsertStatement;
@@ -194,8 +216,12 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.OptionalLong;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -1034,6 +1060,20 @@ public class StatementAnalyzer {
     private void analyzeWhere(Node node, Scope scope, Expression predicate) {
       verifyNoAggregateWindowOrGroupingFunctions(predicate, "WHERE clause");
 
+      // contains Columns, expand them and concat them
+      if (containsColumns(predicate)) {
+        ExpandColumnsVisitor visitor = new ExpandColumnsVisitor(null);
+        List<Expression> expandedExpressions = visitor.process(predicate, 
scope);
+        if (expandedExpressions.isEmpty()) {
+          throw new IllegalStateException("There is at least one result of 
expanded");
+        }
+        if (expandedExpressions.size() >= 2) {
+          predicate = new LogicalExpression(LogicalExpression.Operator.AND, 
expandedExpressions);
+        } else {
+          predicate = expandedExpressions.get(0);
+        }
+      }
+
       ExpressionAnalysis expressionAnalysis = analyzeExpression(predicate, 
scope);
       analysis.recordSubqueries(node, expressionAnalysis);
 
@@ -1061,8 +1101,25 @@ public class StatementAnalyzer {
           analyzeSelectAllColumns(
               (AllColumns) item, node, scope, outputExpressionBuilder, 
selectExpressionBuilder);
         } else if (item instanceof SingleColumn) {
-          analyzeSelectSingleColumn(
-              (SingleColumn) item, node, scope, outputExpressionBuilder, 
selectExpressionBuilder);
+          SingleColumn singleColumn = (SingleColumn) item;
+          Expression selectExpression = singleColumn.getExpression();
+          if (containsColumns(selectExpression)) {
+            ExpandColumnsVisitor visitor =
+                new ExpandColumnsVisitor(singleColumn.getAlias().orElse(null));
+            List<Expression> expandedExpressions = 
visitor.process(selectExpression, scope);
+            if (expandedExpressions.isEmpty()) {
+              throw new IllegalStateException("There is at least one result of 
expanded");
+            }
+            singleColumn.setExpandedExpressions(expandedExpressions);
+            
singleColumn.setAccordingColumnName(visitor.getAccordingColumnNames());
+            for (Expression expression : expandedExpressions) {
+              analyzeSelectSingleColumn(
+                  expression, node, scope, outputExpressionBuilder, 
selectExpressionBuilder);
+            }
+          } else {
+            analyzeSelectSingleColumn(
+                selectExpression, node, scope, outputExpressionBuilder, 
selectExpressionBuilder);
+          }
         } else {
           throw new IllegalArgumentException(
               "Unsupported SelectItem type: " + item.getClass().getName());
@@ -1077,6 +1134,788 @@ public class StatementAnalyzer {
       return outputExpressionBuilder.build();
     }
 
+    /**
+     * Check if there is Columns function in expression, and verify they are 
same if there are multi
+     * Column functions.
+     *
+     * @param expression input expression
+     * @return if there is Columns function in expression
+     * @throws SemanticException if there are multi Columns functions but 
different
+     */
+    private boolean containsColumns(Expression expression) {
+      return containsColumnsHelper(expression) != null;
+    }
+
+    private Node containsColumnsHelper(Node node) {
+      if (node instanceof Columns) {
+        return node;
+      }
+
+      Node target = null;
+      for (Node child : node.getChildren()) {
+        Node childResult = containsColumnsHelper(child);
+
+        if (childResult == null) {
+          continue;
+        }
+
+        // initialize target
+        if (target == null) {
+          target = childResult;
+          continue;
+        }
+
+        if (!childResult.equals(target)) {
+          throw new SemanticException(
+              "Multiple different COLUMNS in the same expression are not 
supported");
+        }
+      }
+      return target;
+    }
+
+    private class ExpandColumnsVisitor extends AstVisitor<List<Expression>, 
Scope> {
+      private final Identifier alias;
+      // Record Columns expanded result in process, not always equals with 
final result
+      private List<Expression> expandedExpressions;
+      // Records the actual output column name of each Expression, used to 
compute output Scope.
+      private List<String> accordingColumnNames;
+
+      private ExpandColumnsVisitor(Identifier alias) {
+        this.alias = alias;
+      }
+
+      public List<String> getAccordingColumnNames() {
+        return accordingColumnNames;
+      }
+
+      protected List<Expression> visitNode(Node node, Scope scope) {
+        throw new UnsupportedOperationException(
+            "This Visitor only supported process of Expression");
+      }
+
+      protected List<Expression> visitExpression(Expression node, Scope scope) 
{
+        if (node.getChildren().isEmpty()) {
+          return Collections.singletonList(node);
+        }
+        throw new UnsupportedOperationException("UnSupported Expression: " + 
node);
+      }
+
+      @Override
+      public List<Expression> visitColumns(Columns node, Scope context) {
+        // avoid redundant process
+        if (expandedExpressions != null) {
+          return expandedExpressions;
+        }
+
+        List<Field> requestedFields = (List<Field>) 
context.getRelationType().getVisibleFields();
+        List<Field> fields = filterInaccessibleFields(requestedFields);
+        if (fields.isEmpty()) {
+          if (!requestedFields.isEmpty()) {
+            throw new SemanticException("Relation not found or not allowed");
+          }
+          throw new SemanticException("COLUMNS not allowed for relation that 
has no columns");
+        }
+
+        ImmutableList.Builder<Expression> matchedColumns = 
ImmutableList.builder();
+        ImmutableList.Builder<String> outputColumnNames = 
ImmutableList.builder();
+        if (node.isColumnsAsterisk()) {
+          for (Field field : fields) {
+            String columnName = field.getName().orElse(null);
+            if (columnName == null) {
+              throw new SemanticException("Unknown ColumnName: " + field);
+            }
+            matchedColumns.add(new Identifier(columnName));
+            outputColumnNames.add(alias == null ? columnName : 
alias.getValue());
+          }
+        } else {
+          Pattern pattern;
+          try {
+            pattern = Pattern.compile(node.getPattern());
+          } catch (PatternSyntaxException e) {
+            throw new SemanticException(String.format("Invalid regex '%s'", 
node.getPattern()));
+          }
+          Matcher matcher = pattern.matcher("");
+
+          for (Field field : fields) {
+            String columnName = field.getName().orElse(null);
+            if (columnName == null) {
+              throw new SemanticException("Unknown ColumnName: " + field);
+            }
+            matcher.reset(columnName);
+            if (matcher.matches()) {
+              matchedColumns.add(new Identifier(columnName));
+
+              // process alias
+              if (alias != null) {
+                try {
+                  outputColumnNames.add(matcher.replaceAll(alias.getValue()));
+                } catch (Exception e) {
+                  throw new SemanticException(e.getMessage());
+                }
+              } else {
+                outputColumnNames.add(columnName);
+              }
+            }
+          }
+        }
+        List<Expression> result = matchedColumns.build();
+        if (result.isEmpty()) {
+          throw new SemanticException(
+              String.format("No matching columns found that match regex '%s'", 
node.getPattern()));
+        }
+        expandedExpressions = result;
+        accordingColumnNames = outputColumnNames.build();
+
+        return result;
+      }
+
+      @Override
+      protected List<Expression> visitArithmeticBinary(
+          ArithmeticBinaryExpression node, Scope context) {
+        List<Expression> leftResult = process(node.getLeft(), context);
+        List<Expression> rightResult = process(node.getRight(), context);
+
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int leftSize = leftResult.size();
+        int rightSize = rightResult.size();
+        int maxSize = Math.max(leftSize, rightSize);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger leftIndex = (leftSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger rightIndex = (rightSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          resultBuilder.add(
+              new ArithmeticBinaryExpression(
+                  node.getOperator(),
+                  leftResult.get(leftIndex.get()),
+                  rightResult.get(rightIndex.get())));
+          baseIndex.getAndIncrement();
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitArithmeticUnary(
+          ArithmeticUnaryExpression node, Scope context) {
+        List<Expression> childResult = process(node.getValue(), context);
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        for (Expression expression : childResult) {
+          resultBuilder.add(new ArithmeticUnaryExpression(node.getSign(), 
expression));
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitBetweenPredicate(BetweenPredicate node, 
Scope context) {
+        List<Expression> valueResult = process(node.getValue(), context);
+        List<Expression> minResult = process(node.getMin(), context);
+        List<Expression> maxResult = process(node.getMax(), context);
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int valueResultSize = valueResult.size();
+        int minResultSize = minResult.size();
+        int maxResultSize = maxResult.size();
+        int maxSize = Math.max(valueResultSize, Math.max(minResultSize, 
maxResultSize));
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger valueIndex = (valueResultSize == maxSize) ? baseIndex : 
new AtomicInteger(0);
+        AtomicInteger minIndex = (minResultSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger maxIndex = (maxResultSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          resultBuilder.add(
+              new BetweenPredicate(
+                  valueResult.get(valueIndex.get()),
+                  minResult.get(minIndex.get()),
+                  maxResult.get(maxIndex.get())));
+          baseIndex.getAndIncrement();
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitCast(Cast node, Scope context) {
+        List<Expression> childResult = process(node.getExpression(), context);
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        for (Expression expression : childResult) {
+          resultBuilder.add(new Cast(expression, node.getType()));
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitCoalesceExpression(CoalesceExpression 
node, Scope context) {
+        ImmutableList.Builder<List<Expression>> childrenResultListBuilder =
+            new ImmutableList.Builder<>();
+        node.getOperands()
+            .forEach(operand -> childrenResultListBuilder.add(process(operand, 
context)));
+        List<List<Expression>> childrenResultList = 
childrenResultListBuilder.build();
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int maxSize = 
childrenResultList.stream().mapToInt(List::size).max().orElse(0);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger[] childrenIndexes = new 
AtomicInteger[childrenResultList.size()];
+        for (int i = 0; i < childrenIndexes.length; i++) {
+          childrenIndexes[i] =
+              (childrenResultList.get(i).size() == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        }
+        for (int i = 0; i < maxSize; i++) {
+          ImmutableList.Builder<Expression> operandListBuilder = new 
ImmutableList.Builder<>();
+          for (int j = 0; j < childrenIndexes.length; j++) {
+            int operandIndexInResult = childrenIndexes[j].get();
+            
operandListBuilder.add(childrenResultList.get(j).get(operandIndexInResult));
+          }
+          resultBuilder.add(new 
CoalesceExpression(operandListBuilder.build()));
+          baseIndex.getAndIncrement();
+        }
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitComparisonExpression(
+          ComparisonExpression node, Scope context) {
+        List<Expression> leftResult = process(node.getLeft(), context);
+        List<Expression> rightResult = process(node.getRight(), context);
+
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int leftSize = leftResult.size();
+        int rightSize = rightResult.size();
+        int maxSize = Math.max(leftSize, rightSize);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger leftIndex = (leftSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger rightIndex = (rightSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          resultBuilder.add(
+              new ComparisonExpression(
+                  node.getOperator(),
+                  leftResult.get(leftIndex.get()),
+                  rightResult.get(rightIndex.get())));
+          baseIndex.getAndIncrement();
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitDereferenceExpression(
+          DereferenceExpression node, Scope context) {
+        process(node.getBase(), context);
+        if (expandedExpressions == null) {
+          return Collections.singletonList(node);
+        }
+        throw new SemanticException("Columns are not supported in 
DereferenceExpression");
+      }
+
+      @Override
+      protected List<Expression> visitExists(ExistsPredicate node, Scope 
context) {
+        // We don't need to process Query here
+        return Collections.singletonList(node);
+      }
+
+      @Override
+      protected List<Expression> visitFunctionCall(FunctionCall node, Scope 
context) {
+        ImmutableList.Builder<List<Expression>> childrenResultListBuilder =
+            new ImmutableList.Builder<>();
+        node.getArguments()
+            .forEach(operand -> childrenResultListBuilder.add(process(operand, 
context)));
+        List<List<Expression>> childrenResultList = 
childrenResultListBuilder.build();
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int maxSize = 
childrenResultList.stream().mapToInt(List::size).max().orElse(0);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger[] childrenIndexes = new 
AtomicInteger[childrenResultList.size()];
+        for (int i = 0; i < childrenIndexes.length; i++) {
+          childrenIndexes[i] =
+              (childrenResultList.get(i).size() == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        }
+        for (int i = 0; i < maxSize; i++) {
+          ImmutableList.Builder<Expression> operandListBuilder = new 
ImmutableList.Builder<>();
+          for (int j = 0; j < childrenIndexes.length; j++) {
+            int operandIndexInResult = childrenIndexes[j].get();
+            
operandListBuilder.add(childrenResultList.get(j).get(operandIndexInResult));
+          }
+          resultBuilder.add(new FunctionCall(node.getName(), 
operandListBuilder.build()));
+          baseIndex.getAndIncrement();
+        }
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitIdentifier(Identifier node, Scope 
context) {
+        return Collections.singletonList(node);
+      }
+
+      @Override
+      protected List<Expression> visitIfExpression(IfExpression node, Scope 
context) {
+        List<Expression> firstResult = process(node.getCondition(), context);
+        List<Expression> secondResult = process(node.getTrueValue(), context);
+        List<Expression> thirdResult =
+            node.getFalseValue().isPresent() ? 
process(node.getFalseValue().get(), context) : null;
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int firstSize = firstResult.size();
+        int secondSize = secondResult.size();
+        int thirdSize = thirdResult == null ? 0 : thirdResult.size();
+        int maxSize = Math.max(thirdSize, Math.max(firstSize, secondSize));
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger firstIndex = (firstSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger secondIndex = (secondSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger thirdIndex = (thirdSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          resultBuilder.add(
+              new IfExpression(
+                  firstResult.get(firstIndex.get()),
+                  secondResult.get(secondIndex.get()),
+                  thirdResult == null ? null : 
thirdResult.get(thirdIndex.get())));
+          baseIndex.getAndIncrement();
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitInListExpression(InListExpression node, 
Scope context) {
+        ImmutableList.Builder<List<Expression>> childrenResultListBuilder =
+            new ImmutableList.Builder<>();
+        node.getValues()
+            .forEach(operand -> childrenResultListBuilder.add(process(operand, 
context)));
+        List<List<Expression>> childrenResultList = 
childrenResultListBuilder.build();
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int maxSize = 
childrenResultList.stream().mapToInt(List::size).max().orElse(0);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger[] childrenIndexes = new 
AtomicInteger[childrenResultList.size()];
+        for (int i = 0; i < childrenIndexes.length; i++) {
+          childrenIndexes[i] =
+              (childrenResultList.get(i).size() == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        }
+        for (int i = 0; i < maxSize; i++) {
+          ImmutableList.Builder<Expression> operandListBuilder = new 
ImmutableList.Builder<>();
+          for (int j = 0; j < childrenIndexes.length; j++) {
+            int operandIndexInResult = childrenIndexes[j].get();
+            
operandListBuilder.add(childrenResultList.get(j).get(operandIndexInResult));
+          }
+          resultBuilder.add(new InListExpression(operandListBuilder.build()));
+          baseIndex.getAndIncrement();
+        }
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitInPredicate(InPredicate node, Scope 
context) {
+        List<Expression> leftResult = process(node.getValue(), context);
+        List<Expression> rightResult = process(node.getValueList(), context);
+
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int leftSize = leftResult.size();
+        int rightSize = rightResult.size();
+        int maxSize = Math.max(leftSize, rightSize);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger leftIndex = (leftSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger rightIndex = (rightSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          resultBuilder.add(
+              new InPredicate(leftResult.get(leftIndex.get()), 
rightResult.get(rightIndex.get())));
+          baseIndex.getAndIncrement();
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitIsNotNullPredicate(IsNotNullPredicate 
node, Scope context) {
+        List<Expression> childResult = process(node.getValue(), context);
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        for (Expression expression : childResult) {
+          resultBuilder.add(new IsNotNullPredicate(expression));
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitIsNullPredicate(IsNullPredicate node, 
Scope context) {
+        List<Expression> childResult = process(node.getValue(), context);
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        for (Expression expression : childResult) {
+          resultBuilder.add(new IsNullPredicate(expression));
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitLikePredicate(LikePredicate node, Scope 
context) {
+        List<Expression> firstResult = process(node.getValue(), context);
+        List<Expression> secondResult = process(node.getPattern(), context);
+        List<Expression> thirdResult =
+            node.getEscape().isPresent() ? process(node.getEscape().get(), 
context) : null;
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int firstSize = firstResult.size();
+        int secondSize = secondResult.size();
+        int thirdSize = thirdResult == null ? 0 : thirdResult.size();
+        int maxSize = Math.max(thirdSize, Math.max(firstSize, secondSize));
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger firstIndex = (firstSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger secondIndex = (secondSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger thirdIndex = (thirdSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          resultBuilder.add(
+              new LikePredicate(
+                  firstResult.get(firstIndex.get()),
+                  secondResult.get(secondIndex.get()),
+                  thirdResult == null ? null : 
thirdResult.get(thirdIndex.get())));
+          baseIndex.getAndIncrement();
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitLiteral(Literal node, Scope context) {
+        return Collections.singletonList(node);
+      }
+
+      @Override
+      protected List<Expression> visitLogicalExpression(LogicalExpression 
node, Scope context) {
+        ImmutableList.Builder<List<Expression>> childrenResultListBuilder =
+            new ImmutableList.Builder<>();
+        node.getTerms()
+            .forEach(operand -> childrenResultListBuilder.add(process(operand, 
context)));
+        List<List<Expression>> childrenResultList = 
childrenResultListBuilder.build();
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int maxSize = 
childrenResultList.stream().mapToInt(List::size).max().orElse(0);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger[] childrenIndexes = new 
AtomicInteger[childrenResultList.size()];
+        for (int i = 0; i < childrenIndexes.length; i++) {
+          childrenIndexes[i] =
+              (childrenResultList.get(i).size() == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        }
+        for (int i = 0; i < maxSize; i++) {
+          ImmutableList.Builder<Expression> operandListBuilder = new 
ImmutableList.Builder<>();
+          for (int j = 0; j < childrenIndexes.length; j++) {
+            int operandIndexInResult = childrenIndexes[j].get();
+            
operandListBuilder.add(childrenResultList.get(j).get(operandIndexInResult));
+          }
+          resultBuilder.add(new LogicalExpression(node.getOperator(), 
operandListBuilder.build()));
+          baseIndex.getAndIncrement();
+        }
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitNotExpression(NotExpression node, Scope 
context) {
+        List<Expression> childResult = process(node.getValue(), context);
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        for (Expression expression : childResult) {
+          resultBuilder.add(new NotExpression(expression));
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitNullIfExpression(NullIfExpression node, 
Scope context) {
+        throw new SemanticException(
+            String.format("%s are not supported now", 
node.getClass().getSimpleName()));
+      }
+
+      @Override
+      protected List<Expression> visitQuantifiedComparisonExpression(
+          QuantifiedComparisonExpression node, Scope context) {
+        List<Expression> childResult = process(node.getValue(), context);
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        for (Expression expression : childResult) {
+          resultBuilder.add(
+              new QuantifiedComparisonExpression(
+                  node.getOperator(), node.getQuantifier(), expression, 
node.getSubquery()));
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitRow(Row node, Scope context) {
+        throw new SemanticException(
+            String.format("%s are not supported now", 
node.getClass().getSimpleName()));
+      }
+
+      @Override
+      protected List<Expression> visitSearchedCaseExpression(
+          SearchedCaseExpression node, Scope context) {
+        ImmutableList.Builder<List<Expression>> firstChildResultListBuilder =
+            new ImmutableList.Builder<>();
+        node.getWhenClauses()
+            .forEach(when -> firstChildResultListBuilder.add(process(when, 
context)));
+        List<List<Expression>> firstChildResultList = 
firstChildResultListBuilder.build();
+        List<Expression> secondResult =
+            node.getDefaultValue().isPresent()
+                ? process(node.getDefaultValue().get(), context)
+                : null;
+
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int secondSize = secondResult == null ? 0 : secondResult.size();
+        int maxSize = 
firstChildResultList.stream().mapToInt(List::size).max().orElse(0);
+        maxSize = Math.max(maxSize, secondSize);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger[] childrenIndexes = new 
AtomicInteger[firstChildResultList.size()];
+        for (int i = 0; i < childrenIndexes.length; i++) {
+          childrenIndexes[i] =
+              (firstChildResultList.get(i).size() == maxSize) ? baseIndex : 
new AtomicInteger(0);
+        }
+        AtomicInteger secondIndex = (secondSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          ImmutableList.Builder<WhenClause> operandListBuilder = new 
ImmutableList.Builder<>();
+          for (int j = 0; j < childrenIndexes.length; j++) {
+            int operandIndexInResult = childrenIndexes[j].get();
+            operandListBuilder.add(
+                (WhenClause) 
firstChildResultList.get(j).get(operandIndexInResult));
+          }
+
+          resultBuilder.add(
+              new SearchedCaseExpression(
+                  operandListBuilder.build(),
+                  (secondResult == null ? null : 
secondResult.get(secondIndex.get()))));
+          baseIndex.getAndIncrement();
+        }
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitSimpleCaseExpression(
+          SimpleCaseExpression node, Scope context) {
+        List<Expression> firstResult = process(node.getOperand(), context);
+        ImmutableList.Builder<List<Expression>> whenResultListBuilder =
+            new ImmutableList.Builder<>();
+        node.getWhenClauses().forEach(when -> 
whenResultListBuilder.add(process(when, context)));
+        List<List<Expression>> whenResultList = whenResultListBuilder.build();
+        List<Expression> secondResult =
+            node.getDefaultValue().isPresent()
+                ? process(node.getDefaultValue().get(), context)
+                : null;
+
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int firstSize = firstResult.size();
+        int secondSize = secondResult == null ? 0 : secondResult.size();
+        int maxSize = 
whenResultList.stream().mapToInt(List::size).max().orElse(0);
+        maxSize = Math.max(Math.max(firstSize, maxSize), secondSize);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger[] childrenIndexes = new 
AtomicInteger[whenResultList.size()];
+        AtomicInteger firstIndex = (firstSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < childrenIndexes.length; i++) {
+          childrenIndexes[i] =
+              (whenResultList.get(i).size() == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        }
+        AtomicInteger secondIndex = (secondSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          ImmutableList.Builder<WhenClause> operandListBuilder = new 
ImmutableList.Builder<>();
+          for (int j = 0; j < childrenIndexes.length; j++) {
+            int operandIndexInResult = childrenIndexes[j].get();
+            operandListBuilder.add((WhenClause) 
whenResultList.get(j).get(operandIndexInResult));
+          }
+
+          resultBuilder.add(
+              new SimpleCaseExpression(
+                  firstResult.get(firstIndex.get()),
+                  operandListBuilder.build(),
+                  (secondResult == null ? null : 
secondResult.get(secondIndex.get()))));
+          baseIndex.getAndIncrement();
+        }
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitSubqueryExpression(SubqueryExpression 
node, Scope context) {
+        // We don't need to process Query here
+        return Collections.singletonList(node);
+      }
+
+      @Override
+      protected List<Expression> visitTrim(Trim node, Scope context) {
+        List<Expression> firstResult = process(node.getTrimSource(), context);
+        List<Expression> secondResult =
+            node.getTrimCharacter().isPresent()
+                ? process(node.getTrimCharacter().get(), context)
+                : null;
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int firstSize = firstResult.size();
+        int secondSize = secondResult == null ? 0 : secondResult.size();
+        int maxSize = Math.max(secondSize, firstSize);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger firstIndex = (firstSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger secondIndex = (secondSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          resultBuilder.add(
+              new Trim(
+                  node.getSpecification(),
+                  firstResult.get(firstIndex.get()),
+                  secondResult == null ? null : 
secondResult.get(secondIndex.get())));
+          baseIndex.getAndIncrement();
+        }
+
+        return resultBuilder.build();
+      }
+
+      @Override
+      protected List<Expression> visitWhenClause(WhenClause node, Scope 
context) {
+        List<Expression> leftResult = process(node.getOperand(), context);
+        List<Expression> rightResult = process(node.getResult(), context);
+
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        int leftSize = leftResult.size();
+        int rightSize = rightResult.size();
+        int maxSize = Math.max(leftSize, rightSize);
+
+        AtomicInteger baseIndex = new AtomicInteger(0);
+        // if child is expanded, index of it reference the baseIndex, else the 
index of it always be
+        // 0
+        AtomicInteger leftIndex = (leftSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        AtomicInteger rightIndex = (rightSize == maxSize) ? baseIndex : new 
AtomicInteger(0);
+        for (int i = 0; i < maxSize; i++) {
+          resultBuilder.add(
+              new WhenClause(leftResult.get(leftIndex.get()), 
rightResult.get(rightIndex.get())));
+          baseIndex.getAndIncrement();
+        }
+
+        return resultBuilder.build();
+      }
+    }
+
     private void analyzeSelectAllColumns(
         AllColumns allColumns,
         QuerySpecification node,
@@ -1318,12 +2157,11 @@ public class StatementAnalyzer {
     //    }
 
     private void analyzeSelectSingleColumn(
-        SingleColumn singleColumn,
+        Expression expression,
         QuerySpecification node,
         Scope scope,
         ImmutableList.Builder<Expression> outputExpressionBuilder,
         ImmutableList.Builder<Analysis.SelectExpression> 
selectExpressionBuilder) {
-      Expression expression = singleColumn.getExpression();
       ExpressionAnalysis expressionAnalysis = analyzeExpression(expression, 
scope);
       analysis.recordSubqueries(node, expressionAnalysis);
       outputExpressionBuilder.add(expression);
@@ -1578,6 +2416,70 @@ public class StatementAnalyzer {
         } else if (item instanceof SingleColumn) {
           SingleColumn column = (SingleColumn) item;
           Expression expression = column.getExpression();
+
+          // process Columns
+          List<Expression> expandedExpressions = 
column.getExpandedExpressions();
+          if (expandedExpressions != null) {
+            for (int i = 0; i < expandedExpressions.size(); i++) {
+              expression = expandedExpressions.get(i);
+
+              // Different from process of normal SingleColumn, alias has been 
processed when
+              // expanded Columns in analyzeSelect, so we needn't process 
alias here.
+              Optional<String> field = Optional.empty();
+              Optional<QualifiedObjectName> originTable = Optional.empty();
+              // Put accordingColumnName into originColumn to rename expr in 
OutputNode if necessary
+              Optional<String> originColumn = 
Optional.of(column.getAccordingColumnNames().get(i));
+              QualifiedName name = null;
+
+              if (expression instanceof Identifier) {
+                name = QualifiedName.of(((Identifier) expression).getValue());
+              }
+
+              if (name != null) {
+                Field matchingField = null;
+                try {
+                  matchingField = 
analysis.getResolvedField(expression).getField();
+                } catch (IllegalArgumentException e) {
+                  List<Field> matchingFields = 
sourceScope.getRelationType().resolveFields(name);
+                  if (!matchingFields.isEmpty()) {
+                    matchingField = matchingFields.get(0);
+                  }
+                }
+                if (matchingField != null) {
+                  originTable = matchingField.getOriginTable();
+                }
+
+                // expression is Identifier, the name of field is original 
column name
+                field = originColumn;
+              }
+
+              boolean aliased = column.getAlias().isPresent();
+              Field newField =
+                  Field.newUnqualified(
+                      aliased ? originColumn : field,
+                      analysis.getType(expression),
+                      TsTableColumnCategory.FIELD,
+                      originTable,
+                      originColumn,
+                      aliased);
+              // outputExpressions to look up the type
+              if (originTable.isPresent()) {
+                analysis.addSourceColumns(
+                    newField,
+                    ImmutableSet.of(
+                        new Analysis.SourceColumn(
+                            originTable.get(),
+                            originColumn.orElseThrow(
+                                () -> new NoSuchElementException("No value 
present")))));
+              } else {
+                analysis.addSourceColumns(
+                    newField, analysis.getExpressionSourceColumns(expression));
+              }
+              outputFields.add(newField);
+            }
+            continue;
+          }
+
           Optional<Identifier> field = column.getAlias();
 
           Optional<QualifiedObjectName> originTable = Optional.empty();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/TableLogicalPlanner.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/TableLogicalPlanner.java
index 02ec34740a9..b7cac999941 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/TableLogicalPlanner.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/TableLogicalPlanner.java
@@ -84,6 +84,7 @@ import java.util.Optional;
 import static java.util.Objects.requireNonNull;
 import static 
org.apache.iotdb.db.queryengine.metric.QueryPlanCostMetricSet.LOGICAL_PLANNER;
 import static 
org.apache.iotdb.db.queryengine.metric.QueryPlanCostMetricSet.LOGICAL_PLAN_OPTIMIZE;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.OutputNode.COLUMN_NAME_PREFIX;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDevice.COUNT_DEVICE_HEADER_STRING;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowDevice.getDeviceColumnHeaderList;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.type.InternalTypeManager.getTSDataType;
@@ -242,7 +243,17 @@ public class TableLogicalPlanner {
     } else {
       RelationType outputDescriptor = analysis.getOutputDescriptor();
       for (Field field : outputDescriptor.getVisibleFields()) {
-        String name = field.getName().orElse("_col" + columnNumber);
+        String name = field.getName().orElse(null);
+
+        if (name == null) {
+          String originColumnName = field.getOriginColumnName().orElse(null);
+          StringBuilder stringBuilder = new 
StringBuilder(COLUMN_NAME_PREFIX).append(columnNumber);
+          // process of expr with Column, we record originColumnName in Field 
when analyze
+          if (originColumnName != null) {
+            stringBuilder.append('_').append(originColumnName);
+          }
+          name = stringBuilder.toString();
+        }
 
         names.add(name);
         int fieldIndex = outputDescriptor.indexOf(field);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OutputNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OutputNode.java
index c7afff2ddd8..d9d166e7116 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OutputNode.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OutputNode.java
@@ -46,6 +46,8 @@ public class OutputNode extends SingleChildProcessNode {
   // column name = symbol
   private final List<Symbol> outputSymbols;
 
+  public static final String COLUMN_NAME_PREFIX = "_col";
+
   public OutputNode(
       PlanNodeId id, PlanNode child, List<String> columnNames, List<Symbol> 
outputSymbols) {
     super(id, child);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
index 1a022bc3159..ff076f3e1ed 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
@@ -677,6 +677,10 @@ public abstract class AstVisitor<R, C> {
     return visitStatement(node, context);
   }
 
+  protected R visitColumns(Columns node, C context) {
+    return visitExpression(node, context);
+  }
+
   protected R visitSetSqlDialect(SetSqlDialect node, C context) {
     return visitStatement(node, context);
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Columns.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Columns.java
new file mode 100644
index 00000000000..6860940aa28
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Columns.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or 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.queryengine.plan.relational.sql.ast;
+
+import com.google.common.collect.ImmutableList;
+
+import javax.annotation.Nonnull;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+public final class Columns extends Expression {
+  private final String pattern;
+
+  public Columns(@Nonnull NodeLocation location, String pattern) {
+    super(requireNonNull(location, "location is null"));
+    this.pattern = pattern;
+  }
+
+  public String getPattern() {
+    return pattern;
+  }
+
+  public boolean isColumnsAsterisk() {
+    return pattern == null;
+  }
+
+  @Override
+  public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
+    return visitor.visitColumns(this, context);
+  }
+
+  @Override
+  public List<Node> getChildren() {
+    return ImmutableList.of();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Columns columns = (Columns) o;
+    return Objects.equals(columns.pattern, this.pattern);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(pattern);
+  }
+
+  @Override
+  public boolean shallowEquals(Node other) {
+    if (!sameClass(this, other)) {
+      return false;
+    }
+
+    Columns otherColumns = (Columns) other;
+    return Objects.equals(otherColumns.pattern, this.pattern);
+  }
+
+  // =============== serialize =================
+  @Override
+  public TableExpressionType getExpressionType() {
+    throw new UnsupportedOperationException("Columns should be expanded in 
Analyze stage");
+  }
+
+  @Override
+  public void serialize(DataOutputStream stream) throws IOException {
+    throw new UnsupportedOperationException("Columns should be expanded in 
Analyze stage");
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SingleColumn.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SingleColumn.java
index e4e3cf9f745..9d8d0bb2b9d 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SingleColumn.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SingleColumn.java
@@ -34,6 +34,11 @@ public class SingleColumn extends SelectItem {
   @Nullable private final Identifier alias;
   private final Expression expression;
 
+  // If there is Columns in expression, records the result of expanded
+  private List<Expression> expandedExpressions;
+  // Records the actual output column name of each Expression, used to compute 
output Scope.
+  private List<String> accordingColumnNames;
+
   public SingleColumn(Expression expression) {
     super(null);
     this.expression = requireNonNull(expression, "expression is null");
@@ -66,6 +71,22 @@ public class SingleColumn extends SelectItem {
     return expression;
   }
 
+  public List<Expression> getExpandedExpressions() {
+    return expandedExpressions;
+  }
+
+  public void setExpandedExpressions(List<Expression> expandedExpressions) {
+    this.expandedExpressions = expandedExpressions;
+  }
+
+  public List<String> getAccordingColumnNames() {
+    return accordingColumnNames;
+  }
+
+  public void setAccordingColumnName(List<String> accordingColumnNames) {
+    this.accordingColumnNames = accordingColumnNames;
+  }
+
   @Override
   public boolean equals(Object obj) {
     if (this == obj) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Trim.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Trim.java
index 78733b7a5ad..235e9e079c3 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Trim.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Trim.java
@@ -45,7 +45,7 @@ public class Trim extends Expression {
     super(null);
     this.specification = requireNonNull(specification, "specification is 
null");
     this.trimSource = requireNonNull(trimSource, "trimSource is null");
-    this.trimCharacter = requireNonNull(trimCharacter, "trimCharacter is 
null");
+    this.trimCharacter = trimCharacter;
   }
 
   public Trim(NodeLocation location, Specification specification, Expression 
trimSource) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
index 5b81f09e509..cc40c2f1096 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
@@ -50,6 +50,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Cast;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ClearCache;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CoalesceExpression;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ColumnDefinition;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Columns;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountDevice;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CountStatement;
@@ -2809,6 +2810,16 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     }
   }
 
+  @Override
+  public Node visitColumns(RelationalSqlParser.ColumnsContext ctx) {
+    String pattern = null;
+    RelationalSqlParser.StringContext context = ctx.pattern;
+    if (context != null) {
+      pattern = unquote(context.getText());
+    }
+    return new Columns(getLocation(ctx), pattern);
+  }
+
   // ************** literals **************
 
   @Override
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ExpressionFormatter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ExpressionFormatter.java
index d6222564ec7..b4b3cb2c4af 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ExpressionFormatter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ExpressionFormatter.java
@@ -29,6 +29,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.BinaryLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.BooleanLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Cast;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CoalesceExpression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Columns;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentDatabase;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentTime;
@@ -542,6 +543,11 @@ public final class ExpressionFormatter {
       return node.getValue();
     }
 
+    @Override
+    protected String visitColumns(Columns node, Void context) {
+      return "COLUMNS(" + (node.isColumnsAsterisk() ? "*" : node.getPattern()) 
+ ")";
+    }
+
     private String formatBinaryExpression(String operator, Expression left, 
Expression right) {
       return '(' + process(left, null) + ' ' + operator + ' ' + process(right, 
null) + ')';
     }
diff --git 
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
 
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
index 885c2cb2ac8..2b1f6b23b8d 100644
--- 
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
+++ 
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
@@ -953,6 +953,7 @@ primaryExpression
     | dateExpression                                                           
           #dateTimeExpression
     | '(' expression (',' expression)+ ')'                                     
           #rowConstructor
     | ROW '(' expression (',' expression)* ')'                                 
           #rowConstructor
+    | COLUMNS '(' (ASTERISK | pattern=string) ')'                              
           #columns
     | qualifiedName '(' (label=identifier '.')? ASTERISK ')'                   
           #functionCall
     | qualifiedName '(' (setQuantifier? expression (',' expression)*)?')'      
           #functionCall
     | '(' query ')'                                                            
           #subqueryExpression

Reply via email to