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