You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by li...@apache.org on 2021/05/25 10:23:28 UTC

[iotdb] branch master updated: [IOTDB-1022] ANTLR Grammar & Logical Operator for Arithmetic Operations and Nested Operations in SELECT Clauses (#3236)

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

liudw 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 dae1f15  [IOTDB-1022] ANTLR Grammar & Logical Operator for Arithmetic Operations and Nested Operations in SELECT Clauses (#3236)
dae1f15 is described below

commit dae1f152ac19771b221f66cffe2e0b77d505c436
Author: Steve Yurong Su <st...@outlook.com>
AuthorDate: Tue May 25 05:22:58 2021 -0500

    [IOTDB-1022] ANTLR Grammar & Logical Operator for Arithmetic Operations and Nested Operations in SELECT Clauses (#3236)
---
 .../antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4   | 131 ++-----
 .../cluster/query/ClusterPhysicalGenerator.java    |   8 +-
 .../apache/iotdb/cluster/query/ClusterPlanner.java |  43 ++-
 .../query/ClusterPhysicalGeneratorTest.java        |  10 +-
 .../org/apache/iotdb/db/metadata/MManager.java     |   3 -
 .../org/apache/iotdb/db/metadata/PartialPath.java  |  14 -
 .../main/java/org/apache/iotdb/db/qp/Planner.java  | 186 ++--------
 .../org/apache/iotdb/db/qp/logical/Operator.java   |   1 -
 .../db/qp/logical/crud/DeleteDataOperator.java     |  17 +-
 .../iotdb/db/qp/logical/crud/InsertOperator.java   |  15 +-
 .../iotdb/db/qp/logical/crud/QueryOperator.java    |  44 ++-
 .../iotdb/db/qp/logical/crud/SFWOperator.java      |  98 -----
 .../iotdb/db/qp/logical/crud/SelectOperator.java   | 118 +++---
 .../db/qp/logical/sys/CreateIndexOperator.java     |  17 +-
 .../iotdb/db/qp/logical/sys/DropIndexOperator.java |  18 +-
 .../iotdb/db/qp/physical/crud/AggregationPlan.java |   6 +-
 .../db/qp/physical/crud/AlignByDevicePlan.java     |   6 +
 .../iotdb/db/qp/physical/crud/LastQueryPlan.java   |  30 +-
 .../iotdb/db/qp/physical/crud/QueryPlan.java       |  24 +-
 .../db/qp/physical/crud/RawDataQueryPlan.java      |  67 ++++
 .../apache/iotdb/db/qp/physical/crud/UDFPlan.java  |   4 +-
 .../apache/iotdb/db/qp/physical/crud/UDTFPlan.java |  83 ++++-
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    | 276 ++++++--------
 .../iotdb/db/qp/strategy/LogicalChecker.java       | 108 ++++++
 .../iotdb/db/qp/strategy/LogicalGenerator.java     |  63 +++-
 .../iotdb/db/qp/strategy/PhysicalGenerator.java    | 195 +++-------
 .../qp/strategy/optimizer/ConcatPathOptimizer.java | 406 ++++-----------------
 .../qp/strategy/optimizer/ILogicalOptimizer.java   |   2 +-
 .../qp/strategy/optimizer/RemoveNotOptimizer.java  |   7 +-
 .../apache/iotdb/db/qp/utils/WildcardsRemover.java | 160 ++++++++
 .../apache/iotdb/db/query/dataset/UDTFDataSet.java |   2 +-
 .../iotdb/db/query/executor/LastQueryExecutor.java |  12 +-
 .../iotdb/db/query/expression/Expression.java      |  31 +-
 .../iotdb/db/query/expression/ResultColumn.java    | 139 +++++++
 .../expression/binary/AdditionExpression.java}     |  21 +-
 .../query/expression/binary/BinaryExpression.java  | 118 ++++++
 .../expression/binary/DivisionExpression.java}     |  21 +-
 .../expression/binary/ModuloExpression.java}       |  21 +-
 .../binary/MultiplicationExpression.java           |  19 +-
 .../expression/binary/SubtractionExpression.java}  |  21 +-
 .../query/expression/unary/FunctionExpression.java | 232 ++++++++++++
 .../db/query/expression/unary/MinusExpression.java |  78 ++++
 .../query/expression/unary/TimeSeriesOperand.java  |  82 +++++
 .../db/query/udf/core/context/UDFContext.java      | 142 -------
 .../db/query/udf/core/executor/UDTFExecutor.java   |  17 +-
 .../query/udf/service/UDFRegistrationService.java  |   6 +-
 .../org/apache/iotdb/db/service/TSServiceImpl.java |  43 +--
 .../iotdb/db/integration/IoTDBUDFManagementIT.java |  10 +-
 .../db/integration/IoTDBUDTFHybridQueryIT.java     |   3 +-
 .../iotdb/db/qp/logical/IndexLogicalPlanTest.java  |  48 ++-
 .../iotdb/db/qp/logical/LogicalPlanSmallTest.java  |  88 ++---
 .../query/dataset/UDTFAlignByTimeDataSetTest.java  |  18 +-
 .../zeppelin/iotdb/IoTDBInterpreterTest.java       |  27 +-
 53 files changed, 1874 insertions(+), 1485 deletions(-)

diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
index c7507ec..5ce6135 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
@@ -103,71 +103,30 @@ statement
     | START TRIGGER triggerName=ID #startTrigger
     | STOP TRIGGER triggerName=ID #stopTrigger
     | SHOW TRIGGERS #showTriggers
-    | SELECT topClause? selectElements
-    fromClause
-    whereClause?
-    specialClause? #selectStatement
+    | selectClause fromClause whereClause? specialClause? #selectStatement
     ;
 
-selectElements
-    : aggregationCall (COMMA aggregationCall)* #aggregationElement
-    | tableCall (COMMA tableCall)* #tableElement
-    | lastClause #lastElement
-    | asClause (COMMA asClause)* #asElement
-    | functionAsClause (COMMA functionAsClause)* #functionAsElement
-    ;
-
-aggregationCall
-    : builtInFunctionCall
-    | udfCall
-    ;
-
-tableCall
-    : suffixPath
-    | udfCall
-    | SINGLE_QUOTE_STRING_LITERAL
-    ;
-
-udfCall
-    : udfName=ID LR_BRACKET udfSuffixPaths udfAttribute* RR_BRACKET
-    ;
-
-udfSuffixPaths
-    : suffixPath (COMMA suffixPath)*
-    ;
-
-udfAttribute
-    : COMMA udfAttributeKey=stringLiteral OPERATOR_EQ udfAttributeValue=stringLiteral
-    ;
-
-builtInFunctionCall
-    : functionName LR_BRACKET suffixPath RR_BRACKET
-    ;
-
-functionName
-    : MIN_TIME
-    | MAX_TIME
-    | MIN_VALUE
-    | MAX_VALUE
-    | COUNT
-    | AVG
-    | FIRST_VALUE
-    | SUM
-    | LAST_VALUE
-    ;
+selectClause
+   : SELECT (LAST | topClause)? resultColumn (COMMA resultColumn)*
+   ;
 
-functionAsClause
-    : builtInFunctionCall (AS ID)?
-    ;
+resultColumn
+   : expression (AS ID)?
+   ;
 
-lastClause
-    : LAST suffixPath (COMMA suffixPath)*
-    | LAST asClause (COMMA asClause)*
-    ;
+expression
+   : LR_BRACKET unary=expression RR_BRACKET
+   | (PLUS | MINUS) unary=expression
+   | leftExpression=expression (STAR | DIV | MOD) rightExpression=expression
+   | leftExpression=expression (PLUS | MINUS) rightExpression=expression
+   | functionName=suffixPath LR_BRACKET expression (COMMA expression)* functionAttribute* RR_BRACKET
+   | suffixPath
+   | literal=SINGLE_QUOTE_STRING_LITERAL
+   ;
 
-asClause
-    : suffixPath (AS ID)?
-    ;
+functionAttribute
+   : COMMA functionAttributeKey=stringLiteral OPERATOR_EQ functionAttributeValue=stringLiteral
+   ;
 
 alias
     : LR_BRACKET ID RR_BRACKET
@@ -523,14 +482,6 @@ nodeName
     | COUNT
     | NODES
     | LEVEL
-    | MIN_TIME
-    | MAX_TIME
-    | MIN_VALUE
-    | MAX_VALUE
-    | AVG
-    | FIRST_VALUE
-    | SUM
-    | LAST_VALUE
     | LAST
     | DISABLE
     | ALIGN
@@ -634,14 +585,6 @@ nodeNameWithoutStar
     | COUNT
     | NODES
     | LEVEL
-    | MIN_TIME
-    | MAX_TIME
-    | MIN_VALUE
-    | MAX_VALUE
-    | AVG
-    | FIRST_VALUE
-    | SUM
-    | LAST_VALUE
     | LAST
     | DISABLE
     | ALIGN
@@ -1101,38 +1044,6 @@ LEVEL
     : L E V E L
     ;
 
-MIN_TIME
-    : M I N UNDERLINE T I M E
-    ;
-
-MAX_TIME
-    : M A X UNDERLINE T I M E
-    ;
-
-MIN_VALUE
-    : M I N UNDERLINE V A L U E
-    ;
-
-MAX_VALUE
-    : M A X UNDERLINE V A L U E
-    ;
-
-AVG
-    : A V G
-    ;
-
-FIRST_VALUE
-    : F I R S T UNDERLINE V A L U E
-    ;
-
-SUM
-    : S U M
-    ;
-
-LAST_VALUE
-    : L A S T UNDERLINE V A L U E
-    ;
-
 LAST
     : L A S T
     ;
@@ -1370,6 +1281,10 @@ MINUS : '-';
 
 PLUS : '+';
 
+DIV : '/';
+
+MOD : '%';
+
 DOT : '.';
 
 LR_BRACKET : '(';
diff --git a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPhysicalGenerator.java b/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPhysicalGenerator.java
index c074d92..9e5e740 100644
--- a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPhysicalGenerator.java
+++ b/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPhysicalGenerator.java
@@ -26,7 +26,7 @@ import org.apache.iotdb.db.exception.metadata.MetadataException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
 import org.apache.iotdb.db.metadata.PartialPath;
 import org.apache.iotdb.db.qp.logical.Operator;
-import org.apache.iotdb.db.qp.logical.crud.SFWOperator;
+import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
 import org.apache.iotdb.db.qp.logical.sys.LoadConfigurationOperator.LoadConfigurationOperatorType;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
 import org.apache.iotdb.db.qp.physical.sys.LoadConfigurationPlan;
@@ -64,12 +64,12 @@ public class ClusterPhysicalGenerator extends PhysicalGenerator {
   }
 
   @Override
-  protected List<TSDataType> getSeriesTypes(List<PartialPath> paths) throws MetadataException {
+  public List<TSDataType> getSeriesTypes(List<PartialPath> paths) throws MetadataException {
     return getCMManager().getSeriesTypesByPaths(paths, null).left;
   }
 
   @Override
-  protected Pair<List<PartialPath>, Map<String, Integer>> getSeriesSchema(List<PartialPath> paths)
+  public Pair<List<PartialPath>, Map<String, Integer>> getSeriesSchema(List<PartialPath> paths)
       throws MetadataException {
     return getCMManager().getSeriesSchemas(paths);
   }
@@ -88,7 +88,7 @@ public class ClusterPhysicalGenerator extends PhysicalGenerator {
   public PhysicalPlan transformToPhysicalPlan(Operator operator, int fetchSize)
       throws QueryProcessException {
     // update storage groups before parsing query plans
-    if (operator instanceof SFWOperator) {
+    if (operator instanceof QueryOperator) {
       try {
         getCMManager().syncMetaLeader();
       } catch (MetadataException e) {
diff --git a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPlanner.java b/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPlanner.java
index ecbfb74..a2469ac 100644
--- a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPlanner.java
+++ b/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterPlanner.java
@@ -19,14 +19,14 @@
 
 package org.apache.iotdb.cluster.query;
 
+import org.apache.iotdb.db.exception.metadata.IllegalPathException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
 import org.apache.iotdb.db.qp.Planner;
 import org.apache.iotdb.db.qp.logical.Operator;
-import org.apache.iotdb.db.qp.logical.crud.SFWOperator;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
-import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
-import org.apache.iotdb.db.qp.strategy.optimizer.ConcatPathOptimizer;
-import org.apache.iotdb.db.query.control.QueryResourceManager;
+import org.apache.iotdb.db.qp.strategy.LogicalChecker;
+import org.apache.iotdb.db.qp.strategy.LogicalGenerator;
+import org.apache.iotdb.service.rpc.thrift.TSRawDataQueryReq;
 
 import java.time.ZoneId;
 
@@ -36,23 +36,28 @@ public class ClusterPlanner extends Planner {
   @Override
   public PhysicalPlan parseSQLToPhysicalPlan(String sqlStr, ZoneId zoneId, int fetchSize)
       throws QueryProcessException {
-    Operator operator = logicalGenerator.generate(sqlStr, zoneId);
-    int maxDeduplicatedPathNum =
-        QueryResourceManager.getInstance().getMaxDeduplicatedPathNum(fetchSize);
-    if (operator instanceof SFWOperator && ((SFWOperator) operator).isLastQuery()) {
-      // Dataset of last query actually has only three columns, so we shouldn't limit the path num
-      // while constructing logical plan
-      // To avoid overflowing because logicalOptimize function may do maxDeduplicatedPathNum + 1, we
-      // set it to Integer.MAX_VALUE - 1
-      maxDeduplicatedPathNum = Integer.MAX_VALUE - 1;
-    }
-    operator = logicalOptimize(operator, maxDeduplicatedPathNum);
-    PhysicalGenerator physicalGenerator = new ClusterPhysicalGenerator();
-    return physicalGenerator.transformToPhysicalPlan(operator, fetchSize);
+    // from SQL to logical operator
+    Operator operator = LogicalGenerator.generate(sqlStr, zoneId);
+    // check if there are logical errors
+    LogicalChecker.check(operator);
+    // optimize the logical operator
+    operator = logicalOptimize(operator, fetchSize);
+    // from logical operator to physical plan
+    return new ClusterPhysicalGenerator().transformToPhysicalPlan(operator, fetchSize);
   }
 
   @Override
-  protected ConcatPathOptimizer getConcatPathOptimizer() {
-    return new ClusterConcatPathOptimizer();
+  public PhysicalPlan rawDataQueryReqToPhysicalPlan(
+      TSRawDataQueryReq rawDataQueryReq, ZoneId zoneId)
+      throws IllegalPathException, QueryProcessException {
+    // from TSRawDataQueryReq to logical operator
+    Operator operator = LogicalGenerator.generate(rawDataQueryReq, zoneId);
+    // check if there are logical errors
+    LogicalChecker.check(operator);
+    // optimize the logical operator
+    operator = logicalOptimize(operator, rawDataQueryReq.fetchSize);
+    // from logical operator to physical plan
+    return new ClusterPhysicalGenerator()
+        .transformToPhysicalPlan(operator, rawDataQueryReq.fetchSize);
   }
 }
diff --git a/cluster/src/test/java/org/apache/iotdb/cluster/query/ClusterPhysicalGeneratorTest.java b/cluster/src/test/java/org/apache/iotdb/cluster/query/ClusterPhysicalGeneratorTest.java
index b6dc1f6..f1d7d57 100644
--- a/cluster/src/test/java/org/apache/iotdb/cluster/query/ClusterPhysicalGeneratorTest.java
+++ b/cluster/src/test/java/org/apache/iotdb/cluster/query/ClusterPhysicalGeneratorTest.java
@@ -28,11 +28,15 @@ import org.apache.iotdb.db.qp.logical.crud.FromOperator;
 import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
 import org.apache.iotdb.db.qp.logical.crud.SelectOperator;
 import org.apache.iotdb.db.qp.physical.crud.RawDataQueryPlan;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.query.expression.unary.TimeSeriesOperand;
 
 import org.junit.Before;
 import org.junit.Test;
 
 import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
 
 import static org.junit.Assert.assertEquals;
 
@@ -53,7 +57,11 @@ public class ClusterPhysicalGeneratorTest extends BaseQueryTest {
 
     SelectOperator selectOperator =
         new SelectOperator(SQLConstant.TOK_SELECT, ZoneId.systemDefault());
-    selectOperator.setSuffixPathList(pathList);
+    List<ResultColumn> resultColumns = new ArrayList<>();
+    for (PartialPath partialPath : pathList) {
+      resultColumns.add(new ResultColumn(new TimeSeriesOperand(partialPath)));
+    }
+    selectOperator.setResultColumns(resultColumns);
     FromOperator fromOperator = new FromOperator(SQLConstant.TOK_FROM);
     fromOperator.addPrefixTablePath(new PartialPath(TestUtils.getTestSg(0)));
 
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
index 53fc126..098d40f 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
@@ -1285,9 +1285,6 @@ public class MManager {
         if (partialPath.isMeasurementAliasExists()) {
           indexMap.put(partialPath.getFullPathWithAlias(), index);
         }
-        if (partialPath.isTsAliasExists()) {
-          indexMap.put(partialPath.getTsAlias(), index);
-        }
         i++;
       }
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/PartialPath.java b/server/src/main/java/org/apache/iotdb/db/metadata/PartialPath.java
index 54e4362..d95221b 100755
--- a/server/src/main/java/org/apache/iotdb/db/metadata/PartialPath.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/PartialPath.java
@@ -43,8 +43,6 @@ public class PartialPath extends Path implements Comparable<Path> {
   protected String[] nodes;
   // alias of measurement, null pointer cannot be serialized in thrift so empty string is instead
   protected String measurementAlias = "";
-  // alias of time series used in SELECT AS
-  protected String tsAlias = "";
 
   /**
    * Construct the PartialPath using a String, will split the given String into String[] E.g., path
@@ -237,18 +235,6 @@ public class PartialPath extends Path implements Comparable<Path> {
     return measurementAlias != null && !measurementAlias.isEmpty();
   }
 
-  public String getTsAlias() {
-    return tsAlias;
-  }
-
-  public void setTsAlias(String tsAlias) {
-    this.tsAlias = tsAlias;
-  }
-
-  public boolean isTsAliasExists() {
-    return tsAlias != null && !tsAlias.isEmpty();
-  }
-
   @Override
   public String getFullPathWithAlias() {
     return getDevice() + IoTDBConstant.PATH_SEPARATOR + measurementAlias;
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/Planner.java b/server/src/main/java/org/apache/iotdb/db/qp/Planner.java
index 355b7ef..db4722a 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/Planner.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/Planner.java
@@ -23,120 +23,56 @@ import org.apache.iotdb.db.exception.query.LogicalOperatorException;
 import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
 import org.apache.iotdb.db.exception.query.PathNumOverLimitException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
-import org.apache.iotdb.db.metadata.PartialPath;
-import org.apache.iotdb.db.qp.constant.SQLConstant;
 import org.apache.iotdb.db.qp.logical.Operator;
-import org.apache.iotdb.db.qp.logical.crud.BasicFunctionOperator;
 import org.apache.iotdb.db.qp.logical.crud.FilterOperator;
-import org.apache.iotdb.db.qp.logical.crud.FromOperator;
 import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
-import org.apache.iotdb.db.qp.logical.crud.SFWOperator;
-import org.apache.iotdb.db.qp.logical.crud.SelectOperator;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.strategy.LogicalChecker;
 import org.apache.iotdb.db.qp.strategy.LogicalGenerator;
 import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
 import org.apache.iotdb.db.qp.strategy.optimizer.ConcatPathOptimizer;
 import org.apache.iotdb.db.qp.strategy.optimizer.DnfFilterOptimizer;
 import org.apache.iotdb.db.qp.strategy.optimizer.MergeSingleFilterOptimizer;
 import org.apache.iotdb.db.qp.strategy.optimizer.RemoveNotOptimizer;
-import org.apache.iotdb.db.query.control.QueryResourceManager;
 import org.apache.iotdb.db.utils.TestOnly;
 import org.apache.iotdb.service.rpc.thrift.TSRawDataQueryReq;
 
 import java.time.ZoneId;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
 
-import static org.apache.iotdb.db.conf.IoTDBConstant.TIME;
+import static org.apache.iotdb.db.qp.logical.Operator.OperatorType.QUERY;
+import static org.apache.iotdb.db.qp.logical.Operator.OperatorType.QUERY_INDEX;
 
 /** provide a integration method for other user. */
 public class Planner {
 
-  protected LogicalGenerator logicalGenerator;
-
   public Planner() {
-    this.logicalGenerator = new LogicalGenerator();
-  }
-
-  @TestOnly
-  public PhysicalPlan parseSQLToPhysicalPlan(String sqlStr) throws QueryProcessException {
-    return parseSQLToPhysicalPlan(sqlStr, ZoneId.systemDefault(), 1024);
+    // do nothing
   }
 
   /** @param fetchSize this parameter only take effect when it is a query plan */
   public PhysicalPlan parseSQLToPhysicalPlan(String sqlStr, ZoneId zoneId, int fetchSize)
       throws QueryProcessException {
-    Operator operator = logicalGenerator.generate(sqlStr, zoneId);
-    int maxDeduplicatedPathNum =
-        QueryResourceManager.getInstance().getMaxDeduplicatedPathNum(fetchSize);
-    if (operator instanceof SFWOperator && ((SFWOperator) operator).isLastQuery()) {
-      // Dataset of last query actually has only three columns, so we shouldn't limit the path num
-      // while constructing logical plan
-      // To avoid overflowing because logicalOptimize function may do maxDeduplicatedPathNum + 1, we
-      // set it to Integer.MAX_VALUE - 1
-      maxDeduplicatedPathNum = Integer.MAX_VALUE - 1;
-    }
-    operator = logicalOptimize(operator, maxDeduplicatedPathNum);
-    PhysicalGenerator physicalGenerator = new PhysicalGenerator();
-    return physicalGenerator.transformToPhysicalPlan(operator, fetchSize);
+    // from SQL to logical operator
+    Operator operator = LogicalGenerator.generate(sqlStr, zoneId);
+    // check if there are logical errors
+    LogicalChecker.check(operator);
+    // optimize the logical operator
+    operator = logicalOptimize(operator, fetchSize);
+    // from logical operator to physical plan
+    return new PhysicalGenerator().transformToPhysicalPlan(operator, fetchSize);
   }
 
-  /** convert raw data query to physical plan directly */
   public PhysicalPlan rawDataQueryReqToPhysicalPlan(
       TSRawDataQueryReq rawDataQueryReq, ZoneId zoneId)
-      throws QueryProcessException, IllegalPathException {
-    List<String> paths = rawDataQueryReq.getPaths();
-    long startTime = rawDataQueryReq.getStartTime();
-    long endTime = rawDataQueryReq.getEndTime();
-
-    // construct query operator and set its global time filter
-    QueryOperator queryOp = new QueryOperator(SQLConstant.TOK_QUERY);
-    FromOperator fromOp = new FromOperator(SQLConstant.TOK_FROM);
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
-
-    // iterate the path list and add it to from operator
-    for (String p : paths) {
-      PartialPath path = new PartialPath(p);
-      fromOp.addPrefixTablePath(path);
-    }
-    selectOp.addSelectPath(new PartialPath(""));
-
-    queryOp.setSelectOperator(selectOp);
-    queryOp.setFromOperator(fromOp);
-
-    // set time filter operator
-    FilterOperator filterOp = new FilterOperator(SQLConstant.KW_AND);
-    PartialPath timePath = new PartialPath(TIME);
-    filterOp.setSinglePath(timePath);
-    Set<PartialPath> pathSet = new HashSet<>();
-    pathSet.add(timePath);
-    filterOp.setIsSingle(true);
-    filterOp.setPathSet(pathSet);
-
-    BasicFunctionOperator left =
-        new BasicFunctionOperator(
-            SQLConstant.GREATERTHANOREQUALTO, timePath, Long.toString(startTime));
-    BasicFunctionOperator right =
-        new BasicFunctionOperator(SQLConstant.LESSTHAN, timePath, Long.toString(endTime));
-    filterOp.addChildOperator(left);
-    filterOp.addChildOperator(right);
-
-    queryOp.setFilterOperator(filterOp);
-
-    int maxDeduplicatedPathNum =
-        QueryResourceManager.getInstance().getMaxDeduplicatedPathNum(rawDataQueryReq.fetchSize);
-    if (queryOp.isLastQuery()) {
-      // Dataset of last query actually has only three columns, so we shouldn't limit the path num
-      // while constructing logical plan
-      // To avoid overflowing because logicalOptimize function may do maxDeduplicatedPathNum + 1, we
-      // set it to Integer.MAX_VALUE - 1
-      maxDeduplicatedPathNum = Integer.MAX_VALUE - 1;
-    }
-    SFWOperator op = (SFWOperator) logicalOptimize(queryOp, maxDeduplicatedPathNum);
-
-    PhysicalGenerator physicalGenerator = new PhysicalGenerator();
-    return physicalGenerator.transformToPhysicalPlan(op, rawDataQueryReq.fetchSize);
+      throws IllegalPathException, QueryProcessException {
+    // from TSRawDataQueryReq to logical operator
+    Operator operator = LogicalGenerator.generate(rawDataQueryReq, zoneId);
+    // check if there are logical errors
+    LogicalChecker.check(operator);
+    // optimize the logical operator
+    operator = logicalOptimize(operator, rawDataQueryReq.fetchSize);
+    // from logical operator to physical plan
+    return new PhysicalGenerator().transformToPhysicalPlan(operator, rawDataQueryReq.fetchSize);
   }
 
   /**
@@ -146,82 +82,38 @@ public class Planner {
    * @return optimized logical operator
    * @throws LogicalOptimizeException exception in logical optimizing
    */
-  protected Operator logicalOptimize(Operator operator, int maxDeduplicatedPathNum)
+  protected Operator logicalOptimize(Operator operator, int fetchSize)
       throws LogicalOperatorException, PathNumOverLimitException {
-    switch (operator.getType()) {
-      case AUTHOR:
-      case METADATA:
-      case SET_STORAGE_GROUP:
-      case DELETE_STORAGE_GROUP:
-      case CREATE_TIMESERIES:
-      case DELETE_TIMESERIES:
-      case ALTER_TIMESERIES:
-      case LOADDATA:
-      case INSERT:
-      case GRANT_WATERMARK_EMBEDDING:
-      case REVOKE_WATERMARK_EMBEDDING:
-      case TTL:
-      case LOAD_CONFIGURATION:
-      case SHOW:
-      case LOAD_FILES:
-      case REMOVE_FILE:
-      case MOVE_FILE:
-      case FLUSH:
-      case MERGE:
-      case TRACING:
-      case CLEAR_CACHE:
-      case NULL:
-      case SHOW_MERGE_STATUS:
-      case DELETE_PARTITION:
-      case CREATE_SCHEMA_SNAPSHOT:
-      case KILL:
-      case CREATE_FUNCTION:
-      case DROP_FUNCTION:
-      case CREATE_TRIGGER:
-      case DROP_TRIGGER:
-      case START_TRIGGER:
-      case STOP_TRIGGER:
-        return operator;
-      case QUERY:
-      case DELETE:
-      case CREATE_INDEX:
-      case DROP_INDEX:
-      case QUERY_INDEX:
-        SFWOperator root = (SFWOperator) operator;
-        return optimizeSFWOperator(root, maxDeduplicatedPathNum);
-      default:
-        throw new LogicalOperatorException(operator.getType().toString(), "");
-    }
+    return operator.getType().equals(QUERY) || operator.getType().equals(QUERY_INDEX)
+        ? optimizeQueryOperator((QueryOperator) operator, fetchSize)
+        : operator;
   }
 
   /**
-   * given an unoptimized select-from-where operator and return an optimized result.
+   * given an unoptimized query operator and return an optimized result.
    *
-   * @param root unoptimized select-from-where operator
-   * @return optimized select-from-where operator
-   * @throws LogicalOptimizeException exception in SFW optimizing
+   * @param root unoptimized query operator
+   * @return optimized query operator
+   * @throws LogicalOptimizeException exception in query optimizing
    */
-  private SFWOperator optimizeSFWOperator(SFWOperator root, int maxDeduplicatedPathNum)
+  private QueryOperator optimizeQueryOperator(QueryOperator root, int fetchSize)
       throws LogicalOperatorException, PathNumOverLimitException {
-    ConcatPathOptimizer concatPathOptimizer = getConcatPathOptimizer();
-    root = (SFWOperator) concatPathOptimizer.transform(root, maxDeduplicatedPathNum);
+    root = (QueryOperator) new ConcatPathOptimizer().transform(root, fetchSize);
+
     FilterOperator filter = root.getFilterOperator();
     if (filter == null) {
       return root;
     }
-    Set<PartialPath> pathSet = filter.getPathSet();
-    RemoveNotOptimizer removeNot = new RemoveNotOptimizer();
-    filter = removeNot.optimize(filter);
-    DnfFilterOptimizer dnf = new DnfFilterOptimizer();
-    filter = dnf.optimize(filter);
-    MergeSingleFilterOptimizer merge = new MergeSingleFilterOptimizer();
-    filter = merge.optimize(filter);
+    filter = new RemoveNotOptimizer().optimize(filter);
+    filter = new DnfFilterOptimizer().optimize(filter);
+    filter = new MergeSingleFilterOptimizer().optimize(filter);
     root.setFilterOperator(filter);
-    filter.setPathSet(pathSet);
+
     return root;
   }
 
-  protected ConcatPathOptimizer getConcatPathOptimizer() {
-    return new ConcatPathOptimizer();
+  @TestOnly
+  public PhysicalPlan parseSQLToPhysicalPlan(String sqlStr) throws QueryProcessException {
+    return parseSQLToPhysicalPlan(sqlStr, ZoneId.systemDefault(), 1024);
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
index e1fb10f..f3ac665 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
@@ -73,7 +73,6 @@ public abstract class Operator {
 
   /** If you want to add new OperatorType, you must add it in the last. */
   public enum OperatorType {
-    SFW,
     FILTER,
     GROUPBYTIME,
     SELECT,
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/DeleteDataOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/DeleteDataOperator.java
index 3cddb30..e7a0619 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/DeleteDataOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/DeleteDataOperator.java
@@ -18,10 +18,16 @@
  */
 package org.apache.iotdb.db.qp.logical.crud;
 
+import org.apache.iotdb.db.metadata.PartialPath;
 import org.apache.iotdb.db.qp.logical.Operator;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** this class extends {@code RootOperator} and process delete statement. */
-public class DeleteDataOperator extends SFWOperator {
+public class DeleteDataOperator extends Operator {
+
+  private final List<PartialPath> paths;
 
   private long startTime;
   private long endTime;
@@ -29,6 +35,15 @@ public class DeleteDataOperator extends SFWOperator {
   public DeleteDataOperator(int tokenIntType) {
     super(tokenIntType);
     operatorType = Operator.OperatorType.DELETE;
+    paths = new ArrayList<>();
+  }
+
+  public List<PartialPath> getPaths() {
+    return paths;
+  }
+
+  public void addPath(PartialPath path) {
+    paths.add(path);
   }
 
   public long getStartTime() {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/InsertOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/InsertOperator.java
index bd410c6..e8c3f42 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/InsertOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/InsertOperator.java
@@ -18,8 +18,13 @@
  */
 package org.apache.iotdb.db.qp.logical.crud;
 
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.logical.Operator;
+
 /** this class extends {@code RootOperator} and process insert statement. */
-public class InsertOperator extends SFWOperator {
+public class InsertOperator extends Operator {
+
+  private PartialPath device;
 
   private long[] times;
   private String[] measurementList;
@@ -30,6 +35,14 @@ public class InsertOperator extends SFWOperator {
     operatorType = OperatorType.INSERT;
   }
 
+  public PartialPath getDevice() {
+    return device;
+  }
+
+  public void setDevice(PartialPath device) {
+    this.device = device;
+  }
+
   public String[] getMeasurementList() {
     return measurementList;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/QueryOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/QueryOperator.java
index c640470..7e3055b 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/QueryOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/QueryOperator.java
@@ -20,13 +20,17 @@ package org.apache.iotdb.db.qp.logical.crud;
 
 import org.apache.iotdb.db.index.common.IndexType;
 import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.logical.RootOperator;
 import org.apache.iotdb.db.query.executor.fill.IFill;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 
 import java.util.Map;
 
-/** this class extends {@code RootOperator} and process getIndex statement */
-public class QueryOperator extends SFWOperator {
+public class QueryOperator extends RootOperator {
+
+  private SelectOperator selectOperator;
+  private FromOperator fromOperator;
+  private FilterOperator filterOperator;
 
   private long startTime;
   private long endTime;
@@ -73,6 +77,30 @@ public class QueryOperator extends SFWOperator {
     operatorType = Operator.OperatorType.QUERY;
   }
 
+  public SelectOperator getSelectOperator() {
+    return selectOperator;
+  }
+
+  public void setSelectOperator(SelectOperator selectOperator) {
+    this.selectOperator = selectOperator;
+  }
+
+  public FromOperator getFromOperator() {
+    return fromOperator;
+  }
+
+  public void setFromOperator(FromOperator fromOperator) {
+    this.fromOperator = fromOperator;
+  }
+
+  public FilterOperator getFilterOperator() {
+    return filterOperator;
+  }
+
+  public void setFilterOperator(FilterOperator filterOperator) {
+    this.filterOperator = filterOperator;
+  }
+
   public Map<String, Object> getProps() {
     return props;
   }
@@ -257,6 +285,18 @@ public class QueryOperator extends SFWOperator {
     this.ascending = ascending;
   }
 
+  public boolean isLastQuery() {
+    return selectOperator.isLastQuery();
+  }
+
+  public boolean hasAggregationFunction() {
+    return selectOperator.hasAggregationFunction();
+  }
+
+  public boolean hasTimeSeriesGeneratingFunction() {
+    return selectOperator.hasTimeSeriesGeneratingFunction();
+  }
+
   public boolean isWithoutAnyNull() {
     return withoutAnyNull;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SFWOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SFWOperator.java
deleted file mode 100644
index 14d510b..0000000
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SFWOperator.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.iotdb.db.qp.logical.crud;
-
-import org.apache.iotdb.db.metadata.PartialPath;
-import org.apache.iotdb.db.qp.logical.RootOperator;
-
-import java.util.List;
-
-/**
- * SFWOperator(select-from-where) includes four subclass: INSERT,DELETE,UPDATE,QUERY. All of these
- * four statements has three partition: select clause, from clause and filter clause(where clause).
- */
-public abstract class SFWOperator extends RootOperator {
-
-  private SelectOperator selectOperator;
-  private FromOperator fromOperator;
-  private FilterOperator filterOperator;
-  private boolean hasAggregation = false;
-  private boolean hasUdf = false;
-  private boolean lastQuery = false;
-
-  public SFWOperator(int tokenIntType) {
-    super(tokenIntType);
-    operatorType = OperatorType.SFW;
-  }
-
-  public FromOperator getFromOperator() {
-    return fromOperator;
-  }
-
-  public void setFromOperator(FromOperator from) {
-    this.fromOperator = from;
-  }
-
-  public SelectOperator getSelectOperator() {
-    return selectOperator;
-  }
-
-  /** set selectOperator, then init hasAggregation according to selectOperator. */
-  public void setSelectOperator(SelectOperator sel) {
-    this.selectOperator = sel;
-    if (sel.hasAggregation()) {
-      hasAggregation = true;
-    }
-    if (sel.isUdfQuery()) {
-      hasUdf = true;
-    }
-    if (sel.isLastQuery()) {
-      lastQuery = true;
-    }
-  }
-
-  public FilterOperator getFilterOperator() {
-    return filterOperator;
-  }
-
-  public void setFilterOperator(FilterOperator filter) {
-    this.filterOperator = filter;
-  }
-
-  /**
-   * get information from SelectOperator and FromOperator and generate all table paths.
-   *
-   * @return - a list of seriesPath
-   */
-  public List<PartialPath> getSelectedPaths() {
-    return selectOperator != null ? selectOperator.getSuffixPaths() : null;
-  }
-
-  public boolean hasAggregation() {
-    return hasAggregation;
-  }
-
-  public boolean hasUdf() {
-    return hasUdf;
-  }
-
-  public boolean isLastQuery() {
-    return lastQuery;
-  }
-}
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SelectOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SelectOperator.java
index 5b34ea5..4c260b9 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SelectOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/SelectOperator.java
@@ -20,7 +20,10 @@ package org.apache.iotdb.db.qp.logical.crud;
 
 import org.apache.iotdb.db.metadata.PartialPath;
 import org.apache.iotdb.db.qp.logical.Operator;
-import org.apache.iotdb.db.query.udf.core.context.UDFContext;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.query.expression.unary.FunctionExpression;
+import org.apache.iotdb.db.query.expression.unary.TimeSeriesOperand;
 
 import java.time.ZoneId;
 import java.util.ArrayList;
@@ -30,87 +33,98 @@ import java.util.List;
 public final class SelectOperator extends Operator {
 
   private final ZoneId zoneId;
-  private List<PartialPath> suffixList;
-  private List<String> aggregations;
-  private List<UDFContext> udfList;
 
-  private boolean lastQuery;
-  private boolean udfQuery;
-  private boolean hasBuiltinAggregation;
+  private boolean isLastQuery = false;
+  private boolean hasAggregationFunction = false;
+  private boolean hasTimeSeriesGeneratingFunction = false;
+
+  private List<ResultColumn> resultColumns = new ArrayList<>();
+
+  private List<PartialPath> pathsCache;
+  private List<String> aggregationFunctionsCache;
 
   /** init with tokenIntType, default operatorType is <code>OperatorType.SELECT</code>. */
   public SelectOperator(int tokenIntType, ZoneId zoneId) {
     super(tokenIntType);
-    this.zoneId = zoneId;
     operatorType = OperatorType.SELECT;
-    suffixList = new ArrayList<>();
-    aggregations = new ArrayList<>();
-    udfList = new ArrayList<>();
-    lastQuery = false;
-    udfQuery = false;
-    hasBuiltinAggregation = false;
+    this.zoneId = zoneId;
   }
 
   public ZoneId getZoneId() {
     return zoneId;
   }
 
-  public void addSelectPath(PartialPath suffixPath) {
-    suffixList.add(suffixPath);
-  }
-
-  public void addClusterPath(PartialPath suffixPath, String aggregation) {
-    suffixList.add(suffixPath);
-    aggregations.add(aggregation);
-    if (aggregation != null) {
-      hasBuiltinAggregation = true;
-    }
+  public void markAsLastQuery() {
+    isLastQuery = true;
   }
 
   public boolean isLastQuery() {
-    return this.lastQuery;
+    return isLastQuery;
   }
 
-  public void setLastQuery() {
-    lastQuery = true;
+  public boolean hasAggregationFunction() {
+    return hasAggregationFunction;
   }
 
-  public List<String> getAggregations() {
-    return this.aggregations;
+  public boolean hasTimeSeriesGeneratingFunction() {
+    return hasTimeSeriesGeneratingFunction;
   }
 
-  public void setAggregations(List<String> aggregations) {
-    this.aggregations = aggregations;
+  public void addResultColumn(ResultColumn resultColumn) {
+    resultColumns.add(resultColumn);
+    if (resultColumn.getExpression().isAggregationFunctionExpression()) {
+      hasAggregationFunction = true;
+    }
+    if (resultColumn.getExpression().isTimeSeriesGeneratingFunctionExpression()) {
+      hasTimeSeriesGeneratingFunction = true;
+    }
   }
 
-  public boolean hasAggregation() {
-    return hasBuiltinAggregation; // todo: hasBuiltinAggregation || hasUDAF
-  }
+  public void setResultColumns(List<ResultColumn> resultColumns) {
+    this.resultColumns = resultColumns;
 
-  public void setSuffixPathList(List<PartialPath> suffixPaths) {
-    suffixList = suffixPaths;
+    pathsCache = null;
+    aggregationFunctionsCache = null;
   }
 
-  public List<PartialPath> getSuffixPaths() {
-    return suffixList;
+  public List<ResultColumn> getResultColumns() {
+    return resultColumns;
   }
 
-  public void addUdf(UDFContext udf) {
-    if (udf != null) {
-      udfQuery = true;
+  public List<PartialPath> getPaths() {
+    if (pathsCache == null) {
+      pathsCache = new ArrayList<>();
+      for (ResultColumn resultColumn : resultColumns) {
+        Expression expression = resultColumn.getExpression();
+        if (expression instanceof TimeSeriesOperand) {
+          pathsCache.add(((TimeSeriesOperand) expression).getPath());
+        } else if (expression instanceof FunctionExpression
+            && expression.isAggregationFunctionExpression()) {
+          pathsCache.add(
+              ((TimeSeriesOperand) ((FunctionExpression) expression).getExpressions().get(0))
+                  .getPath());
+        } else {
+          pathsCache.add(null);
+        }
+      }
     }
-    udfList.add(udf);
-  }
-
-  public List<UDFContext> getUdfList() {
-    return udfList;
+    return pathsCache;
   }
 
-  public boolean isUdfQuery() {
-    return udfQuery;
-  }
-
-  public void setUdfList(List<UDFContext> udfList) {
-    this.udfList = udfList;
+  public List<String> getAggregationFunctions() {
+    if (!hasAggregationFunction()) {
+      return null;
+    }
+    if (aggregationFunctionsCache == null) {
+      aggregationFunctionsCache = new ArrayList<>();
+      for (ResultColumn resultColumn : resultColumns) {
+        Expression expression = resultColumn.getExpression();
+        aggregationFunctionsCache.add(
+            expression instanceof FunctionExpression
+                ? ((FunctionExpression) resultColumn.getExpression()).getFunctionName()
+                : null);
+      }
+    }
+    return aggregationFunctionsCache;
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/CreateIndexOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/CreateIndexOperator.java
index 46f5cad..8934bee 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/CreateIndexOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/CreateIndexOperator.java
@@ -19,13 +19,17 @@
 package org.apache.iotdb.db.qp.logical.sys;
 
 import org.apache.iotdb.db.index.common.IndexType;
-import org.apache.iotdb.db.qp.logical.crud.SFWOperator;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.logical.Operator;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 /** this operator is to create a certain index on some time series. */
-public class CreateIndexOperator extends SFWOperator {
+public class CreateIndexOperator extends Operator {
 
+  private final List<PartialPath> paths;
   private Map<String, String> props;
   private long time;
   private IndexType indexType;
@@ -33,6 +37,15 @@ public class CreateIndexOperator extends SFWOperator {
   public CreateIndexOperator(int tokenIntType) {
     super(tokenIntType);
     operatorType = OperatorType.CREATE_INDEX;
+    paths = new ArrayList<>();
+  }
+
+  public List<PartialPath> getPaths() {
+    return paths;
+  }
+
+  public void addPath(PartialPath path) {
+    paths.add(path);
   }
 
   public long getTime() {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/DropIndexOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/DropIndexOperator.java
index 1ea6538..e538311 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/DropIndexOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/DropIndexOperator.java
@@ -19,16 +19,30 @@
 package org.apache.iotdb.db.qp.logical.sys;
 
 import org.apache.iotdb.db.index.common.IndexType;
-import org.apache.iotdb.db.qp.logical.crud.SFWOperator;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.logical.Operator;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /** this operator is to drop a certain index on some time series. */
-public class DropIndexOperator extends SFWOperator {
+public class DropIndexOperator extends Operator {
 
+  private final List<PartialPath> paths;
   private IndexType indexType;
 
   public DropIndexOperator(int tokenIntType) {
     super(tokenIntType);
     operatorType = OperatorType.DROP_INDEX;
+    paths = new ArrayList<>();
+  }
+
+  public List<PartialPath> getPaths() {
+    return paths;
+  }
+
+  public void addPath(PartialPath path) {
+    paths.add(path);
   }
 
   public IndexType getIndexType() {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AggregationPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AggregationPlan.java
index 8a26610..ee98f49 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AggregationPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AggregationPlan.java
@@ -113,11 +113,7 @@ public class AggregationPlan extends RawDataQueryPlan {
 
   @Override
   public String getColumnForReaderFromPath(PartialPath path, int pathIndex) {
-    String columnForReader = super.getColumnForReaderFromPath(path, pathIndex);
-    if (!path.isTsAliasExists()) {
-      columnForReader = this.getAggregations().get(pathIndex) + "(" + columnForReader + ")";
-    }
-    return columnForReader;
+    return resultColumns.get(pathIndex).getResultColumnName();
   }
 
   @Override
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AlignByDevicePlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AlignByDevicePlan.java
index be6ed98..3f79a19 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AlignByDevicePlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AlignByDevicePlan.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.qp.physical.crud;
 import org.apache.iotdb.db.metadata.PartialPath;
 import org.apache.iotdb.db.qp.logical.Operator;
 import org.apache.iotdb.db.qp.logical.Operator.OperatorType;
+import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.read.expression.IExpression;
 
@@ -53,6 +54,11 @@ public class AlignByDevicePlan extends QueryPlan {
     super();
   }
 
+  @Override
+  public void deduplicate(PhysicalGenerator physicalGenerator) {
+    // do nothing
+  }
+
   public void setMeasurements(List<String> measurements) {
     this.measurements = measurements;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/LastQueryPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/LastQueryPlan.java
index 56fdd1c..4064364 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/LastQueryPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/LastQueryPlan.java
@@ -19,14 +19,23 @@
 
 package org.apache.iotdb.db.qp.physical.crud;
 
+import org.apache.iotdb.db.exception.metadata.MetadataException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
 import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.service.IoTDB;
 import org.apache.iotdb.tsfile.read.expression.IExpression;
 import org.apache.iotdb.tsfile.read.expression.impl.GlobalTimeExpression;
 import org.apache.iotdb.tsfile.read.filter.TimeFilter.TimeGt;
 import org.apache.iotdb.tsfile.read.filter.TimeFilter.TimeGtEq;
 import org.apache.iotdb.tsfile.read.filter.basic.Filter;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 public class LastQueryPlan extends RawDataQueryPlan {
 
   public LastQueryPlan() {
@@ -35,6 +44,23 @@ public class LastQueryPlan extends RawDataQueryPlan {
   }
 
   @Override
+  public void deduplicate(PhysicalGenerator physicalGenerator) throws MetadataException {
+    List<ResultColumn> deduplicatedResultColumns = new ArrayList<>();
+    Set<String> columnForReaderSet = new HashSet<>();
+    for (int i = 0; i < resultColumns.size(); i++) {
+      String column = resultColumns.get(i).getResultColumnName();
+      if (!columnForReaderSet.contains(column)) {
+        addDeduplicatedPaths(paths.get(i));
+        addDeduplicatedDataTypes(dataTypes.get(i));
+        deduplicatedResultColumns.add(resultColumns.get(i));
+        columnForReaderSet.add(column);
+      }
+    }
+    transformPaths(IoTDB.metaManager);
+    setResultColumns(deduplicatedResultColumns);
+  }
+
+  @Override
   public void setExpression(IExpression expression) throws QueryProcessException {
     if (isValidExpression(expression)) {
       super.setExpression(expression);
@@ -47,9 +73,7 @@ public class LastQueryPlan extends RawDataQueryPlan {
   private boolean isValidExpression(IExpression expression) {
     if (expression instanceof GlobalTimeExpression) {
       Filter filter = ((GlobalTimeExpression) expression).getFilter();
-      if (filter instanceof TimeGtEq || filter instanceof TimeGt) {
-        return true;
-      }
+      return filter instanceof TimeGtEq || filter instanceof TimeGt;
     }
     return false;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/QueryPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/QueryPlan.java
index a13de6f..c28d9c6 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/QueryPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/QueryPlan.java
@@ -19,10 +19,13 @@
 package org.apache.iotdb.db.qp.physical.crud;
 
 import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.exception.metadata.MetadataException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
 import org.apache.iotdb.db.metadata.PartialPath;
 import org.apache.iotdb.db.qp.logical.Operator;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
+import org.apache.iotdb.db.query.expression.ResultColumn;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 
 import java.util.HashMap;
@@ -31,6 +34,7 @@ import java.util.Map;
 
 public abstract class QueryPlan extends PhysicalPlan {
 
+  protected List<ResultColumn> resultColumns = null;
   protected List<PartialPath> paths = null;
   protected List<TSDataType> dataTypes = null;
   private boolean alignByTime = true; // for disable align sql
@@ -61,6 +65,8 @@ public abstract class QueryPlan extends PhysicalPlan {
     super(isQuery, operatorType);
   }
 
+  public abstract void deduplicate(PhysicalGenerator physicalGenerator) throws MetadataException;
+
   @Override
   public List<PartialPath> getPaths() {
     return paths;
@@ -128,17 +134,13 @@ public abstract class QueryPlan extends PhysicalPlan {
   }
 
   public String getColumnForReaderFromPath(PartialPath path, int pathIndex) {
-    String columnForReader = path.isTsAliasExists() ? path.getTsAlias() : null;
-    if (columnForReader == null) {
-      columnForReader =
-          path.isMeasurementAliasExists() ? path.getFullPathWithAlias() : path.toString();
-    }
-    return columnForReader;
+    ResultColumn resultColumn = resultColumns.get(pathIndex);
+    return resultColumn.hasAlias() ? resultColumn.getAlias() : path.getFullPath();
   }
 
   public String getColumnForDisplay(String columnForReader, int pathIndex)
       throws IllegalPathException {
-    return columnForReader;
+    return resultColumns.get(pathIndex).getResultColumnName();
   }
 
   public boolean isEnableRedirect() {
@@ -157,6 +159,14 @@ public abstract class QueryPlan extends PhysicalPlan {
     this.vectorPathToIndex = vectorPathToIndex;
   }
 
+  public List<ResultColumn> getResultColumns() {
+    return resultColumns;
+  }
+
+  public void setResultColumns(List<ResultColumn> resultColumns) {
+    this.resultColumns = resultColumns;
+  }
+
   public boolean isWithoutAnyNull() {
     return withoutAnyNull;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/RawDataQueryPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/RawDataQueryPlan.java
index 6dc4d60..74b6b25 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/RawDataQueryPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/RawDataQueryPlan.java
@@ -24,12 +24,16 @@ import org.apache.iotdb.db.metadata.MManager;
 import org.apache.iotdb.db.metadata.PartialPath;
 import org.apache.iotdb.db.metadata.VectorPartialPath;
 import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
+import org.apache.iotdb.db.service.IoTDB;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.read.common.Path;
 import org.apache.iotdb.tsfile.read.expression.IExpression;
+import org.apache.iotdb.tsfile.utils.Pair;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -54,6 +58,49 @@ public class RawDataQueryPlan extends QueryPlan {
     super(isQuery, operatorType);
   }
 
+  @Override
+  public void deduplicate(PhysicalGenerator physicalGenerator) throws MetadataException {
+    // sort paths by device, to accelerate the metadata read process
+    List<Pair<PartialPath, Integer>> indexedPaths = new ArrayList<>();
+    for (int i = 0; i < paths.size(); i++) {
+      indexedPaths.add(new Pair<>(paths.get(i), i));
+    }
+    indexedPaths.sort(Comparator.comparing(pair -> pair.left));
+
+    Map<String, Integer> pathNameToReaderIndex = new HashMap<>();
+    Set<String> columnForReaderSet = new HashSet<>();
+    Set<String> columnForDisplaySet = new HashSet<>();
+
+    for (Pair<PartialPath, Integer> indexedPath : indexedPaths) {
+      PartialPath originalPath = indexedPath.left;
+      Integer originalIndex = indexedPath.right;
+
+      String columnForReader = getColumnForReaderFromPath(originalPath, originalIndex);
+      if (!columnForReaderSet.contains(columnForReader)) {
+        addDeduplicatedPaths(originalPath);
+        addDeduplicatedDataTypes(dataTypes.get(originalIndex));
+        pathNameToReaderIndex.put(columnForReader, pathNameToReaderIndex.size());
+        if (this instanceof AggregationPlan) {
+          ((AggregationPlan) this)
+              .addDeduplicatedAggregations(getAggregations().get(originalIndex));
+        }
+        columnForReaderSet.add(columnForReader);
+      }
+
+      String columnForDisplay = getColumnForDisplay(columnForReader, originalIndex);
+      if (!columnForDisplaySet.contains(columnForDisplay)) {
+        addPathToIndex(columnForDisplay, getPathToIndex().size());
+        columnForDisplaySet.add(columnForDisplay);
+      }
+    }
+
+    if (!isRawQuery()) {
+      transformPaths(IoTDB.metaManager);
+    } else {
+      transformVectorPaths(physicalGenerator, columnForDisplaySet);
+    }
+  }
+
   public IExpression getExpression() {
     return expression;
   }
@@ -132,6 +179,26 @@ public class RawDataQueryPlan extends QueryPlan {
     }
   }
 
+  public void transformVectorPaths(
+      PhysicalGenerator physicalGenerator, Set<String> columnForDisplaySet)
+      throws MetadataException {
+    Pair<List<PartialPath>, Map<String, Integer>> pair =
+        physicalGenerator.getSeriesSchema(getDeduplicatedPaths());
+
+    List<PartialPath> vectorizedDeduplicatedPaths = pair.left;
+    List<TSDataType> vectorizedDeduplicatedDataTypes =
+        new ArrayList<>(physicalGenerator.getSeriesTypes(vectorizedDeduplicatedPaths));
+    setDeduplicatedVectorPaths(vectorizedDeduplicatedPaths);
+    setDeduplicatedVectorDataTypes(vectorizedDeduplicatedDataTypes);
+
+    Map<String, Integer> columnForDisplayToQueryDataSetIndex = pair.right;
+    Map<String, Integer> pathToIndex = new HashMap<>();
+    for (String columnForDisplay : columnForDisplaySet) {
+      pathToIndex.put(columnForDisplay, columnForDisplayToQueryDataSetIndex.get(columnForDisplay));
+    }
+    setVectorPathToIndex(pathToIndex);
+  }
+
   public List<PartialPath> getDeduplicatedVectorPaths() {
     return deduplicatedVectorPaths;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/UDFPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/UDFPlan.java
index b152a08..f1ed19f 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/UDFPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/UDFPlan.java
@@ -20,7 +20,7 @@
 package org.apache.iotdb.db.qp.physical.crud;
 
 import org.apache.iotdb.db.exception.query.QueryProcessException;
-import org.apache.iotdb.db.query.udf.core.context.UDFContext;
+import org.apache.iotdb.db.query.expression.ResultColumn;
 
 import java.util.List;
 
@@ -37,7 +37,7 @@ public interface UDFPlan {
    * Build the execution plan of the executors. This method will not create any UDF instances, nor
    * will it execute user-defined logic.
    */
-  void constructUdfExecutors(List<UDFContext> udfContexts);
+  void constructUdfExecutors(List<ResultColumn> resultColumns);
 
   /** Allocate computing resources, create UDF instances, and call UDF initialization methods. */
   void initializeUdfExecutors(long queryId, float collectorMemoryBudgetInMb)
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/UDTFPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/UDTFPlan.java
index 78eb25d..bd2cc94 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/UDTFPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/UDTFPlan.java
@@ -19,19 +19,30 @@
 
 package org.apache.iotdb.db.qp.physical.crud;
 
+import org.apache.iotdb.db.exception.metadata.MetadataException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.metadata.PartialPath;
 import org.apache.iotdb.db.qp.logical.Operator;
-import org.apache.iotdb.db.query.udf.core.context.UDFContext;
+import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.query.expression.unary.FunctionExpression;
+import org.apache.iotdb.db.query.expression.unary.TimeSeriesOperand;
 import org.apache.iotdb.db.query.udf.core.executor.UDTFExecutor;
 import org.apache.iotdb.db.query.udf.service.UDFClassLoaderManager;
 import org.apache.iotdb.db.query.udf.service.UDFRegistrationService;
+import org.apache.iotdb.db.service.IoTDB;
+import org.apache.iotdb.tsfile.utils.Pair;
 
 import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class UDTFPlan extends RawDataQueryPlan implements UDFPlan {
 
@@ -52,18 +63,62 @@ public class UDTFPlan extends RawDataQueryPlan implements UDFPlan {
   }
 
   @Override
-  public void constructUdfExecutors(List<UDFContext> udfContexts) {
-    for (int i = 0; i < udfContexts.size(); ++i) {
-      UDFContext context = udfContexts.get(i);
-      if (context == null) {
-        continue;
+  public void deduplicate(PhysicalGenerator physicalGenerator) throws MetadataException {
+    // sort paths by device, to accelerate the metadata read process
+    List<Pair<PartialPath, Integer>> indexedPaths = new ArrayList<>();
+    for (int i = 0; i < resultColumns.size(); i++) {
+      for (PartialPath path : resultColumns.get(i).collectPaths()) {
+        indexedPaths.add(new Pair<>(path, i));
+      }
+    }
+    indexedPaths.sort(Comparator.comparing(pair -> pair.left));
+
+    Map<String, Integer> pathNameToReaderIndex = new HashMap<>();
+    Set<String> columnForReaderSet = new HashSet<>();
+    Set<String> columnForDisplaySet = new HashSet<>();
+
+    for (Pair<PartialPath, Integer> indexedPath : indexedPaths) {
+      PartialPath originalPath = indexedPath.left;
+      Integer originalIndex = indexedPath.right;
+
+      boolean isUdf =
+          !(resultColumns.get(originalIndex).getExpression() instanceof TimeSeriesOperand);
+
+      String columnForReader = getColumnForReaderFromPath(originalPath, originalIndex);
+      if (!columnForReaderSet.contains(columnForReader)) {
+        addDeduplicatedPaths(originalPath);
+        addDeduplicatedDataTypes(
+            isUdf ? IoTDB.metaManager.getSeriesType(originalPath) : dataTypes.get(originalIndex));
+        pathNameToReaderIndex.put(columnForReader, pathNameToReaderIndex.size());
+        columnForReaderSet.add(columnForReader);
       }
 
-      String columnName = context.getColumnName();
-      if (!columnName2Executor.containsKey(columnName)) {
-        UDTFExecutor executor = new UDTFExecutor(context, zoneId);
-        columnName2Executor.put(columnName, executor);
+      String columnForDisplay = getColumnForDisplay(columnForReader, originalIndex);
+      if (!columnForDisplaySet.contains(columnForDisplay)) {
+        addPathToIndex(columnForDisplay, getPathToIndex().size());
+        if (isUdf) {
+          addUdfOutputColumn(columnForDisplay);
+        } else {
+          addRawQueryOutputColumn(columnForDisplay);
+        }
+        columnForDisplaySet.add(columnForDisplay);
       }
+    }
+
+    setPathNameToReaderIndex(pathNameToReaderIndex);
+  }
+
+  @Override
+  public void constructUdfExecutors(List<ResultColumn> resultColumns) {
+    for (int i = 0; i < resultColumns.size(); ++i) {
+      Expression expression = resultColumns.get(i).getExpression();
+      if (!(expression instanceof FunctionExpression)) {
+        continue;
+      }
+
+      String columnName = expression.toString();
+      columnName2Executor.computeIfAbsent(
+          columnName, k -> new UDTFExecutor((FunctionExpression) expression, zoneId));
       originalOutputColumnIndex2Executor.put(i, columnName2Executor.get(columnName));
     }
   }
@@ -132,14 +187,6 @@ public class UDTFPlan extends RawDataQueryPlan implements UDFPlan {
   }
 
   @Override
-  public String getColumnForDisplay(String columnForReader, int pathIndex) {
-    if (paths.get(pathIndex) == null) {
-      return this.getExecutorByOriginalOutputColumnIndex(pathIndex).getContext().getColumnName();
-    }
-    return columnForReader;
-  }
-
-  @Override
   public boolean isRawQuery() {
     return false;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
index 5fcab27..132fdba 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
@@ -78,8 +78,6 @@ import org.apache.iotdb.db.qp.logical.sys.StartTriggerOperator;
 import org.apache.iotdb.db.qp.logical.sys.StopTriggerOperator;
 import org.apache.iotdb.db.qp.logical.sys.TracingOperator;
 import org.apache.iotdb.db.qp.physical.crud.GroupByTimePlan;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.AggregationCallContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.AggregationElementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.AliasClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.AlignByDeviceClauseOrDisableAlignContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.AlignByDeviceClauseOrDisableAlignStatementContext;
@@ -87,11 +85,8 @@ import org.apache.iotdb.db.qp.sql.SqlBaseParser.AlterClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.AlterTimeseriesContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.AlterUserContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.AndExpressionContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.AsClauseContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.AsElementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.AttributeClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.AttributeClausesContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.BuiltInFunctionCallContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.ClearcacheContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.ConstantContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.CountDevicesContext;
@@ -115,14 +110,14 @@ import org.apache.iotdb.db.qp.sql.SqlBaseParser.DropIndexContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.DropRoleContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.DropTriggerContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.DropUserContext;
+import org.apache.iotdb.db.qp.sql.SqlBaseParser.ExpressionContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.FillClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.FillStatementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.FlushContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.FromClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.FullMergeContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.FullPathContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.FunctionAsClauseContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.FunctionAsElementContext;
+import org.apache.iotdb.db.qp.sql.SqlBaseParser.FunctionAttributeContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.GrantRoleContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.GrantRoleToUserContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.GrantUserContext;
@@ -141,8 +136,6 @@ import org.apache.iotdb.db.qp.sql.SqlBaseParser.InsertMultiValueContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.InsertStatementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.InsertValuesSpecContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.KillQueryContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.LastClauseContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.LastElementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.LimitClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.LimitStatementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.ListAllRoleOfUserContext;
@@ -172,11 +165,13 @@ import org.apache.iotdb.db.qp.sql.SqlBaseParser.PrivilegesContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.PropertyContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.PropertyValueContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.RemoveFileContext;
+import org.apache.iotdb.db.qp.sql.SqlBaseParser.ResultColumnContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.RevokeRoleContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.RevokeRoleFromUserContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.RevokeUserContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.RevokeWatermarkEmbeddingContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.RootOrIdContext;
+import org.apache.iotdb.db.qp.sql.SqlBaseParser.SelectClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.SelectStatementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.SequenceClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.SetStorageGroupContext;
@@ -204,16 +199,13 @@ import org.apache.iotdb.db.qp.sql.SqlBaseParser.StartTriggerContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.StopTriggerContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.StringLiteralContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.SuffixPathContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.TableCallContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.TableElementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.TagClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.TimeIntervalContext;
+import org.apache.iotdb.db.qp.sql.SqlBaseParser.TopClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.TracingOffContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.TracingOnContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.TriggerAttributeContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.TypeClauseContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.UdfAttributeContext;
-import org.apache.iotdb.db.qp.sql.SqlBaseParser.UdfCallContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.UnsetTTLStatementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.WhereClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.WithoutNullStatementContext;
@@ -221,7 +213,16 @@ import org.apache.iotdb.db.qp.utils.DatetimeUtils;
 import org.apache.iotdb.db.query.executor.fill.IFill;
 import org.apache.iotdb.db.query.executor.fill.LinearFill;
 import org.apache.iotdb.db.query.executor.fill.PreviousFill;
-import org.apache.iotdb.db.query.udf.core.context.UDFContext;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.query.expression.binary.AdditionExpression;
+import org.apache.iotdb.db.query.expression.binary.DivisionExpression;
+import org.apache.iotdb.db.query.expression.binary.ModuloExpression;
+import org.apache.iotdb.db.query.expression.binary.MultiplicationExpression;
+import org.apache.iotdb.db.query.expression.binary.SubtractionExpression;
+import org.apache.iotdb.db.query.expression.unary.FunctionExpression;
+import org.apache.iotdb.db.query.expression.unary.MinusExpression;
+import org.apache.iotdb.db.query.expression.unary.TimeSeriesOperand;
 import org.apache.iotdb.tsfile.common.conf.TSFileDescriptor;
 import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
 import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
@@ -256,7 +257,7 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
       "For delete statement, where clause can only contain atomic expressions like : "
           + "time > XXX, time <= XXX, or two atomic expressions connected by 'AND'";
   private ZoneId zoneId;
-  QueryOperator queryOp;
+  private QueryOperator queryOp;
 
   public void setZoneId(ZoneId zoneId) {
     this.zoneId = zoneId;
@@ -310,9 +311,7 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   @Override
   public Operator visitInsertStatement(InsertStatementContext ctx) {
     InsertOperator insertOp = new InsertOperator(SQLConstant.TOK_INSERT);
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
-    selectOp.addSelectPath(parsePrefixPath(ctx.prefixPath()));
-    insertOp.setSelectOperator(selectOp);
+    insertOp.setDevice(parsePrefixPath(ctx.prefixPath()));
     parseInsertColumnSpec(ctx.insertColumnsSpec(), insertOp);
     parseInsertValuesSpec(ctx.insertValuesSpec(), insertOp);
     return insertOp;
@@ -321,17 +320,13 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   @Override
   public Operator visitDeleteStatement(DeleteStatementContext ctx) {
     DeleteDataOperator deleteDataOp = new DeleteDataOperator(SQLConstant.TOK_DELETE);
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
     List<PrefixPathContext> prefixPaths = ctx.prefixPath();
     for (PrefixPathContext prefixPath : prefixPaths) {
-      PartialPath path = parsePrefixPath(prefixPath);
-      selectOp.addSelectPath(path);
+      deleteDataOp.addPath(parsePrefixPath(prefixPath));
     }
-    deleteDataOp.setSelectOperator(selectOp);
     if (ctx.whereClause() != null) {
       FilterOperator whereOp = (FilterOperator) visit(ctx.whereClause());
-      deleteDataOp.setFilterOperator(whereOp.getChildren().get(0));
-      Pair<Long, Long> timeInterval = parseDeleteTimeInterval(deleteDataOp);
+      Pair<Long, Long> timeInterval = parseDeleteTimeInterval(whereOp.getChildren().get(0));
       deleteDataOp.setStartTime(timeInterval.left);
       deleteDataOp.setEndTime(timeInterval.right);
     } else {
@@ -366,31 +361,21 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   @Override
   public Operator visitCreateIndex(CreateIndexContext ctx) {
     CreateIndexOperator createIndexOp = new CreateIndexOperator(SQLConstant.TOK_CREATE_INDEX);
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
     List<PrefixPathContext> prefixPaths = Collections.singletonList(ctx.prefixPath());
     for (PrefixPathContext prefixPath : prefixPaths) {
-      PartialPath path = parsePrefixPath(prefixPath);
-      selectOp.addSelectPath(path);
+      createIndexOp.addPath(parsePrefixPath(prefixPath));
     }
-    createIndexOp.setSelectOperator(selectOp);
     parseIndexWithClause(ctx.indexWithClause(), createIndexOp);
-    FilterOperator whereOp;
     if (ctx.whereClause() != null) {
-      whereOp = (FilterOperator) visit(ctx.whereClause());
-      createIndexOp.setFilterOperator(whereOp.getChildren().get(0));
-      long indexTime = parseCreateIndexFilter(createIndexOp);
+      FilterOperator whereOp = (FilterOperator) visit(ctx.whereClause());
+      long indexTime = parseCreateIndexFilter(whereOp.getChildren().get(0));
       createIndexOp.setTime(indexTime);
     }
     return createIndexOp;
   }
 
-  /**
-   * for create index command, time should only have an end time.
-   *
-   * @param operator create index plan
-   */
-  private long parseCreateIndexFilter(CreateIndexOperator operator) {
-    FilterOperator filterOperator = operator.getFilterOperator();
+  /** for create index command, time should only have an end time. */
+  private long parseCreateIndexFilter(FilterOperator filterOperator) {
     if (filterOperator.getTokenIntType() != SQLConstant.GREATERTHAN
         && filterOperator.getTokenIntType() != SQLConstant.GREATERTHANOREQUALTO) {
       throw new SQLParserException(
@@ -428,13 +413,10 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   @Override
   public Operator visitDropIndex(DropIndexContext ctx) {
     DropIndexOperator dropIndexOperator = new DropIndexOperator(SQLConstant.TOK_DROP_INDEX);
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
     List<PrefixPathContext> prefixPaths = Collections.singletonList(ctx.prefixPath());
     for (PrefixPathContext prefixPath : prefixPaths) {
-      PartialPath path = parsePrefixPath(prefixPath);
-      selectOp.addSelectPath(path);
+      dropIndexOperator.addPath(parsePrefixPath(prefixPath));
     }
-    dropIndexOperator.setSelectOperator(selectOp);
     try {
       dropIndexOperator.setIndexType(IndexType.getIndexType(ctx.indexName.getText()));
     } catch (UnsupportedIndexTypeException e) {
@@ -1007,24 +989,12 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   @Override
   public Operator visitSelectStatement(SelectStatementContext ctx) {
     queryOp = new QueryOperator(SQLConstant.TOK_QUERY);
-    SelectOperator selectOp = (SelectOperator) visit(ctx.selectElements());
-    queryOp.setSelectOperator(selectOp);
-    FromOperator fromOp = (FromOperator) visit(ctx.fromClause());
-    queryOp.setFromOperator(fromOp);
-    if (ctx.topClause() != null) {
-      Map<String, Object> props = new HashMap<>();
-      int top = Integer.parseInt(ctx.topClause().INT().getText());
-      if (top < 0) {
-        throw new SQLParserException("TOP <N>: N should be greater than 0.");
-      }
-      props.put(TOP_K, top);
-      queryOp.setProps(props);
-    }
+    queryOp.setSelectOperator((SelectOperator) visit(ctx.selectClause()));
+    queryOp.setFromOperator((FromOperator) visit(ctx.fromClause()));
     if (ctx.whereClause() != null) {
       Operator operator = visit(ctx.whereClause());
       if (operator instanceof FilterOperator) {
-        FilterOperator whereOp = (FilterOperator) operator;
-        queryOp.setFilterOperator(whereOp.getChildren().get(0));
+        queryOp.setFilterOperator(((FilterOperator) operator).getChildren().get(0));
       }
     }
     if (ctx.specialClause() != null) {
@@ -1034,91 +1004,98 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   }
 
   @Override
-  public Operator visitAggregationElement(AggregationElementContext ctx) {
+  public Operator visitSelectClause(SelectClauseContext ctx) {
     SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
 
-    for (AggregationCallContext aggregationCallContext : ctx.aggregationCall()) {
-      BuiltInFunctionCallContext builtInFunctionCallContext =
-          aggregationCallContext.builtInFunctionCall();
-      UdfCallContext udfCallContext = aggregationCallContext.udfCall();
-      if (builtInFunctionCallContext != null) {
-        selectOp.addClusterPath(
-            parseSuffixPath(builtInFunctionCallContext.suffixPath()),
-            builtInFunctionCallContext.functionName().getText());
-        selectOp.addUdf(null);
-      } else if (udfCallContext != null) {
-        selectOp.addClusterPath(null, null);
-        parseUdfCall(udfCallContext, selectOp);
-      }
+    if (ctx.topClause() != null) {
+      // TODO: parse info of top clause into selectOp
+      visitTopClause(ctx.topClause());
+    } else if (ctx.LAST() != null) {
+      selectOp.markAsLastQuery();
     }
 
-    return selectOp;
-  }
-
-  public void parseUdfCall(UdfCallContext udfCall, SelectOperator selectOp) {
-    String udfName = udfCall.udfName.getText();
-    UDFContext udf = new UDFContext(udfName);
-
-    for (SuffixPathContext suffixPathContext : udfCall.udfSuffixPaths().suffixPath()) {
-      udf.addPath(parseSuffixPath(suffixPathContext));
-    }
-    for (UdfAttributeContext udfAttributeContext : udfCall.udfAttribute()) {
-      udf.addAttribute(
-          removeStringQuote(udfAttributeContext.udfAttributeKey.getText()),
-          removeStringQuote(udfAttributeContext.udfAttributeValue.getText()));
+    for (ResultColumnContext resultColumnContext : ctx.resultColumn()) {
+      selectOp.addResultColumn(parseResultColumn(resultColumnContext));
     }
 
-    selectOp.addUdf(udf);
+    return selectOp;
   }
 
   @Override
-  public Operator visitLastElement(LastElementContext ctx) {
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
-    selectOp.setLastQuery();
-    LastClauseContext lastClauseContext = ctx.lastClause();
-    if (lastClauseContext.asClause().size() != 0) {
-      parseAsClause(lastClauseContext.asClause(), selectOp);
-    } else {
-      List<SuffixPathContext> suffixPaths = lastClauseContext.suffixPath();
-      for (SuffixPathContext suffixPath : suffixPaths) {
-        PartialPath path = parseSuffixPath(suffixPath);
-        selectOp.addSelectPath(path);
-      }
+  public Operator visitTopClause(TopClauseContext ctx) {
+    Map<String, Object> props = new HashMap<>();
+    int top = Integer.parseInt(ctx.INT().getText());
+    if (top < 0) {
+      throw new SQLParserException("TOP <N>: N should be greater than 0.");
     }
-    return selectOp;
+    props.put(TOP_K, top);
+    queryOp.setProps(props);
+    return queryOp;
   }
 
-  @Override
-  public Operator visitAsElement(AsElementContext ctx) {
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
-    parseAsClause(ctx.asClause(), selectOp);
-    return selectOp;
+  private ResultColumn parseResultColumn(ResultColumnContext resultColumnContext) {
+    return new ResultColumn(
+        parseExpression(resultColumnContext.expression()),
+        resultColumnContext.AS() == null ? null : resultColumnContext.ID().getText());
   }
 
-  @Override
-  public Operator visitFunctionAsElement(FunctionAsElementContext ctx) {
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
-    List<FunctionAsClauseContext> functionAsClauseContexts = ctx.functionAsClause();
-    for (FunctionAsClauseContext functionAsClauseContext : functionAsClauseContexts) {
-      BuiltInFunctionCallContext functionCallContext =
-          functionAsClauseContext.builtInFunctionCall();
-      PartialPath path = parseSuffixPath(functionCallContext.suffixPath());
-      if (functionAsClauseContext.ID() != null) {
-        path.setTsAlias(functionAsClauseContext.ID().toString());
-      }
-      selectOp.addClusterPath(path, functionCallContext.functionName().getText());
+  @SuppressWarnings("squid:S3776")
+  private Expression parseExpression(ExpressionContext context) {
+    // unary
+    if (context.functionName != null) {
+      return parseFunctionExpression(context);
     }
-    return selectOp;
+    if (context.suffixPath() != null) {
+      return new TimeSeriesOperand(parseSuffixPath(context.suffixPath()));
+    }
+    if (context.literal != null) {
+      return new TimeSeriesOperand(new PartialPath(new String[] {context.literal.getText()}));
+    }
+    if (context.unary != null) {
+      return context.MINUS() != null
+          ? new MinusExpression(parseExpression(context.expression(0)))
+          : parseExpression(context.expression(0));
+    }
+
+    // binary
+    Expression leftExpression = parseExpression(context.leftExpression);
+    Expression rightExpression = parseExpression(context.rightExpression);
+    if (context.STAR() != null) {
+      return new MultiplicationExpression(leftExpression, rightExpression);
+    }
+    if (context.DIV() != null) {
+      return new DivisionExpression(leftExpression, rightExpression);
+    }
+    if (context.MOD() != null) {
+      return new ModuloExpression(leftExpression, rightExpression);
+    }
+    if (context.PLUS() != null) {
+      return new AdditionExpression(leftExpression, rightExpression);
+    }
+    if (context.MINUS() != null) {
+      return new SubtractionExpression(leftExpression, rightExpression);
+    }
+
+    throw new UnsupportedOperationException();
   }
 
-  public void parseAsClause(List<AsClauseContext> asClauseContexts, SelectOperator selectOp) {
-    for (AsClauseContext asClauseContext : asClauseContexts) {
-      PartialPath path = parseSuffixPath(asClauseContext.suffixPath());
-      if (asClauseContext.ID() != null) {
-        path.setTsAlias(asClauseContext.ID().toString());
-      }
-      selectOp.addSelectPath(path);
+  private Expression parseFunctionExpression(ExpressionContext functionClause) {
+    FunctionExpression functionExpression =
+        new FunctionExpression(functionClause.functionName.getText());
+
+    // expressions
+    for (ExpressionContext expression : functionClause.expression()) {
+      functionExpression.addExpression(parseExpression(expression));
+    }
+
+    // attributes
+    for (FunctionAttributeContext functionAttribute : functionClause.functionAttribute()) {
+      functionExpression.addAttribute(
+          removeStringQuote(functionAttribute.functionAttributeKey.getText()),
+          removeStringQuote(functionAttribute.functionAttributeValue.getText()));
     }
+
+    return functionExpression;
   }
 
   @Override
@@ -1242,30 +1219,6 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   }
 
   @Override
-  public Operator visitTableElement(TableElementContext ctx) {
-    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
-
-    for (TableCallContext tableCallContext : ctx.tableCall()) {
-      SuffixPathContext suffixPathContext = tableCallContext.suffixPath();
-      UdfCallContext udfCallContext = tableCallContext.udfCall();
-      if (suffixPathContext != null) {
-        selectOp.addSelectPath(parseSuffixPath(suffixPathContext));
-        selectOp.addUdf(null);
-      } else if (udfCallContext != null) {
-        selectOp.addSelectPath(null);
-        parseUdfCall(udfCallContext, selectOp);
-      } else {
-        selectOp.addSelectPath(
-            new PartialPath(
-                new String[] {tableCallContext.SINGLE_QUOTE_STRING_LITERAL().getText()}));
-        selectOp.addUdf(null);
-      }
-    }
-
-    return selectOp;
-  }
-
-  @Override
   public Operator visitFromClause(FromClauseContext ctx) {
     FromOperator fromOp = new FromOperator(SQLConstant.TOK_FROM);
     List<PrefixPathContext> prefixFromPaths = ctx.prefixPath();
@@ -1286,10 +1239,11 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
     }
     if (ctx.LIKE() != null) {
       // whole matching case
-      if (queryOp.getSelectedPaths().size() != 1) {
+      if (queryOp.getSelectOperator().getResultColumns().size() != 1) {
         throw new SQLParserException("Index query statement allows only one select path");
       }
-      if (!path.equals(queryOp.getSelectedPaths().get(0))) {
+      if (!path.equals(
+          queryOp.getSelectOperator().getResultColumns().get(0).getExpression().toString())) {
         throw new SQLParserException(
             "In the index query statement, "
                 + "the path in select element and the index predicate should be same");
@@ -1314,9 +1268,9 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
       } else {
         props = new HashMap<>();
       }
-      List<PartialPath> suffixPaths = new ArrayList<>();
-      suffixPaths.add(path);
-      queryOp.getSelectOperator().setSuffixPathList(suffixPaths);
+      List<ResultColumn> resultColumns = new ArrayList<>();
+      resultColumns.add(new ResultColumn(new TimeSeriesOperand(path)));
+      queryOp.getSelectOperator().setResultColumns(resultColumns);
       props.put(PATTERN, compositePattern);
       props.put(THRESHOLD, thresholds);
       queryOp.setIndexType(IndexType.ELB_INDEX);
@@ -1336,7 +1290,7 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   }
 
   public void parseGroupByLevelClause(GroupByLevelClauseContext ctx, QueryOperator queryOp) {
-    if (!queryOp.hasAggregation()) {
+    if (!queryOp.hasAggregationFunction()) {
       throw new SQLParserException(GroupByTimePlan.LACK_FUNC_ERROR_MESSAGE);
     }
     queryOp.setGroupByLevel(true);
@@ -1464,7 +1418,7 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   }
 
   private void parseGroupByTimeClause(GroupByTimeClauseContext ctx, QueryOperator queryOp) {
-    if (!queryOp.hasAggregation()) {
+    if (!queryOp.hasAggregationFunction()) {
       throw new SQLParserException(GroupByTimePlan.LACK_FUNC_ERROR_MESSAGE);
     }
     queryOp.setGroupByTime(true);
@@ -1492,7 +1446,7 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   }
 
   private void parseGroupByFillClause(GroupByFillClauseContext ctx, QueryOperator queryOp) {
-    if (!queryOp.hasAggregation()) {
+    if (!queryOp.hasAggregationFunction()) {
       throw new SQLParserException(GroupByTimePlan.LACK_FUNC_ERROR_MESSAGE);
     }
     queryOp.setGroupByTime(true);
@@ -1655,13 +1609,7 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
     return privileges.toArray(new String[0]);
   }
 
-  /**
-   * for delete command, time should only have an end time.
-   *
-   * @param operator delete logical plan
-   */
-  private Pair<Long, Long> parseDeleteTimeInterval(DeleteDataOperator operator) {
-    FilterOperator filterOperator = operator.getFilterOperator();
+  private Pair<Long, Long> parseDeleteTimeInterval(FilterOperator filterOperator) {
     if (!filterOperator.isLeaf() && filterOperator.getTokenIntType() != SQLConstant.KW_AND) {
       throw new SQLParserException(DELETE_RANGE_ERROR_MSG);
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalChecker.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalChecker.java
new file mode 100644
index 0000000..f55fe7c
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalChecker.java
@@ -0,0 +1,108 @@
+/*
+ * 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.qp.strategy;
+
+import org.apache.iotdb.db.exception.query.LogicalOperatorException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
+import org.apache.iotdb.db.qp.logical.crud.SelectOperator;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.query.expression.unary.TimeSeriesOperand;
+
+public class LogicalChecker {
+
+  /**
+   * TODO: make check() an abstract method and call check() in this method or outside the
+   * LogicalChecker.
+   */
+  public static void check(Operator operator) throws LogicalOperatorException {
+    if (operator instanceof QueryOperator) {
+      checkQueryOperator((QueryOperator) operator);
+    }
+  }
+
+  private static void checkQueryOperator(QueryOperator queryOperator)
+      throws LogicalOperatorException {
+    checkSelectOperator(queryOperator);
+  }
+
+  private static void checkSelectOperator(QueryOperator queryOperator)
+      throws LogicalOperatorException {
+    checkLast(queryOperator);
+    checkAggregation(queryOperator);
+    checkAlignByDevice(queryOperator);
+  }
+
+  private static void checkLast(QueryOperator queryOperator) throws LogicalOperatorException {
+    SelectOperator selectOperator = queryOperator.getSelectOperator();
+    if (!selectOperator.isLastQuery()) {
+      return;
+    }
+
+    for (ResultColumn resultColumn : selectOperator.getResultColumns()) {
+      Expression expression = resultColumn.getExpression();
+      if (!(expression instanceof TimeSeriesOperand)) {
+        throw new LogicalOperatorException("Last queries can only be applied on raw time series.");
+      }
+    }
+  }
+
+  private static void checkAggregation(QueryOperator queryOperator)
+      throws LogicalOperatorException {
+    SelectOperator selectOperator = queryOperator.getSelectOperator();
+    if (!selectOperator.hasAggregationFunction()) {
+      return;
+    }
+
+    for (ResultColumn resultColumn : selectOperator.getResultColumns()) {
+      Expression expression = resultColumn.getExpression();
+      if (expression instanceof TimeSeriesOperand) {
+        throw new LogicalOperatorException(
+            "Common queries and aggregated queries are not allowed to appear at the same time");
+      }
+    }
+  }
+
+  private static void checkAlignByDevice(QueryOperator queryOperator)
+      throws LogicalOperatorException {
+    if (!queryOperator.isAlignByDevice()) {
+      return;
+    }
+
+    SelectOperator selectOperator = queryOperator.getSelectOperator();
+    if (selectOperator.hasTimeSeriesGeneratingFunction()) {
+      throw new LogicalOperatorException("ALIGN BY DEVICE clause is not supported in UDF queries.");
+    }
+
+    for (PartialPath path : selectOperator.getPaths()) {
+      String device = path.getDevice();
+      if (!device.isEmpty()) {
+        throw new LogicalOperatorException(
+            "The paths of the SELECT clause can only be single level. In other words, "
+                + "the paths of the SELECT clause can only be measurements or STAR, without DOT."
+                + " For more details please refer to the SQL document.");
+      }
+    }
+  }
+
+  private LogicalChecker() {}
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
index e4f5a67..51a5e72 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
@@ -18,10 +18,21 @@
  */
 package org.apache.iotdb.db.qp.strategy;
 
+import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.constant.SQLConstant;
 import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.logical.crud.BasicFunctionOperator;
+import org.apache.iotdb.db.qp.logical.crud.FilterOperator;
+import org.apache.iotdb.db.qp.logical.crud.FromOperator;
+import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
+import org.apache.iotdb.db.qp.logical.crud.SelectOperator;
 import org.apache.iotdb.db.qp.sql.IoTDBSqlVisitor;
 import org.apache.iotdb.db.qp.sql.SqlBaseLexer;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.query.expression.unary.TimeSeriesOperand;
+import org.apache.iotdb.service.rpc.thrift.TSRawDataQueryReq;
 
 import org.antlr.v4.runtime.CharStream;
 import org.antlr.v4.runtime.CharStreams;
@@ -31,13 +42,15 @@ import org.antlr.v4.runtime.misc.ParseCancellationException;
 import org.antlr.v4.runtime.tree.ParseTree;
 
 import java.time.ZoneId;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.apache.iotdb.db.conf.IoTDBConstant.TIME;
 
 /** LogicalGenerator. */
 public class LogicalGenerator {
 
-  public LogicalGenerator() {}
-
-  public Operator generate(String sql, ZoneId zoneId) throws ParseCancellationException {
+  public static Operator generate(String sql, ZoneId zoneId) throws ParseCancellationException {
     IoTDBSqlVisitor ioTDBSqlVisitor = new IoTDBSqlVisitor();
     ioTDBSqlVisitor.setZoneId(zoneId);
     CharStream charStream1 = CharStreams.fromString(sql);
@@ -67,4 +80,48 @@ public class LogicalGenerator {
     }
     return ioTDBSqlVisitor.visit(tree);
   }
+
+  public static Operator generate(TSRawDataQueryReq rawDataQueryReq, ZoneId zoneId)
+      throws IllegalPathException {
+    // construct query operator and set its global time filter
+    QueryOperator queryOp = new QueryOperator(SQLConstant.TOK_QUERY);
+    FromOperator fromOp = new FromOperator(SQLConstant.TOK_FROM);
+    SelectOperator selectOp = new SelectOperator(SQLConstant.TOK_SELECT, zoneId);
+
+    // iterate the path list and add it to from operator
+    for (String p : rawDataQueryReq.getPaths()) {
+      PartialPath path = new PartialPath(p);
+      fromOp.addPrefixTablePath(path);
+    }
+    selectOp.addResultColumn(new ResultColumn(new TimeSeriesOperand(new PartialPath(""))));
+
+    queryOp.setSelectOperator(selectOp);
+    queryOp.setFromOperator(fromOp);
+
+    // set time filter operator
+    FilterOperator filterOp = new FilterOperator(SQLConstant.KW_AND);
+    PartialPath timePath = new PartialPath(TIME);
+    filterOp.setSinglePath(timePath);
+    Set<PartialPath> pathSet = new HashSet<>();
+    pathSet.add(timePath);
+    filterOp.setIsSingle(true);
+    filterOp.setPathSet(pathSet);
+
+    BasicFunctionOperator left =
+        new BasicFunctionOperator(
+            SQLConstant.GREATERTHANOREQUALTO,
+            timePath,
+            Long.toString(rawDataQueryReq.getStartTime()));
+    BasicFunctionOperator right =
+        new BasicFunctionOperator(
+            SQLConstant.LESSTHAN, timePath, Long.toString(rawDataQueryReq.getEndTime()));
+    filterOp.addChildOperator(left);
+    filterOp.addChildOperator(right);
+
+    queryOp.setFilterOperator(filterOp);
+
+    return queryOp;
+  }
+
+  private LogicalGenerator() {}
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
index 217a3bb..5c81c6e 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
@@ -122,7 +122,10 @@ import org.apache.iotdb.db.qp.physical.sys.ShowTriggersPlan;
 import org.apache.iotdb.db.qp.physical.sys.StartTriggerPlan;
 import org.apache.iotdb.db.qp.physical.sys.StopTriggerPlan;
 import org.apache.iotdb.db.qp.physical.sys.TracingPlan;
-import org.apache.iotdb.db.query.udf.core.context.UDFContext;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.query.expression.unary.FunctionExpression;
+import org.apache.iotdb.db.query.expression.unary.TimeSeriesOperand;
 import org.apache.iotdb.db.service.IoTDB;
 import org.apache.iotdb.db.utils.SchemaUtils;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
@@ -131,7 +134,6 @@ import org.apache.iotdb.tsfile.utils.Pair;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -152,7 +154,6 @@ public class PhysicalGenerator {
   @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning
   private PhysicalPlan doTransformation(Operator operator, int fetchSize)
       throws QueryProcessException {
-    List<PartialPath> paths;
     switch (operator.getType()) {
       case AUTHOR:
         AuthorOperator author = (AuthorOperator) operator;
@@ -211,13 +212,13 @@ public class PhysicalGenerator {
       case CREATE_INDEX:
         CreateIndexOperator createIndexOp = (CreateIndexOperator) operator;
         return new CreateIndexPlan(
-            createIndexOp.getSelectedPaths(),
+            createIndexOp.getPaths(),
             createIndexOp.getProps(),
             createIndexOp.getTime(),
             createIndexOp.getIndexType());
       case DROP_INDEX:
         DropIndexOperator dropIndexOp = (DropIndexOperator) operator;
-        return new DropIndexPlan(dropIndexOp.getSelectedPaths(), dropIndexOp.getIndexType());
+        return new DropIndexPlan(dropIndexOp.getPaths(), dropIndexOp.getIndexType());
       case ALTER_TIMESERIES:
         AlterTimeSeriesOperator alterTimeSeriesOperator = (AlterTimeSeriesOperator) operator;
         return new AlterTimeSeriesPlan(
@@ -229,11 +230,9 @@ public class PhysicalGenerator {
             alterTimeSeriesOperator.getAttributesMap());
       case DELETE:
         DeleteDataOperator delete = (DeleteDataOperator) operator;
-        paths = delete.getSelectedPaths();
-        return new DeletePlan(delete.getStartTime(), delete.getEndTime(), paths);
+        return new DeletePlan(delete.getStartTime(), delete.getEndTime(), delete.getPaths());
       case INSERT:
         InsertOperator insert = (InsertOperator) operator;
-        paths = insert.getSelectedPaths();
         int measurementsNum = 0;
         for (String measurement : insert.getMeasurementList()) {
           if (measurement.startsWith("(") && measurement.endsWith(")")) {
@@ -250,7 +249,7 @@ public class PhysicalGenerator {
         }
         if (measurementsNum == insert.getValueList().length) {
           return new InsertRowPlan(
-              paths.get(0),
+              insert.getDevice(),
               insert.getTimes()[0],
               insert.getMeasurementList(),
               insert.getValueList());
@@ -259,7 +258,7 @@ public class PhysicalGenerator {
         for (int i = 0; i < insert.getTimes().length; i++) {
           insertRowsPlan.addOneInsertRowPlan(
               new InsertRowPlan(
-                  paths.get(0),
+                  insert.getDevice(),
                   insert.getTimes()[i],
                   insert.getMeasurementList(),
                   Arrays.copyOfRange(
@@ -454,21 +453,21 @@ public class PhysicalGenerator {
     }
   }
 
-  protected List<TSDataType> getSeriesTypes(List<PartialPath> paths) throws MetadataException {
+  public List<TSDataType> getSeriesTypes(List<PartialPath> paths) throws MetadataException {
     return SchemaUtils.getSeriesTypesByPaths(paths);
   }
 
-  interface Transfrom {
+  interface Transform {
     QueryPlan transform(QueryOperator queryOperator) throws QueryProcessException;
   }
 
   /** agg physical plan transform */
-  public static class AggPhysicalPlanRule implements Transfrom {
+  public static class AggPhysicalPlanRule implements Transform {
 
     @Override
     public QueryPlan transform(QueryOperator queryOperator) throws QueryProcessException {
-      QueryPlan queryPlan;
-      if (queryOperator.hasUdf()) {
+      AggregationPlan queryPlan;
+      if (queryOperator.hasTimeSeriesGeneratingFunction()) {
         throw new QueryProcessException(
             "User-defined and built-in hybrid aggregation is not supported.");
       }
@@ -479,8 +478,9 @@ public class PhysicalGenerator {
       } else {
         queryPlan = new AggregationPlan();
       }
-      ((AggregationPlan) queryPlan)
-          .setAggregations(queryOperator.getSelectOperator().getAggregations());
+
+      queryPlan.setPaths(queryOperator.getSelectOperator().getPaths());
+      queryPlan.setAggregations(queryOperator.getSelectOperator().getAggregationFunctions());
 
       if (queryOperator.isGroupByTime()) {
         GroupByTimePlan groupByTimePlan = (GroupByTimePlan) queryPlan;
@@ -505,7 +505,7 @@ public class PhysicalGenerator {
           }
         }
       } else if (queryOperator.isGroupByLevel()) {
-        ((AggregationPlan) queryPlan).setLevel(queryOperator.getLevel());
+        queryPlan.setLevel(queryOperator.getLevel());
         try {
           if (!verifyAllAggregationDataTypesEqual(queryOperator)) {
             throw new QueryProcessException("Aggregate among unmatched data types");
@@ -519,11 +519,11 @@ public class PhysicalGenerator {
   }
 
   /** fill physical plan transfrom */
-  public static class FillPhysicalPlanRule implements Transfrom {
+  public static class FillPhysicalPlanRule implements Transform {
 
     @Override
     public QueryPlan transform(QueryOperator queryOperator) throws QueryProcessException {
-      if (queryOperator.hasUdf()) {
+      if (queryOperator.hasTimeSeriesGeneratingFunction()) {
         throw new QueryProcessException("Fill functions are not supported in UDF queries.");
       }
       FillQueryPlan queryPlan = new FillQueryPlan();
@@ -542,7 +542,7 @@ public class PhysicalGenerator {
   private PhysicalPlan transformQuery(QueryOperator queryOperator) throws QueryProcessException {
     QueryPlan queryPlan;
 
-    if (queryOperator.hasAggregation()) {
+    if (queryOperator.hasAggregationFunction()) {
       queryPlan = new AggPhysicalPlanRule().transform(queryOperator);
     } else if (queryOperator.isFill()) {
       queryPlan = new FillPhysicalPlanRule().transform(queryOperator);
@@ -550,9 +550,10 @@ public class PhysicalGenerator {
       queryPlan = new LastQueryPlan();
     } else if (queryOperator.getIndexType() != null) {
       queryPlan = new QueryIndexPlan();
-    } else if (queryOperator.hasUdf()) {
+    } else if (queryOperator.hasTimeSeriesGeneratingFunction()) {
       queryPlan = new UDTFPlan(queryOperator.getSelectOperator().getZoneId());
-      ((UDTFPlan) queryPlan).constructUdfExecutors(queryOperator.getSelectOperator().getUdfList());
+      ((UDTFPlan) queryPlan)
+          .constructUdfExecutors(queryOperator.getSelectOperator().getResultColumns());
     } else {
       queryPlan = new RawDataQueryPlan();
     }
@@ -560,7 +561,7 @@ public class PhysicalGenerator {
     if (queryOperator.isAlignByDevice()) {
       queryPlan = getAlignQueryPlan(queryOperator, queryPlan);
     } else {
-      queryPlan.setPaths(queryOperator.getSelectedPaths());
+      queryPlan.setPaths(queryOperator.getSelectOperator().getPaths());
       // Last query result set will not be affected by alignment
       if (queryPlan instanceof LastQueryPlan && !queryOperator.isAlignByTime()) {
         throw new QueryProcessException("Disable align cannot be applied to LAST query.");
@@ -597,8 +598,15 @@ public class PhysicalGenerator {
       }
       return queryPlan;
     }
+
+    queryPlan.setResultColumns(queryOperator.getSelectOperator().getResultColumns());
+
     try {
-      deduplicate(queryPlan);
+      List<PartialPath> paths = queryPlan.getPaths();
+      List<TSDataType> dataTypes = getSeriesTypes(paths);
+      queryPlan.setDataTypes(dataTypes);
+
+      queryPlan.deduplicate(this);
     } catch (MetadataException e) {
       throw new QueryProcessException(e);
     }
@@ -629,8 +637,8 @@ public class PhysicalGenerator {
     List<PartialPath> prefixPaths = queryOperator.getFromOperator().getPrefixPaths();
     // remove stars in fromPaths and get deviceId with deduplication
     List<PartialPath> devices = this.removeStarsInDeviceWithUnique(prefixPaths);
-    List<PartialPath> suffixPaths = queryOperator.getSelectOperator().getSuffixPaths();
-    List<String> originAggregations = queryOperator.getSelectOperator().getAggregations();
+    List<ResultColumn> resultColumns = queryOperator.getSelectOperator().getResultColumns();
+    List<String> originAggregations = queryOperator.getSelectOperator().getAggregationFunctions();
 
     // to record result measurement columns
     List<String> measurements = new ArrayList<>();
@@ -644,8 +652,13 @@ public class PhysicalGenerator {
     Map<String, TSDataType> measurementDataTypeMap = new HashMap<>();
     List<PartialPath> paths = new ArrayList<>();
 
-    for (int i = 0; i < suffixPaths.size(); i++) { // per suffix in SELECT
-      PartialPath suffixPath = suffixPaths.get(i);
+    for (int i = 0; i < resultColumns.size(); i++) { // per suffix in SELECT
+      ResultColumn resultColumn = resultColumns.get(i);
+      Expression suffixExpression = resultColumn.getExpression();
+      PartialPath suffixPath =
+          suffixExpression instanceof TimeSeriesOperand
+              ? ((TimeSeriesOperand) suffixExpression).getPath()
+              : (((FunctionExpression) suffixExpression).getPaths().get(0));
 
       // to record measurements in the loop of a suffix path
       Set<String> measurementSetOfGivenSuffix = new LinkedHashSet<>();
@@ -658,24 +671,23 @@ public class PhysicalGenerator {
       }
 
       for (PartialPath device : devices) { // per device in FROM after deduplication
-
         PartialPath fullPath = device.concatPath(suffixPath);
         try {
           // remove stars in SELECT to get actual paths
           List<PartialPath> actualPaths = getMatchedTimeseries(fullPath);
-          if (suffixPath.isTsAliasExists()) {
+          if (resultColumn.hasAlias()) {
             if (actualPaths.size() == 1) {
               String columnName = actualPaths.get(0).getMeasurement();
               if (originAggregations != null && !originAggregations.isEmpty()) {
                 measurementAliasMap.put(
-                    originAggregations.get(i) + "(" + columnName + ")", suffixPath.getTsAlias());
+                    originAggregations.get(i) + "(" + columnName + ")", resultColumn.getAlias());
               } else {
-                measurementAliasMap.put(columnName, suffixPath.getTsAlias());
+                measurementAliasMap.put(columnName, resultColumn.getAlias());
               }
             } else if (actualPaths.size() >= 2) {
               throw new QueryProcessException(
                   "alias '"
-                      + suffixPath.getTsAlias()
+                      + resultColumn.getAlias()
                       + "' can only be matched with one time series");
             }
           }
@@ -852,116 +864,7 @@ public class PhysicalGenerator {
     basicOperator.setSinglePath(concatPath);
   }
 
-  @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning
-  private void deduplicate(QueryPlan queryPlan) throws MetadataException {
-    // generate dataType first
-    List<PartialPath> paths = queryPlan.getPaths();
-    List<TSDataType> dataTypes = getSeriesTypes(paths);
-    queryPlan.setDataTypes(dataTypes);
-
-    // deduplicate from here
-    if (queryPlan instanceof AlignByDevicePlan) {
-      return;
-    }
-
-    RawDataQueryPlan rawDataQueryPlan = (RawDataQueryPlan) queryPlan;
-    Set<String> columnForReaderSet = new HashSet<>();
-    // if it's a last query, no need to sort by device
-    if (queryPlan instanceof LastQueryPlan) {
-      for (int i = 0; i < paths.size(); i++) {
-        PartialPath path = paths.get(i);
-        String column = queryPlan.getColumnForReaderFromPath(path, i);
-        if (!columnForReaderSet.contains(column)) {
-          TSDataType seriesType = dataTypes.get(i);
-          rawDataQueryPlan.addDeduplicatedPaths(path);
-          rawDataQueryPlan.addDeduplicatedDataTypes(seriesType);
-          columnForReaderSet.add(column);
-        }
-      }
-      ((LastQueryPlan) queryPlan).transformPaths(IoTDB.metaManager);
-      return;
-    }
-
-    // sort path by device
-    List<Pair<PartialPath, Integer>> indexedPaths = new ArrayList<>();
-    for (int i = 0; i < paths.size(); i++) {
-      PartialPath path = paths.get(i);
-      if (path != null) { // non-udf
-        indexedPaths.add(new Pair<>(paths.get(i), i));
-      } else { // udf
-        UDFContext context =
-            ((UDTFPlan) queryPlan).getExecutorByOriginalOutputColumnIndex(i).getContext();
-        for (PartialPath udfPath : context.getPaths()) {
-          indexedPaths.add(new Pair<>(udfPath, i));
-        }
-      }
-    }
-    indexedPaths.sort(Comparator.comparing(pair -> pair.left));
-
-    Map<String, Integer> pathNameToReaderIndex = new HashMap<>();
-    Set<String> columnForDisplaySet = new HashSet<>();
-    for (Pair<PartialPath, Integer> indexedPath : indexedPaths) {
-      PartialPath originalPath = indexedPath.left;
-      Integer originalIndex = indexedPath.right;
-
-      String columnForReader = queryPlan.getColumnForReaderFromPath(originalPath, originalIndex);
-      boolean isUdf = queryPlan instanceof UDTFPlan && paths.get(originalIndex) == null;
-
-      if (!columnForReaderSet.contains(columnForReader)) {
-        rawDataQueryPlan.addDeduplicatedPaths(originalPath);
-        rawDataQueryPlan.addDeduplicatedDataTypes(
-            isUdf ? IoTDB.metaManager.getSeriesType(originalPath) : dataTypes.get(originalIndex));
-        pathNameToReaderIndex.put(columnForReader, pathNameToReaderIndex.size());
-        if (queryPlan instanceof AggregationPlan) {
-          ((AggregationPlan) queryPlan)
-              .addDeduplicatedAggregations(queryPlan.getAggregations().get(originalIndex));
-        }
-        columnForReaderSet.add(columnForReader);
-      }
-
-      String columnForDisplay = queryPlan.getColumnForDisplay(columnForReader, originalIndex);
-
-      if (!columnForDisplaySet.contains(columnForDisplay)) {
-        queryPlan.addPathToIndex(columnForDisplay, queryPlan.getPathToIndex().size());
-        if (queryPlan instanceof UDTFPlan) {
-          if (isUdf) {
-            ((UDTFPlan) queryPlan).addUdfOutputColumn(columnForDisplay);
-          } else {
-            ((UDTFPlan) queryPlan).addRawQueryOutputColumn(columnForDisplay);
-          }
-        }
-        columnForDisplaySet.add(columnForDisplay);
-      }
-    }
-    if (queryPlan instanceof UDTFPlan) {
-      ((UDTFPlan) queryPlan).setPathNameToReaderIndex(pathNameToReaderIndex);
-      return;
-    }
-
-    if (!rawDataQueryPlan.isRawQuery()) {
-      rawDataQueryPlan.transformPaths(IoTDB.metaManager);
-    } else {
-      // support vector
-      List<PartialPath> deduplicatedPaths = rawDataQueryPlan.getDeduplicatedPaths();
-      Pair<List<PartialPath>, Map<String, Integer>> pair = getSeriesSchema(deduplicatedPaths);
-
-      List<PartialPath> vectorizedDeduplicatedPaths = pair.left;
-      List<TSDataType> vectorizedDeduplicatedDataTypes =
-          new ArrayList<>(getSeriesTypes(vectorizedDeduplicatedPaths));
-      rawDataQueryPlan.setDeduplicatedVectorPaths(vectorizedDeduplicatedPaths);
-      rawDataQueryPlan.setDeduplicatedVectorDataTypes(vectorizedDeduplicatedDataTypes);
-
-      Map<String, Integer> columnForDisplayToQueryDataSetIndex = pair.right;
-      Map<String, Integer> pathToIndex = new HashMap<>();
-      for (String columnForDisplay : columnForDisplaySet) {
-        pathToIndex.put(
-            columnForDisplay, columnForDisplayToQueryDataSetIndex.get(columnForDisplay));
-      }
-      queryPlan.setVectorPathToIndex(pathToIndex);
-    }
-  }
-
-  protected Pair<List<PartialPath>, Map<String, Integer>> getSeriesSchema(List<PartialPath> paths)
+  public Pair<List<PartialPath>, Map<String, Integer>> getSeriesSchema(List<PartialPath> paths)
       throws MetadataException {
     return IoTDB.metaManager.getSeriesSchemas(paths);
   }
@@ -987,12 +890,12 @@ public class PhysicalGenerator {
 
   private static boolean verifyAllAggregationDataTypesEqual(QueryOperator queryOperator)
       throws MetadataException {
-    List<String> aggregations = queryOperator.getSelectOperator().getAggregations();
+    List<String> aggregations = queryOperator.getSelectOperator().getAggregationFunctions();
     if (aggregations.isEmpty()) {
       return true;
     }
 
-    List<PartialPath> paths = queryOperator.getSelectedPaths();
+    List<PartialPath> paths = queryOperator.getSelectOperator().getPaths();
     List<TSDataType> dataTypes = SchemaUtils.getSeriesTypesByPaths(paths);
     String aggType = aggregations.get(0);
     switch (aggType) {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ConcatPathOptimizer.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ConcatPathOptimizer.java
index 0991328..69c6d94 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ConcatPathOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ConcatPathOptimizer.java
@@ -30,11 +30,10 @@ import org.apache.iotdb.db.qp.logical.crud.FilterOperator;
 import org.apache.iotdb.db.qp.logical.crud.FromOperator;
 import org.apache.iotdb.db.qp.logical.crud.FunctionOperator;
 import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
-import org.apache.iotdb.db.qp.logical.crud.SFWOperator;
 import org.apache.iotdb.db.qp.logical.crud.SelectOperator;
-import org.apache.iotdb.db.query.udf.core.context.UDFContext;
+import org.apache.iotdb.db.qp.utils.WildcardsRemover;
+import org.apache.iotdb.db.query.expression.ResultColumn;
 import org.apache.iotdb.db.service.IoTDB;
-import org.apache.iotdb.tsfile.utils.Pair;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,211 +46,94 @@ import java.util.Set;
 /** concat paths in select and from clause. */
 public class ConcatPathOptimizer implements ILogicalOptimizer {
 
-  private static final Logger logger = LoggerFactory.getLogger(ConcatPathOptimizer.class);
+  private static final Logger LOGGER = LoggerFactory.getLogger(ConcatPathOptimizer.class);
+
   private static final String WARNING_NO_SUFFIX_PATHS =
-      "given SFWOperator doesn't have suffix paths, cannot concat seriesPath";
+      "failed to concat series paths because the given query operator didn't have suffix paths";
   private static final String WARNING_NO_PREFIX_PATHS =
-      "given SFWOperator doesn't have prefix paths, cannot concat seriesPath";
+      "failed to concat series paths because the given query operator didn't have prefix paths";
 
-  @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning
   @Override
-  public Operator transform(Operator operator, int maxDeduplicatedPathNum)
+  public Operator transform(Operator operator, int fetchSize)
       throws LogicalOptimizeException, PathNumOverLimitException {
-    if (!(operator instanceof SFWOperator)) {
-      logger.warn("given operator isn't SFWOperator, cannot concat seriesPath");
-      return operator;
-    }
-    SFWOperator sfwOperator = (SFWOperator) operator;
-    FromOperator from = sfwOperator.getFromOperator();
-    List<PartialPath> prefixPaths;
-    if (from == null) {
-      logger.warn(WARNING_NO_PREFIX_PATHS);
-      return operator;
-    } else {
-      prefixPaths = from.getPrefixPaths();
-      if (prefixPaths.isEmpty()) {
-        logger.warn(WARNING_NO_PREFIX_PATHS);
-        return operator;
-      }
-    }
-    SelectOperator select = sfwOperator.getSelectOperator();
-    List<PartialPath> initialSuffixPaths;
-    if (select == null) {
-      logger.warn(WARNING_NO_SUFFIX_PATHS);
-      return operator;
-    } else {
-      initialSuffixPaths = select.getSuffixPaths();
-      if (initialSuffixPaths.isEmpty()) {
-        logger.warn(WARNING_NO_SUFFIX_PATHS);
-        return operator;
-      }
-    }
-
-    checkAggrOfSelectOperator(select);
+    QueryOperator queryOperator = (QueryOperator) operator;
+    if (!optimizable(queryOperator)) {
+      return queryOperator;
+    }
+    concatSelect(queryOperator);
+    removeWildcardsInSelectPaths(queryOperator, fetchSize);
+    concatFilterAndRemoveWildcards(queryOperator);
+    return queryOperator;
+  }
 
-    boolean isAlignByDevice = false;
-    if (operator instanceof QueryOperator) {
-      if (!((QueryOperator) operator).isAlignByDevice()
-          || ((QueryOperator) operator).isLastQuery()) {
-        // concat paths and remove stars
-        int seriesLimit = ((QueryOperator) operator).getSeriesLimit();
-        int seriesOffset = ((QueryOperator) operator).getSeriesOffset();
-        concatSelect(
-            prefixPaths,
-            select,
-            seriesLimit,
-            seriesOffset,
-            maxDeduplicatedPathNum,
-            ((QueryOperator) operator).getIndexType() == null);
-      } else {
-        isAlignByDevice = true;
-        if (((QueryOperator) operator).hasUdf()) {
-          throw new LogicalOptimizeException(
-              "ALIGN BY DEVICE clause is not supported in UDF queries.");
-        }
-        for (PartialPath path : initialSuffixPaths) {
-          String device = path.getDevice();
-          if (!device.isEmpty()) {
-            throw new LogicalOptimizeException(
-                "The paths of the SELECT clause can only be single level. In other words, "
-                    + "the paths of the SELECT clause can only be measurements or STAR, without DOT."
-                    + " For more details please refer to the SQL document.");
-          }
-        }
-        // ALIGN_BY_DEVICE leaves the 1) concat, 2) remove star, 3) slimit tasks to the next phase,
-        // i.e., PhysicalGenerator.transformQuery
-      }
+  private boolean optimizable(QueryOperator queryOperator) {
+    if (queryOperator.isAlignByDevice()) {
+      return false;
     }
 
-    // concat filter
-    FilterOperator filter = sfwOperator.getFilterOperator();
-    Set<PartialPath> filterPaths = new HashSet<>();
-    if (filter == null) {
-      return operator;
-    }
-    if (!isAlignByDevice) {
-      sfwOperator.setFilterOperator(concatFilter(prefixPaths, filter, filterPaths));
+    SelectOperator select = queryOperator.getSelectOperator();
+    if (select == null || select.getResultColumns().isEmpty()) {
+      LOGGER.warn(WARNING_NO_SUFFIX_PATHS);
+      return false;
     }
-    sfwOperator.getFilterOperator().setPathSet(filterPaths);
-    // GROUP_BY_DEVICE leaves the concatFilter to PhysicalGenerator to optimize filter without
-    // prefix first
 
-    return sfwOperator;
-  }
-
-  private List<PartialPath> judgeSelectOperator(SelectOperator selectOperator)
-      throws LogicalOptimizeException {
-    List<PartialPath> suffixPaths;
-    if (selectOperator == null) {
-      throw new LogicalOptimizeException(WARNING_NO_SUFFIX_PATHS);
-    } else {
-      suffixPaths = selectOperator.getSuffixPaths();
-      if (suffixPaths.isEmpty()) {
-        throw new LogicalOptimizeException(WARNING_NO_SUFFIX_PATHS);
-      }
+    FromOperator from = queryOperator.getFromOperator();
+    if (from == null || from.getPrefixPaths().isEmpty()) {
+      LOGGER.warn(WARNING_NO_PREFIX_PATHS);
+      return false;
     }
-    return suffixPaths;
-  }
 
-  private void checkAggrOfSelectOperator(SelectOperator selectOperator)
-      throws LogicalOptimizeException {
-    if (!selectOperator.getAggregations().isEmpty()
-        && selectOperator.getSuffixPaths().size() != selectOperator.getAggregations().size()) {
-      throw new LogicalOptimizeException(
-          "Common queries and aggregated queries are not allowed to appear at the same time");
-    }
+    return true;
   }
 
-  private void extendListSafely(List<String> source, int index, List<String> target) {
-    if (source != null && !source.isEmpty()) {
-      target.add(source.get(index));
+  private void concatSelect(QueryOperator queryOperator) throws LogicalOptimizeException {
+    List<PartialPath> prefixPaths = queryOperator.getFromOperator().getPrefixPaths();
+    List<ResultColumn> resultColumns = new ArrayList<>();
+    for (ResultColumn suffixColumn : queryOperator.getSelectOperator().getResultColumns()) {
+      suffixColumn.concat(prefixPaths, resultColumns);
     }
+    queryOperator.getSelectOperator().setResultColumns(resultColumns);
   }
 
-  /**
-   * Extract paths from select&from cql, expand them into complete versions, and reassign them to
-   * selectOperator's suffixPathList. Treat aggregations similarly.
-   */
-  @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning
-  private void concatSelect(
-      List<PartialPath> fromPaths,
-      SelectOperator selectOperator,
-      int limit,
-      int offset,
-      int maxDeduplicatedPathNum,
-      boolean needRemoveStar)
+  private void removeWildcardsInSelectPaths(QueryOperator queryOperator, int fetchSize)
       throws LogicalOptimizeException, PathNumOverLimitException {
-    List<PartialPath> suffixPaths = judgeSelectOperator(selectOperator);
-    List<PartialPath> afterConcatPaths = new ArrayList<>(); // null elements are for the UDFs
-
-    List<String> originAggregations = selectOperator.getAggregations();
-    List<String> afterConcatAggregations = new ArrayList<>(); // null elements are for the UDFs
-
-    List<UDFContext> originUdfList = selectOperator.getUdfList();
-    List<UDFContext> afterConcatUdfList = new ArrayList<>();
-
-    for (int i = 0; i < suffixPaths.size(); i++) {
-      // selectPath cannot start with ROOT, which is guaranteed by TSParser
-      PartialPath selectPath = suffixPaths.get(i);
-
-      if (selectPath == null) { // udf
-        UDFContext originUdf = originUdfList.get(i);
-        List<PartialPath> originUdfSuffixPaths = originUdf.getPaths();
-
-        List<List<PartialPath>> afterConcatUdfPathsList = new ArrayList<>();
-        for (PartialPath originUdfSuffixPath : originUdfSuffixPaths) {
-          List<PartialPath> afterConcatUdfPaths = new ArrayList<>();
-          for (PartialPath fromPath : fromPaths) {
-            afterConcatUdfPaths.add(fromPath.concatPath(originUdfSuffixPath));
-          }
-          afterConcatUdfPathsList.add(afterConcatUdfPaths);
-        }
-        List<List<PartialPath>> extendedAfterConcatUdfPathsList = new ArrayList<>();
-        cartesianProduct(
-            afterConcatUdfPathsList, extendedAfterConcatUdfPathsList, 0, new ArrayList<>());
-
-        for (List<PartialPath> afterConcatUdfPaths : extendedAfterConcatUdfPathsList) {
-          afterConcatPaths.add(null);
-          extendListSafely(originAggregations, i, afterConcatAggregations);
-
-          afterConcatUdfList.add(
-              new UDFContext(originUdf.getName(), originUdf.getAttributes(), afterConcatUdfPaths));
-        }
-      } else { // non-udf
-        for (PartialPath fromPath : fromPaths) {
-          PartialPath fullPath = fromPath.concatPath(selectPath);
-          if (selectPath.isTsAliasExists()) {
-            fullPath.setTsAlias(selectPath.getTsAlias());
-          }
-          afterConcatPaths.add(fullPath);
-          extendListSafely(originAggregations, i, afterConcatAggregations);
+    if (queryOperator.getIndexType() != null) {
+      return;
+    }
 
-          afterConcatUdfList.add(null);
-        }
+    WildcardsRemover wildcardsRemover = new WildcardsRemover(queryOperator, fetchSize);
+    List<ResultColumn> resultColumns = new ArrayList<>();
+    for (ResultColumn resultColumn : queryOperator.getSelectOperator().getResultColumns()) {
+      resultColumn.removeWildcards(wildcardsRemover, resultColumns);
+      if (wildcardsRemover.checkIfPathNumberIsOverLimit(resultColumns)) {
+        break;
       }
     }
+    wildcardsRemover.checkIfSoffsetIsExceeded(resultColumns);
+    queryOperator.getSelectOperator().setResultColumns(resultColumns);
+  }
 
-    if (needRemoveStar) {
-      removeStarsInPath(
-          afterConcatPaths,
-          afterConcatAggregations,
-          afterConcatUdfList,
-          selectOperator,
-          limit,
-          offset,
-          maxDeduplicatedPathNum);
-    } else {
-      selectOperator.setSuffixPathList(afterConcatPaths);
+  private void concatFilterAndRemoveWildcards(QueryOperator queryOperator)
+      throws LogicalOptimizeException {
+    FilterOperator filter = queryOperator.getFilterOperator();
+    if (filter == null) {
+      return;
     }
+
+    Set<PartialPath> filterPaths = new HashSet<>();
+    queryOperator.setFilterOperator(
+        concatFilterAndRemoveWildcards(
+            queryOperator.getFromOperator().getPrefixPaths(), filter, filterPaths));
+    queryOperator.getFilterOperator().setPathSet(filterPaths);
   }
 
-  private FilterOperator concatFilter(
+  private FilterOperator concatFilterAndRemoveWildcards(
       List<PartialPath> fromPaths, FilterOperator operator, Set<PartialPath> filterPaths)
       throws LogicalOptimizeException {
     if (!operator.isLeaf()) {
       List<FilterOperator> newFilterList = new ArrayList<>();
       for (FilterOperator child : operator.getChildren()) {
-        newFilterList.add(concatFilter(fromPaths, child, filterPaths));
+        newFilterList.add(concatFilterAndRemoveWildcards(fromPaths, child, filterPaths));
       }
       operator.setChildren(newFilterList);
       return operator;
@@ -266,7 +148,7 @@ public class ConcatPathOptimizer implements ILogicalOptimizer {
     }
     List<PartialPath> concatPaths = new ArrayList<>();
     fromPaths.forEach(fromPath -> concatPaths.add(fromPath.concatPath(filterPath)));
-    List<PartialPath> noStarPaths = removeStarsInPathWithUnique(concatPaths);
+    List<PartialPath> noStarPaths = removeWildcardsInConcatPaths(concatPaths);
     filterPaths.addAll(noStarPaths);
     if (noStarPaths.size() == 1) {
       // Transform "select s1 from root.car.* where s1 > 10" to
@@ -306,173 +188,33 @@ public class ConcatPathOptimizer implements ILogicalOptimizer {
     return filterBinaryTree;
   }
 
-  /**
-   * replace "*" by actual paths.
-   *
-   * @param paths list of paths which may contain stars
-   * @return a unique seriesPath list
-   */
-  private List<PartialPath> removeStarsInPathWithUnique(List<PartialPath> paths)
+  private List<PartialPath> removeWildcardsInConcatPaths(List<PartialPath> originalPaths)
       throws LogicalOptimizeException {
-    List<PartialPath> retPaths = new ArrayList<>();
-    HashSet<PartialPath> pathSet = new HashSet<>();
+    HashSet<PartialPath> actualPaths = new HashSet<>();
     try {
-      for (PartialPath path : paths) {
-        List<PartialPath> all = removeWildcard(path, 0, 0).left;
-        if (all.size() == 0) {
+      for (PartialPath originalPath : originalPaths) {
+        List<PartialPath> all =
+            IoTDB.metaManager.getAllTimeseriesPathWithAlias(originalPath, 0, 0).left;
+        if (all.isEmpty()) {
           throw new LogicalOptimizeException(
-              String.format("Unknown time series %s in `where clause`", path));
-        }
-        for (PartialPath subPath : all) {
-          if (!pathSet.contains(subPath)) {
-            pathSet.add(subPath);
-            retPaths.add(subPath);
-          }
+              String.format("Unknown time series %s in `where clause`", originalPath));
         }
+        actualPaths.addAll(all);
       }
     } catch (MetadataException e) {
       throw new LogicalOptimizeException("error when remove star: " + e.getMessage());
     }
-    return retPaths;
-  }
-
-  @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning
-  private void removeStarsInPath(
-      List<PartialPath> afterConcatPaths,
-      List<String> afterConcatAggregations,
-      List<UDFContext> afterConcatUdfList,
-      SelectOperator selectOperator,
-      int finalLimit,
-      int finalOffset,
-      int maxDeduplicatedPathNum)
-      throws LogicalOptimizeException, PathNumOverLimitException {
-    int offset = finalOffset;
-    int limit =
-        finalLimit == 0 || maxDeduplicatedPathNum < finalLimit
-            ? maxDeduplicatedPathNum + 1
-            : finalLimit;
-    int consumed = 0;
-
-    List<PartialPath> newSuffixPathList = new ArrayList<>();
-    List<String> newAggregations = new ArrayList<>();
-    List<UDFContext> newUdfList = new ArrayList<>();
-
-    for (int i = 0; i < afterConcatPaths.size(); i++) {
-      try {
-        PartialPath afterConcatPath = afterConcatPaths.get(i);
-
-        if (afterConcatPath == null) { // udf
-          UDFContext originUdf = afterConcatUdfList.get(i);
-          List<PartialPath> originPaths = originUdf.getPaths();
-          List<List<PartialPath>> extendedPaths = new ArrayList<>();
-
-          boolean atLeastOneSeriesNotExisted = false;
-          for (PartialPath originPath : originPaths) {
-            List<PartialPath> actualPaths = removeWildcard(originPath, 0, 0).left;
-            if (actualPaths.isEmpty()) {
-              atLeastOneSeriesNotExisted = true;
-              break;
-            }
-            checkAndSetTsAlias(actualPaths, originPath);
-            extendedPaths.add(actualPaths);
-          }
-          if (atLeastOneSeriesNotExisted) {
-            continue;
-          }
-
-          List<List<PartialPath>> actualPaths = new ArrayList<>();
-          cartesianProduct(extendedPaths, actualPaths, 0, new ArrayList<>());
-
-          for (List<PartialPath> actualPath : actualPaths) {
-            if (offset != 0) {
-              --offset;
-              continue;
-            } else if (limit != 0) {
-              --limit;
-            } else {
-              break;
-            }
-
-            newSuffixPathList.add(null);
-            extendListSafely(afterConcatAggregations, i, newAggregations);
-
-            newUdfList.add(
-                new UDFContext(originUdf.getName(), originUdf.getAttributes(), actualPath));
-          }
-        } else { // non-udf
-          Pair<List<PartialPath>, Integer> pair = removeWildcard(afterConcatPath, limit, offset);
-          List<PartialPath> actualPaths = pair.left;
-          checkAndSetTsAlias(actualPaths, afterConcatPath);
-
-          for (PartialPath actualPath : actualPaths) {
-            newSuffixPathList.add(actualPath);
-            extendListSafely(afterConcatAggregations, i, newAggregations);
-
-            newUdfList.add(null);
-          }
-
-          consumed += pair.right;
-          if (offset != 0) {
-            int delta = offset - pair.right;
-            offset = Math.max(delta, 0);
-            if (delta < 0) {
-              limit += delta;
-            }
-          } else {
-            limit -= pair.right;
-          }
-        }
-
-        if (limit == 0) {
-          if (maxDeduplicatedPathNum < newSuffixPathList.size()) {
-            throw new PathNumOverLimitException(maxDeduplicatedPathNum);
-          }
-          break;
-        }
-      } catch (MetadataException e) {
-        throw new LogicalOptimizeException("error when remove star: " + e.getMessage());
-      }
-    }
-
-    if (consumed == 0 ? finalOffset != 0 : newSuffixPathList.isEmpty()) {
-      throw new LogicalOptimizeException(
-          String.format(
-              "The value of SOFFSET (%d) is equal to or exceeds the number of sequences (%d) that can actually be returned.",
-              finalOffset, consumed));
-    }
-    selectOperator.setSuffixPathList(newSuffixPathList);
-    selectOperator.setAggregations(newAggregations);
-    selectOperator.setUdfList(newUdfList);
-  }
-
-  protected Pair<List<PartialPath>, Integer> removeWildcard(PartialPath path, int limit, int offset)
-      throws MetadataException {
-    return IoTDB.metaManager.getAllTimeseriesPathWithAlias(path, limit, offset);
-  }
-
-  private void checkAndSetTsAlias(List<PartialPath> actualPaths, PartialPath originPath)
-      throws LogicalOptimizeException {
-    if (originPath.isTsAliasExists()) {
-      if (actualPaths.size() == 1) {
-        actualPaths.get(0).setTsAlias(originPath.getTsAlias());
-      } else if (actualPaths.size() >= 2) {
-        throw new LogicalOptimizeException(
-            "alias '" + originPath.getTsAlias() + "' can only be matched with one time series");
-      }
-    }
+    return new ArrayList<>(actualPaths);
   }
 
-  private static void cartesianProduct(
-      List<List<PartialPath>> dimensionValue,
-      List<List<PartialPath>> resultList,
-      int layer,
-      List<PartialPath> currentList) {
+  public static <T> void cartesianProduct(
+      List<List<T>> dimensionValue, List<List<T>> resultList, int layer, List<T> currentList) {
     if (layer < dimensionValue.size() - 1) {
       if (dimensionValue.get(layer).isEmpty()) {
         cartesianProduct(dimensionValue, resultList, layer + 1, currentList);
       } else {
         for (int i = 0; i < dimensionValue.get(layer).size(); i++) {
-          List<PartialPath> list = new ArrayList<>(currentList);
+          List<T> list = new ArrayList<>(currentList);
           list.add(dimensionValue.get(layer).get(i));
           cartesianProduct(dimensionValue, resultList, layer + 1, list);
         }
@@ -482,7 +224,7 @@ public class ConcatPathOptimizer implements ILogicalOptimizer {
         resultList.add(currentList);
       } else {
         for (int i = 0; i < dimensionValue.get(layer).size(); i++) {
-          List<PartialPath> list = new ArrayList<>(currentList);
+          List<T> list = new ArrayList<>(currentList);
           list.add(dimensionValue.get(layer).get(i));
           resultList.add(list);
         }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
index 26ab0a5..d851f7e 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
@@ -26,6 +26,6 @@ import org.apache.iotdb.db.qp.logical.Operator;
 @FunctionalInterface
 public interface ILogicalOptimizer {
 
-  Operator transform(Operator operator, int maxDeduplicatedPathNum)
+  Operator transform(Operator operator, int fetchSize)
       throws LogicalOptimizeException, PathNumOverLimitException;
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/RemoveNotOptimizer.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/RemoveNotOptimizer.java
index 598a884..099bcd9 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/RemoveNotOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/RemoveNotOptimizer.java
@@ -20,11 +20,13 @@ package org.apache.iotdb.db.qp.strategy.optimizer;
 
 import org.apache.iotdb.db.exception.query.LogicalOperatorException;
 import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
+import org.apache.iotdb.db.metadata.PartialPath;
 import org.apache.iotdb.db.qp.constant.SQLConstant;
 import org.apache.iotdb.db.qp.logical.crud.FilterOperator;
 import org.apache.iotdb.db.qp.logical.crud.FunctionOperator;
 
 import java.util.List;
+import java.util.Set;
 
 import static org.apache.iotdb.db.qp.constant.SQLConstant.KW_AND;
 import static org.apache.iotdb.db.qp.constant.SQLConstant.KW_NOT;
@@ -41,7 +43,10 @@ public class RemoveNotOptimizer implements IFilterOptimizer {
    */
   @Override
   public FilterOperator optimize(FilterOperator filter) throws LogicalOperatorException {
-    return removeNot(filter);
+    Set<PartialPath> pathSet = filter.getPathSet();
+    FilterOperator optimizedFilterOperator = removeNot(filter);
+    optimizedFilterOperator.setPathSet(pathSet);
+    return optimizedFilterOperator;
   }
 
   private FilterOperator removeNot(FilterOperator filter) throws LogicalOperatorException {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/utils/WildcardsRemover.java b/server/src/main/java/org/apache/iotdb/db/qp/utils/WildcardsRemover.java
new file mode 100644
index 0000000..eb3e02b
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/qp/utils/WildcardsRemover.java
@@ -0,0 +1,160 @@
+/*
+ * 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.qp.utils;
+
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
+import org.apache.iotdb.db.exception.query.PathNumOverLimitException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.logical.crud.QueryOperator;
+import org.apache.iotdb.db.qp.strategy.optimizer.ConcatPathOptimizer;
+import org.apache.iotdb.db.query.control.QueryResourceManager;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.service.IoTDB;
+import org.apache.iotdb.tsfile.utils.Pair;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Removes wildcards (applying memory control and slimit/soffset control) */
+public class WildcardsRemover {
+
+  private final int maxDeduplicatedPathNum;
+  private final int soffset;
+
+  private int currentOffset;
+  private int currentLimit;
+
+  /** Records the path number that the MManager totally returned. */
+  private int consumed;
+
+  public WildcardsRemover(QueryOperator queryOperator, int fetchSize) {
+    // Dataset of last query actually has only three columns, so we shouldn't limit the path num
+    // while constructing logical plan
+    // To avoid overflowing because logicalOptimize function may do maxDeduplicatedPathNum + 1, we
+    // set it to Integer.MAX_VALUE - 1
+    maxDeduplicatedPathNum =
+        queryOperator.isLastQuery()
+            ? Integer.MAX_VALUE - 1
+            : QueryResourceManager.getInstance().getMaxDeduplicatedPathNum(fetchSize);
+    soffset = queryOperator.getSeriesOffset();
+    currentOffset = soffset;
+
+    final int slimit = queryOperator.getSeriesLimit();
+    currentLimit =
+        slimit == 0 || maxDeduplicatedPathNum < slimit ? maxDeduplicatedPathNum + 1 : slimit;
+
+    consumed = 0;
+  }
+
+  public WildcardsRemover() {
+    maxDeduplicatedPathNum = Integer.MAX_VALUE - 1;
+    soffset = 0;
+    currentLimit = maxDeduplicatedPathNum + 1;
+    consumed = 0;
+  }
+
+  public List<PartialPath> removeWildcardFrom(PartialPath path) throws LogicalOptimizeException {
+    try {
+      Pair<List<PartialPath>, Integer> pair =
+          IoTDB.metaManager.getAllTimeseriesPathWithAlias(path, currentLimit, currentOffset);
+
+      consumed += pair.right;
+      if (currentOffset != 0) {
+        int delta = currentOffset - pair.right;
+        currentOffset = Math.max(delta, 0);
+        if (delta < 0) {
+          currentLimit += delta;
+        }
+      } else {
+        currentLimit -= pair.right;
+      }
+
+      return pair.left;
+    } catch (MetadataException e) {
+      throw new LogicalOptimizeException("error occurred when removing star: " + e.getMessage());
+    }
+  }
+
+  public List<List<Expression>> removeWildcardsFrom(List<Expression> expressions)
+      throws LogicalOptimizeException {
+    // One by one, remove the wildcards from the input expressions. In most cases, an expression
+    // will produce multiple expressions after removing the wildcards. We use extendedExpressions to
+    // collect the produced expressions.
+    List<List<Expression>> extendedExpressions = new ArrayList<>();
+    for (Expression originExpression : expressions) {
+      List<Expression> actualExpressions = new ArrayList<>();
+      originExpression.removeWildcards(new WildcardsRemover(), actualExpressions);
+      if (actualExpressions.isEmpty()) {
+        // Let's ignore the eval of the function which has at least one non-existence series as
+        // input. See IOTDB-1212: https://github.com/apache/iotdb/pull/3101
+        return Collections.emptyList();
+      }
+      extendedExpressions.add(actualExpressions);
+    }
+
+    // Calculate the Cartesian product of extendedExpressions to get the actual expressions after
+    // removing all wildcards. We use actualExpressions to collect them.
+    List<List<Expression>> actualExpressions = new ArrayList<>();
+    ConcatPathOptimizer.cartesianProduct(
+        extendedExpressions, actualExpressions, 0, new ArrayList<>());
+
+    // Apply the soffset & slimit control to the actualExpressions and return the remaining
+    // expressions.
+    List<List<Expression>> remainingExpressions = new ArrayList<>();
+    for (List<Expression> actualExpression : actualExpressions) {
+      if (currentOffset != 0) {
+        --currentOffset;
+        continue;
+      } else if (currentLimit != 0) {
+        --currentLimit;
+      } else {
+        break;
+      }
+      remainingExpressions.add(actualExpression);
+    }
+    consumed += actualExpressions.size();
+    return remainingExpressions;
+  }
+
+  /** @return should break the loop or not */
+  public boolean checkIfPathNumberIsOverLimit(List<ResultColumn> resultColumns)
+      throws PathNumOverLimitException {
+    if (currentLimit == 0) {
+      if (maxDeduplicatedPathNum < resultColumns.size()) {
+        throw new PathNumOverLimitException(maxDeduplicatedPathNum);
+      }
+      return true;
+    }
+    return false;
+  }
+
+  public void checkIfSoffsetIsExceeded(List<ResultColumn> resultColumns)
+      throws LogicalOptimizeException {
+    if (consumed == 0 ? soffset != 0 : resultColumns.isEmpty()) {
+      throw new LogicalOptimizeException(
+          String.format(
+              "The value of SOFFSET (%d) is equal to or exceeds the number of sequences (%d) that can actually be returned.",
+              soffset, consumed));
+    }
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/query/dataset/UDTFDataSet.java b/server/src/main/java/org/apache/iotdb/db/query/dataset/UDTFDataSet.java
index 5761ef2..19d81c6 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/dataset/UDTFDataSet.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/dataset/UDTFDataSet.java
@@ -163,7 +163,7 @@ public abstract class UDTFDataSet extends QueryDataSet {
   }
 
   private int[] calculateReaderIndexes(UDTFExecutor executor) {
-    List<PartialPath> paths = executor.getContext().getPaths();
+    List<PartialPath> paths = executor.getExpression().getPaths();
     int[] readerIndexes = new int[paths.size()];
     for (int i = 0; i < readerIndexes.length; ++i) {
       readerIndexes[i] = udtfPlan.getReaderIndex(paths.get(i).getFullPath());
diff --git a/server/src/main/java/org/apache/iotdb/db/query/executor/LastQueryExecutor.java b/server/src/main/java/org/apache/iotdb/db/query/executor/LastQueryExecutor.java
index d6a3169..4100964 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/executor/LastQueryExecutor.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/executor/LastQueryExecutor.java
@@ -101,16 +101,10 @@ public class LastQueryExecutor {
       if (lastPairList.get(i).right != null && lastPairList.get(i).right.getValue() != null) {
         TimeValuePair lastTimeValuePair = lastPairList.get(i).right;
         RowRecord resultRecord = new RowRecord(lastTimeValuePair.getTimestamp());
+
         Field pathField = new Field(TSDataType.TEXT);
-        if (selectedSeries.get(i).isTsAliasExists()) {
-          pathField.setBinaryV(new Binary(selectedSeries.get(i).getTsAlias()));
-        } else {
-          if (selectedSeries.get(i).isMeasurementAliasExists()) {
-            pathField.setBinaryV(new Binary(selectedSeries.get(i).getFullPathWithAlias()));
-          } else {
-            pathField.setBinaryV(new Binary(selectedSeries.get(i).getFullPath()));
-          }
-        }
+        pathField.setBinaryV(
+            new Binary(lastQueryPlan.getResultColumns().get(i).getResultColumnName()));
         resultRecord.addField(pathField);
 
         Field valueField = new Field(TSDataType.TEXT);
diff --git a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterConcatPathOptimizer.java b/server/src/main/java/org/apache/iotdb/db/query/expression/Expression.java
similarity index 54%
copy from cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterConcatPathOptimizer.java
copy to server/src/main/java/org/apache/iotdb/db/query/expression/Expression.java
index 5d67a8c..b0c5f60 100644
--- a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterConcatPathOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/Expression.java
@@ -17,21 +17,34 @@
  * under the License.
  */
 
-package org.apache.iotdb.cluster.query;
+package org.apache.iotdb.db.query.expression;
 
 import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
 import org.apache.iotdb.db.metadata.PartialPath;
-import org.apache.iotdb.db.qp.strategy.optimizer.ConcatPathOptimizer;
-import org.apache.iotdb.db.service.IoTDB;
-import org.apache.iotdb.tsfile.utils.Pair;
+import org.apache.iotdb.db.qp.utils.WildcardsRemover;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 
 import java.util.List;
+import java.util.Set;
 
-public class ClusterConcatPathOptimizer extends ConcatPathOptimizer {
+public interface Expression {
 
-  @Override
-  protected Pair<List<PartialPath>, Integer> removeWildcard(PartialPath path, int limit, int offset)
-      throws MetadataException {
-    return IoTDB.metaManager.getAllTimeseriesPathWithAlias(path, limit, offset);
+  default boolean isAggregationFunctionExpression() {
+    return false;
   }
+
+  default boolean isTimeSeriesGeneratingFunctionExpression() {
+    return false;
+  }
+
+  // TODO: implement this method
+  TSDataType dataType() throws MetadataException;
+
+  void concat(List<PartialPath> prefixPaths, List<Expression> resultExpressions);
+
+  void removeWildcards(WildcardsRemover wildcardsRemover, List<Expression> resultExpressions)
+      throws LogicalOptimizeException;
+
+  void collectPaths(Set<PartialPath> pathSet);
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/query/expression/ResultColumn.java b/server/src/main/java/org/apache/iotdb/db/query/expression/ResultColumn.java
new file mode 100644
index 0000000..2cd6269
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/ResultColumn.java
@@ -0,0 +1,139 @@
+/*
+ * 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.query.expression;
+
+import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.utils.WildcardsRemover;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * {@code ResultColumn} is used to represent a result column of a query.
+ *
+ * <p>Assume that we have time series in db as follows: <br>
+ * [ root.sg.d.a, root.sg.d.b, root.sg.e.a, root.sg.e.b ]
+ *
+ * <ul>
+ *   Example 1: select a, a + b, udf(udf(b)) from root.sg.d, root.sg.e;
+ *   <li>Step 1: constructed by sql visitor in logical operator: <br>
+ *       result columns: <br>
+ *       [a, a + b, udf(udf(b))]
+ *   <li>Step 2: concatenated with prefix paths in logical optimizer:<br>
+ *       result columns: <br>
+ *       [root.sg.d.a, root.sg.e.a, root.sg.d.a + root.sg.d.b, root.sg.d.a + root.sg.e.b,
+ *       root.sg.e.a + root.sg.d.b, root.sg.e.a + root.sg.e.b, udf(udf(root.sg.d.b)),
+ *       udf(udf(root.sg.e.b))]
+ *   <li>Step 3: remove wildcards in logical optimizer:<br>
+ *       result columns: <br>
+ *       [root.sg.d.a, root.sg.e.a, root.sg.d.a + root.sg.d.b, root.sg.d.a + root.sg.e.b,
+ *       root.sg.e.a + root.sg.d.b, root.sg.e.a + root.sg.e.b, udf(udf(root.sg.d.b)),
+ *       udf(udf(root.sg.e.b))]
+ * </ul>
+ *
+ * <ul>
+ *   Example 2: select *, a + *, udf(udf(*)) from root.sg.d;
+ *   <li>Step 1: constructed by sql visitor in logical operator: <br>
+ *       result columns: <br>
+ *       [*, a + * , udf(udf(*))]
+ *   <li>Step 2: concatenated with prefix paths in logical optimizer:<br>
+ *       result columns: <br>
+ *       [root.sg.d.*, root.sg.d.a + root.sg.d.*, udf(udf(root.sg.d.*))]
+ *   <li>Step 3: remove wildcards in logical optimizer:<br>
+ *       result columns: <br>
+ *       [root.sg.d.a, root.sg.d.b, root.sg.d.a + root.sg.d.a, root.sg.d.a + root.sg.d.b,
+ *       udf(udf(root.sg.d.a)), udf(udf(root.sg.d.b))]
+ * </ul>
+ */
+public class ResultColumn {
+
+  private final Expression expression;
+  private final String alias;
+
+  public ResultColumn(Expression expression, String alias) {
+    this.expression = expression;
+    this.alias = alias;
+  }
+
+  public ResultColumn(Expression expression) {
+    this.expression = expression;
+    alias = null;
+  }
+
+  /**
+   * @param prefixPaths prefix paths in the from clause
+   * @param resultColumns used to collect the result columns
+   */
+  public void concat(List<PartialPath> prefixPaths, List<ResultColumn> resultColumns)
+      throws LogicalOptimizeException {
+    List<Expression> resultExpressions = new ArrayList<>();
+    expression.concat(prefixPaths, resultExpressions);
+    if (hasAlias() && 1 < resultExpressions.size()) {
+      throw new LogicalOptimizeException(
+          String.format("alias '%s' can only be matched with one time series", alias));
+    }
+    for (Expression resultExpression : resultExpressions) {
+      resultColumns.add(new ResultColumn(resultExpression, alias));
+    }
+  }
+
+  /**
+   * @param wildcardsRemover used to remove wildcards from {@code expression} and apply slimit &
+   *     soffset control
+   * @param resultColumns used to collect the result columns
+   */
+  public void removeWildcards(WildcardsRemover wildcardsRemover, List<ResultColumn> resultColumns)
+      throws LogicalOptimizeException {
+    List<Expression> resultExpressions = new ArrayList<>();
+    expression.removeWildcards(wildcardsRemover, resultExpressions);
+    if (hasAlias() && 1 < resultExpressions.size()) {
+      throw new LogicalOptimizeException(
+          String.format("alias '%s' can only be matched with one time series", alias));
+    }
+    for (Expression resultExpression : resultExpressions) {
+      resultColumns.add(new ResultColumn(resultExpression, alias));
+    }
+  }
+
+  public Set<PartialPath> collectPaths() {
+    Set<PartialPath> pathSet = new HashSet<>();
+    expression.collectPaths(pathSet);
+    return pathSet;
+  }
+
+  public Expression getExpression() {
+    return expression;
+  }
+
+  public boolean hasAlias() {
+    return alias != null;
+  }
+
+  public String getAlias() {
+    return alias;
+  }
+
+  public String getResultColumnName() {
+    return alias != null ? alias : expression.toString();
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/AdditionExpression.java
similarity index 62%
copy from server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
copy to server/src/main/java/org/apache/iotdb/db/query/expression/binary/AdditionExpression.java
index 26ab0a5..5500ba4 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/AdditionExpression.java
@@ -16,16 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.iotdb.db.qp.strategy.optimizer;
 
-import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
-import org.apache.iotdb.db.exception.query.PathNumOverLimitException;
-import org.apache.iotdb.db.qp.logical.Operator;
+package org.apache.iotdb.db.query.expression.binary;
 
-/** provide a context, transform it for optimization. */
-@FunctionalInterface
-public interface ILogicalOptimizer {
+import org.apache.iotdb.db.query.expression.Expression;
 
-  Operator transform(Operator operator, int maxDeduplicatedPathNum)
-      throws LogicalOptimizeException, PathNumOverLimitException;
+public class AdditionExpression extends BinaryExpression {
+
+  public AdditionExpression(Expression leftExpression, Expression rightExpression) {
+    super(leftExpression, rightExpression);
+  }
+
+  @Override
+  protected String operator() {
+    return "+";
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/query/expression/binary/BinaryExpression.java b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/BinaryExpression.java
new file mode 100644
index 0000000..cff5bc3
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/BinaryExpression.java
@@ -0,0 +1,118 @@
+/*
+ * 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.query.expression.binary;
+
+import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.utils.WildcardsRemover;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public abstract class BinaryExpression implements Expression {
+
+  protected final Expression leftExpression;
+  protected final Expression rightExpression;
+
+  protected BinaryExpression(Expression leftExpression, Expression rightExpression) {
+    this.leftExpression = leftExpression;
+    this.rightExpression = rightExpression;
+  }
+
+  /**
+   * The result data type of all arithmetic operations will be DOUBLE.
+   *
+   * <p>TODO: This is just a simple implementation and should be optimized later.
+   */
+  @Override
+  public final TSDataType dataType() {
+    return TSDataType.DOUBLE;
+  }
+
+  @Override
+  public final void concat(List<PartialPath> prefixPaths, List<Expression> resultExpressions) {
+    List<Expression> leftExpressions = new ArrayList<>();
+    leftExpression.concat(prefixPaths, leftExpressions);
+
+    List<Expression> rightExpressions = new ArrayList<>();
+    rightExpression.concat(prefixPaths, rightExpressions);
+
+    reconstruct(leftExpressions, rightExpressions, resultExpressions);
+  }
+
+  @Override
+  public final void removeWildcards(
+      WildcardsRemover wildcardsRemover, List<Expression> resultExpressions)
+      throws LogicalOptimizeException {
+    List<Expression> leftExpressions = new ArrayList<>();
+    leftExpression.removeWildcards(wildcardsRemover, leftExpressions);
+
+    List<Expression> rightExpressions = new ArrayList<>();
+    rightExpression.removeWildcards(wildcardsRemover, rightExpressions);
+
+    reconstruct(leftExpressions, rightExpressions, resultExpressions);
+  }
+
+  private void reconstruct(
+      List<Expression> leftExpressions,
+      List<Expression> rightExpressions,
+      List<Expression> resultExpressions) {
+    for (Expression le : leftExpressions) {
+      for (Expression re : rightExpressions) {
+        switch (operator()) {
+          case "+":
+            resultExpressions.add(new AdditionExpression(le, re));
+            break;
+          case "-":
+            resultExpressions.add(new SubtractionExpression(le, re));
+            break;
+          case "*":
+            resultExpressions.add(new MultiplicationExpression(le, re));
+            break;
+          case "/":
+            resultExpressions.add(new DivisionExpression(le, re));
+            break;
+          case "%":
+            resultExpressions.add(new ModuloExpression(le, re));
+            break;
+          default:
+            throw new UnsupportedOperationException();
+        }
+      }
+    }
+  }
+
+  @Override
+  public void collectPaths(Set<PartialPath> pathSet) {
+    leftExpression.collectPaths(pathSet);
+    rightExpression.collectPaths(pathSet);
+  }
+
+  @Override
+  public final String toString() {
+    return String.format(
+        "%s %s %s", leftExpression.toString(), operator(), rightExpression.toString());
+  }
+
+  protected abstract String operator();
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/DivisionExpression.java
similarity index 62%
copy from server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
copy to server/src/main/java/org/apache/iotdb/db/query/expression/binary/DivisionExpression.java
index 26ab0a5..e611887 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/DivisionExpression.java
@@ -16,16 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.iotdb.db.qp.strategy.optimizer;
 
-import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
-import org.apache.iotdb.db.exception.query.PathNumOverLimitException;
-import org.apache.iotdb.db.qp.logical.Operator;
+package org.apache.iotdb.db.query.expression.binary;
 
-/** provide a context, transform it for optimization. */
-@FunctionalInterface
-public interface ILogicalOptimizer {
+import org.apache.iotdb.db.query.expression.Expression;
 
-  Operator transform(Operator operator, int maxDeduplicatedPathNum)
-      throws LogicalOptimizeException, PathNumOverLimitException;
+public class DivisionExpression extends BinaryExpression {
+
+  public DivisionExpression(Expression leftExpression, Expression rightExpression) {
+    super(leftExpression, rightExpression);
+  }
+
+  @Override
+  protected String operator() {
+    return "/";
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/ModuloExpression.java
similarity index 62%
copy from server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
copy to server/src/main/java/org/apache/iotdb/db/query/expression/binary/ModuloExpression.java
index 26ab0a5..94dd905 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/ModuloExpression.java
@@ -16,16 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.iotdb.db.qp.strategy.optimizer;
 
-import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
-import org.apache.iotdb.db.exception.query.PathNumOverLimitException;
-import org.apache.iotdb.db.qp.logical.Operator;
+package org.apache.iotdb.db.query.expression.binary;
 
-/** provide a context, transform it for optimization. */
-@FunctionalInterface
-public interface ILogicalOptimizer {
+import org.apache.iotdb.db.query.expression.Expression;
 
-  Operator transform(Operator operator, int maxDeduplicatedPathNum)
-      throws LogicalOptimizeException, PathNumOverLimitException;
+public class ModuloExpression extends BinaryExpression {
+
+  public ModuloExpression(Expression leftExpression, Expression rightExpression) {
+    super(leftExpression, rightExpression);
+  }
+
+  @Override
+  protected String operator() {
+    return "%";
+  }
 }
diff --git a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterConcatPathOptimizer.java b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/MultiplicationExpression.java
similarity index 57%
rename from cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterConcatPathOptimizer.java
rename to server/src/main/java/org/apache/iotdb/db/query/expression/binary/MultiplicationExpression.java
index 5d67a8c..ae3d17b 100644
--- a/cluster/src/main/java/org/apache/iotdb/cluster/query/ClusterConcatPathOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/MultiplicationExpression.java
@@ -17,21 +17,18 @@
  * under the License.
  */
 
-package org.apache.iotdb.cluster.query;
+package org.apache.iotdb.db.query.expression.binary;
 
-import org.apache.iotdb.db.exception.metadata.MetadataException;
-import org.apache.iotdb.db.metadata.PartialPath;
-import org.apache.iotdb.db.qp.strategy.optimizer.ConcatPathOptimizer;
-import org.apache.iotdb.db.service.IoTDB;
-import org.apache.iotdb.tsfile.utils.Pair;
+import org.apache.iotdb.db.query.expression.Expression;
 
-import java.util.List;
+public class MultiplicationExpression extends BinaryExpression {
 
-public class ClusterConcatPathOptimizer extends ConcatPathOptimizer {
+  public MultiplicationExpression(Expression leftExpression, Expression rightExpression) {
+    super(leftExpression, rightExpression);
+  }
 
   @Override
-  protected Pair<List<PartialPath>, Integer> removeWildcard(PartialPath path, int limit, int offset)
-      throws MetadataException {
-    return IoTDB.metaManager.getAllTimeseriesPathWithAlias(path, limit, offset);
+  protected String operator() {
+    return "*";
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/SubtractionExpression.java
similarity index 62%
copy from server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
copy to server/src/main/java/org/apache/iotdb/db/query/expression/binary/SubtractionExpression.java
index 26ab0a5..65bcf50 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/optimizer/ILogicalOptimizer.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/binary/SubtractionExpression.java
@@ -16,16 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.iotdb.db.qp.strategy.optimizer;
 
-import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
-import org.apache.iotdb.db.exception.query.PathNumOverLimitException;
-import org.apache.iotdb.db.qp.logical.Operator;
+package org.apache.iotdb.db.query.expression.binary;
 
-/** provide a context, transform it for optimization. */
-@FunctionalInterface
-public interface ILogicalOptimizer {
+import org.apache.iotdb.db.query.expression.Expression;
 
-  Operator transform(Operator operator, int maxDeduplicatedPathNum)
-      throws LogicalOptimizeException, PathNumOverLimitException;
+public class SubtractionExpression extends BinaryExpression {
+
+  public SubtractionExpression(Expression leftExpression, Expression rightExpression) {
+    super(leftExpression, rightExpression);
+  }
+
+  @Override
+  protected String operator() {
+    return "-";
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/query/expression/unary/FunctionExpression.java b/server/src/main/java/org/apache/iotdb/db/query/expression/unary/FunctionExpression.java
new file mode 100644
index 0000000..a321f97
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/unary/FunctionExpression.java
@@ -0,0 +1,232 @@
+/*
+ * 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.query.expression.unary;
+
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.constant.SQLConstant;
+import org.apache.iotdb.db.qp.strategy.optimizer.ConcatPathOptimizer;
+import org.apache.iotdb.db.qp.utils.WildcardsRemover;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.tsfile.exception.NotImplementedException;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class FunctionExpression implements Expression {
+
+  /**
+   * true: aggregation function<br>
+   * false: time series generating function
+   */
+  private final boolean isAggregationFunctionExpression;
+
+  private final String functionName;
+  private final Map<String, String> functionAttributes;
+
+  /**
+   * example: select udf(a, b, udf(c)) from root.sg.d;
+   *
+   * <p>3 expressions [root.sg.d.a, root.sg.d.b, udf(root.sg.d.c)] will be in this field.
+   */
+  private List<Expression> expressions;
+
+  private List<TSDataType> dataTypes;
+  private List<PartialPath> paths;
+
+  private String expressionString;
+  private String parametersString;
+
+  public FunctionExpression(String functionName) {
+    this.functionName = functionName;
+    functionAttributes = new LinkedHashMap<>();
+    expressions = new ArrayList<>();
+    isAggregationFunctionExpression =
+        SQLConstant.getNativeFunctionNames().contains(functionName.toLowerCase());
+  }
+
+  public FunctionExpression(
+      String functionName, Map<String, String> functionAttributes, List<Expression> expressions) {
+    this.functionName = functionName;
+    this.functionAttributes = functionAttributes;
+    this.expressions = expressions;
+    isAggregationFunctionExpression =
+        SQLConstant.getNativeFunctionNames().contains(functionName.toLowerCase());
+  }
+
+  @Override
+  public boolean isAggregationFunctionExpression() {
+    return isAggregationFunctionExpression;
+  }
+
+  @Override
+  public boolean isTimeSeriesGeneratingFunctionExpression() {
+    return !isAggregationFunctionExpression;
+  }
+
+  public void addAttribute(String key, String value) {
+    functionAttributes.put(key, value);
+  }
+
+  public void addExpression(Expression expression) {
+    expressions.add(expression);
+  }
+
+  public void setExpressions(List<Expression> expressions) {
+    this.expressions = expressions;
+  }
+
+  public String getFunctionName() {
+    return functionName;
+  }
+
+  public Map<String, String> getFunctionAttributes() {
+    return functionAttributes;
+  }
+
+  public List<Expression> getExpressions() {
+    return expressions;
+  }
+
+  @Override
+  public TSDataType dataType() {
+    // TODO: the expression type is determined in runtime
+    throw new NotImplementedException();
+  }
+
+  @Override
+  public void concat(List<PartialPath> prefixPaths, List<Expression> resultExpressions) {
+    List<List<Expression>> resultExpressionsForRecursionList = new ArrayList<>();
+
+    for (Expression suffixExpression : expressions) {
+      List<Expression> resultExpressionsForRecursion = new ArrayList<>();
+      suffixExpression.concat(prefixPaths, resultExpressionsForRecursion);
+      resultExpressionsForRecursionList.add(resultExpressionsForRecursion);
+    }
+
+    List<List<Expression>> functionExpressions = new ArrayList<>();
+    ConcatPathOptimizer.cartesianProduct(
+        resultExpressionsForRecursionList, functionExpressions, 0, new ArrayList<>());
+    for (List<Expression> functionExpression : functionExpressions) {
+      resultExpressions.add(
+          new FunctionExpression(functionName, functionAttributes, functionExpression));
+    }
+  }
+
+  @Override
+  public void removeWildcards(WildcardsRemover wildcardsRemover, List<Expression> resultExpressions)
+      throws LogicalOptimizeException {
+    for (List<Expression> functionExpression : wildcardsRemover.removeWildcardsFrom(expressions)) {
+      resultExpressions.add(
+          new FunctionExpression(functionName, functionAttributes, functionExpression));
+    }
+  }
+
+  @Override
+  public void collectPaths(Set<PartialPath> pathSet) {
+    for (Expression expression : expressions) {
+      expression.collectPaths(pathSet);
+    }
+  }
+
+  public List<TSDataType> getDataTypes() throws MetadataException {
+    if (dataTypes == null) {
+      dataTypes = new ArrayList<>();
+      for (Expression expression : expressions) {
+        dataTypes.add(expression.dataType());
+      }
+    }
+    return dataTypes;
+  }
+
+  public List<PartialPath> getPaths() {
+    if (paths == null) {
+      paths = new ArrayList<>();
+      for (Expression expression : expressions) {
+        paths.add(
+            expression instanceof TimeSeriesOperand
+                ? ((TimeSeriesOperand) expression).getPath()
+                : null);
+      }
+    }
+    return paths;
+  }
+
+  @Override
+  public String toString() {
+    if (expressionString == null) {
+      expressionString = functionName + "(" + getParametersString() + ")";
+    }
+    return expressionString;
+  }
+
+  /**
+   * Generates the parameter part of the function column name.
+   *
+   * <p>Example:
+   *
+   * <p>Full column name -> udf(root.sg.d.s1, sin(root.sg.d.s1), 'key1'='value1', 'key2'='value2')
+   *
+   * <p>The parameter part -> root.sg.d.s1, sin(root.sg.d.s1), 'key1'='value1', 'key2'='value2'
+   */
+  public String getParametersString() {
+    if (parametersString == null) {
+      StringBuilder builder = new StringBuilder();
+      if (!expressions.isEmpty()) {
+        builder.append(expressions.get(0).toString());
+        for (int i = 1; i < expressions.size(); ++i) {
+          builder.append(", ").append(expressions.get(i).toString());
+        }
+      }
+      if (!functionAttributes.isEmpty()) {
+        if (!expressions.isEmpty()) {
+          builder.append(", ");
+        }
+        Iterator<Entry<String, String>> iterator = functionAttributes.entrySet().iterator();
+        Entry<String, String> entry = iterator.next();
+        builder
+            .append("\"")
+            .append(entry.getKey())
+            .append("\"=\"")
+            .append(entry.getValue())
+            .append("\"");
+        while (iterator.hasNext()) {
+          entry = iterator.next();
+          builder
+              .append(", ")
+              .append("\"")
+              .append(entry.getKey())
+              .append("\"=\"")
+              .append(entry.getValue())
+              .append("\"");
+        }
+      }
+      parametersString = builder.toString();
+    }
+    return parametersString;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/query/expression/unary/MinusExpression.java b/server/src/main/java/org/apache/iotdb/db/query/expression/unary/MinusExpression.java
new file mode 100644
index 0000000..afd0017
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/unary/MinusExpression.java
@@ -0,0 +1,78 @@
+/*
+ * 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.query.expression.unary;
+
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.utils.WildcardsRemover;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class MinusExpression implements Expression {
+
+  protected Expression expression;
+
+  public MinusExpression(Expression expression) {
+    this.expression = expression;
+  }
+
+  public Expression getExpression() {
+    return expression;
+  }
+
+  @Override
+  public TSDataType dataType() throws MetadataException {
+    return expression.dataType();
+  }
+
+  @Override
+  public void concat(List<PartialPath> prefixPaths, List<Expression> resultExpressions) {
+    List<Expression> resultExpressionsForRecursion = new ArrayList<>();
+    expression.concat(prefixPaths, resultExpressionsForRecursion);
+    for (Expression resultExpression : resultExpressionsForRecursion) {
+      resultExpressions.add(new MinusExpression(resultExpression));
+    }
+  }
+
+  @Override
+  public void removeWildcards(WildcardsRemover wildcardsRemover, List<Expression> resultExpressions)
+      throws LogicalOptimizeException {
+    List<Expression> resultExpressionsForRecursion = new ArrayList<>();
+    expression.removeWildcards(wildcardsRemover, resultExpressionsForRecursion);
+    for (Expression resultExpression : resultExpressionsForRecursion) {
+      resultExpressions.add(new MinusExpression(resultExpression));
+    }
+  }
+
+  @Override
+  public void collectPaths(Set<PartialPath> pathSet) {
+    expression.collectPaths(pathSet);
+  }
+
+  @Override
+  public String toString() {
+    return "-" + expression.toString();
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/query/expression/unary/TimeSeriesOperand.java b/server/src/main/java/org/apache/iotdb/db/query/expression/unary/TimeSeriesOperand.java
new file mode 100644
index 0000000..e69353c
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/query/expression/unary/TimeSeriesOperand.java
@@ -0,0 +1,82 @@
+/*
+ * 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.query.expression.unary;
+
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.utils.WildcardsRemover;
+import org.apache.iotdb.db.query.expression.Expression;
+import org.apache.iotdb.db.service.IoTDB;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import java.util.List;
+import java.util.Set;
+
+public class TimeSeriesOperand implements Expression {
+
+  protected PartialPath path;
+  protected TSDataType dataType;
+
+  public TimeSeriesOperand(PartialPath path) {
+    this.path = path;
+  }
+
+  public PartialPath getPath() {
+    return path;
+  }
+
+  public void setPath(PartialPath path) {
+    this.path = path;
+  }
+
+  @Override
+  public TSDataType dataType() throws MetadataException {
+    if (dataType == null) {
+      dataType = IoTDB.metaManager.getSeriesType(path);
+    }
+    return dataType;
+  }
+
+  @Override
+  public void concat(List<PartialPath> prefixPaths, List<Expression> resultExpressions) {
+    for (PartialPath prefixPath : prefixPaths) {
+      resultExpressions.add(new TimeSeriesOperand(prefixPath.concatPath(path)));
+    }
+  }
+
+  @Override
+  public void removeWildcards(WildcardsRemover wildcardsRemover, List<Expression> resultExpressions)
+      throws LogicalOptimizeException {
+    for (PartialPath actualPath : wildcardsRemover.removeWildcardFrom(path)) {
+      resultExpressions.add(new TimeSeriesOperand(actualPath));
+    }
+  }
+
+  @Override
+  public void collectPaths(Set<PartialPath> pathSet) {
+    pathSet.add(path);
+  }
+
+  @Override
+  public String toString() {
+    return path.isMeasurementAliasExists() ? path.getFullPathWithAlias() : path.getFullPath();
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/query/udf/core/context/UDFContext.java b/server/src/main/java/org/apache/iotdb/db/query/udf/core/context/UDFContext.java
deleted file mode 100644
index 00e294e..0000000
--- a/server/src/main/java/org/apache/iotdb/db/query/udf/core/context/UDFContext.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.iotdb.db.query.udf.core.context;
-
-import org.apache.iotdb.db.exception.metadata.MetadataException;
-import org.apache.iotdb.db.metadata.PartialPath;
-import org.apache.iotdb.db.service.IoTDB;
-import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-public class UDFContext {
-
-  private final String name;
-  private final Map<String, String> attributes;
-
-  private List<PartialPath> paths;
-  private List<TSDataType> dataTypes;
-
-  private String columnParameterPart;
-  private String column;
-
-  public UDFContext(String name) {
-    this.name = name;
-    attributes = new LinkedHashMap<>();
-    paths = new ArrayList<>();
-  }
-
-  public UDFContext(String name, Map<String, String> attributes, List<PartialPath> paths) {
-    this.name = name;
-    this.attributes = attributes;
-    this.paths = paths;
-  }
-
-  public void addAttribute(String key, String value) {
-    attributes.put(key, value);
-  }
-
-  public void addPath(PartialPath path) {
-    paths.add(path);
-  }
-
-  public void setPaths(List<PartialPath> paths) {
-    this.paths = paths;
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public Map<String, String> getAttributes() {
-    return attributes;
-  }
-
-  public List<PartialPath> getPaths() {
-    return paths;
-  }
-
-  public List<TSDataType> getDataTypes() throws MetadataException {
-    if (dataTypes == null) {
-      dataTypes = new ArrayList<>();
-      for (PartialPath path : paths) {
-        dataTypes.add(IoTDB.metaManager.getSeriesType(path));
-      }
-    }
-    return dataTypes;
-  }
-
-  /** Generates the column name of the udf query. */
-  public String getColumnName() {
-    if (column == null) {
-      column = name + "(" + getColumnNameParameterPart() + ")";
-    }
-    return column;
-  }
-
-  /**
-   * Generates the parameter part of the udf column name.
-   *
-   * <p>Example: <br>
-   * Full column name -> udf(root.sg.d.s1, root.sg.d.s1, 'key1'='value1', 'key2'='value2') <br>
-   * The parameter part -> root.sg.d.s1, root.sg.d.s1, 'key1'='value1', 'key2'='value2'
-   */
-  private String getColumnNameParameterPart() {
-    if (columnParameterPart == null) {
-      StringBuilder builder = new StringBuilder();
-      if (!paths.isEmpty()) {
-        builder.append(paths.get(0).getFullPath());
-        for (int i = 1; i < paths.size(); ++i) {
-          builder.append(", ").append(paths.get(i).getFullPath());
-        }
-      }
-      if (!attributes.isEmpty()) {
-        if (!paths.isEmpty()) {
-          builder.append(", ");
-        }
-        Iterator<Entry<String, String>> iterator = attributes.entrySet().iterator();
-        Entry<String, String> entry = iterator.next();
-        builder
-            .append("\"")
-            .append(entry.getKey())
-            .append("\"=\"")
-            .append(entry.getValue())
-            .append("\"");
-        while (iterator.hasNext()) {
-          entry = iterator.next();
-          builder
-              .append(", ")
-              .append("\"")
-              .append(entry.getKey())
-              .append("\"=\"")
-              .append(entry.getValue())
-              .append("\"");
-        }
-      }
-      columnParameterPart = builder.toString();
-    }
-    return columnParameterPart;
-  }
-}
diff --git a/server/src/main/java/org/apache/iotdb/db/query/udf/core/executor/UDTFExecutor.java b/server/src/main/java/org/apache/iotdb/db/query/udf/core/executor/UDTFExecutor.java
index 9d69582..59b0a2d 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/udf/core/executor/UDTFExecutor.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/udf/core/executor/UDTFExecutor.java
@@ -20,13 +20,13 @@
 package org.apache.iotdb.db.query.udf.core.executor;
 
 import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.query.expression.unary.FunctionExpression;
 import org.apache.iotdb.db.query.udf.api.UDTF;
 import org.apache.iotdb.db.query.udf.api.access.Row;
 import org.apache.iotdb.db.query.udf.api.access.RowWindow;
 import org.apache.iotdb.db.query.udf.api.customizer.config.UDTFConfigurations;
 import org.apache.iotdb.db.query.udf.api.customizer.parameter.UDFParameterValidator;
 import org.apache.iotdb.db.query.udf.api.customizer.parameter.UDFParameters;
-import org.apache.iotdb.db.query.udf.core.context.UDFContext;
 import org.apache.iotdb.db.query.udf.datastructure.tv.ElasticSerializableTVList;
 import org.apache.iotdb.db.query.udf.service.UDFRegistrationService;
 
@@ -34,21 +34,22 @@ import java.time.ZoneId;
 
 public class UDTFExecutor {
 
-  protected final UDFContext context;
+  protected final FunctionExpression expression;
   protected final UDTFConfigurations configurations;
   protected UDTF udtf;
   protected ElasticSerializableTVList collector;
 
-  public UDTFExecutor(UDFContext context, ZoneId zoneId) {
-    this.context = context;
+  public UDTFExecutor(FunctionExpression expression, ZoneId zoneId) {
+    this.expression = expression;
     configurations = new UDTFConfigurations(zoneId);
   }
 
   public void beforeStart(long queryId, float collectorMemoryBudgetInMB)
       throws QueryProcessException {
-    udtf = (UDTF) UDFRegistrationService.getInstance().reflect(context);
+    udtf = (UDTF) UDFRegistrationService.getInstance().reflect(expression);
 
-    UDFParameters parameters = new UDFParameters(context.getPaths(), context.getAttributes());
+    UDFParameters parameters =
+        new UDFParameters(expression.getPaths(), expression.getFunctionAttributes());
 
     try {
       udtf.validate(new UDFParameterValidator(parameters));
@@ -103,8 +104,8 @@ public class UDTFExecutor {
             + e);
   }
 
-  public UDFContext getContext() {
-    return context;
+  public FunctionExpression getExpression() {
+    return expression;
   }
 
   public UDTFConfigurations getConfigurations() {
diff --git a/server/src/main/java/org/apache/iotdb/db/query/udf/service/UDFRegistrationService.java b/server/src/main/java/org/apache/iotdb/db/query/udf/service/UDFRegistrationService.java
index 88ceaba..bc680c5 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/udf/service/UDFRegistrationService.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/udf/service/UDFRegistrationService.java
@@ -25,9 +25,9 @@ import org.apache.iotdb.db.exception.StartupException;
 import org.apache.iotdb.db.exception.UDFRegistrationException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
 import org.apache.iotdb.db.qp.constant.SQLConstant;
+import org.apache.iotdb.db.query.expression.unary.FunctionExpression;
 import org.apache.iotdb.db.query.udf.api.UDF;
 import org.apache.iotdb.db.query.udf.builtin.BuiltinFunction;
-import org.apache.iotdb.db.query.udf.core.context.UDFContext;
 import org.apache.iotdb.db.service.IService;
 import org.apache.iotdb.db.service.ServiceType;
 import org.apache.iotdb.db.utils.TestOnly;
@@ -249,8 +249,8 @@ public class UDFRegistrationService implements IService {
     }
   }
 
-  public UDF reflect(UDFContext context) throws QueryProcessException {
-    String functionName = context.getName().toUpperCase();
+  public UDF reflect(FunctionExpression expression) throws QueryProcessException {
+    String functionName = expression.getFunctionName().toUpperCase();
     UDFRegistrationInformation information = registrationInformation.get(functionName);
     if (information == null) {
       String errorMessage =
diff --git a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
index 15d2b35..fc8c344 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
@@ -82,6 +82,8 @@ import org.apache.iotdb.db.query.dataset.AlignByDeviceDataSet;
 import org.apache.iotdb.db.query.dataset.DirectAlignByTimeDataSet;
 import org.apache.iotdb.db.query.dataset.DirectNonAlignDataSet;
 import org.apache.iotdb.db.query.dataset.UDTFDataSet;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.db.query.expression.unary.TimeSeriesOperand;
 import org.apache.iotdb.db.tools.watermark.GroupedLSBWatermarkEncoder;
 import org.apache.iotdb.db.tools.watermark.WatermarkEncoder;
 import org.apache.iotdb.db.utils.QueryDataSetUtils;
@@ -987,27 +989,18 @@ public class TSServiceImpl implements TSIService.Iface {
   }
 
   // wide means not align by device
-  @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning
   private void getWideQueryHeaders(
       QueryPlan plan, List<String> respColumns, List<String> columnTypes)
       throws TException, MetadataException {
-    // Restore column header of aggregate to func(column_name), only
-    // support single aggregate function for now
+    List<ResultColumn> resultColumns = plan.getResultColumns();
     List<PartialPath> paths = plan.getPaths();
     List<TSDataType> seriesTypes = new ArrayList<>();
     switch (plan.getOperatorType()) {
       case QUERY:
       case FILL:
-        for (PartialPath path : paths) {
-          String column;
-          if (path.isTsAliasExists()) {
-            column = path.getTsAlias();
-          } else {
-            column =
-                path.isMeasurementAliasExists() ? path.getFullPathWithAlias() : path.getFullPath();
-          }
-          respColumns.add(column);
-          seriesTypes.add(getSeriesTypeByPath(path));
+        for (int i = 0; i < resultColumns.size(); ++i) {
+          respColumns.add(resultColumns.get(i).getResultColumnName());
+          seriesTypes.add(getSeriesTypeByPath(paths.get(i)));
         }
         break;
       case AGGREGATION:
@@ -1019,18 +1012,8 @@ public class TSServiceImpl implements TSIService.Iface {
             aggregations.add(aggregations.get(0));
           }
         }
-        for (int i = 0; i < paths.size(); i++) {
-          PartialPath path = paths.get(i);
-          String column;
-          if (path.isTsAliasExists()) {
-            column = path.getTsAlias();
-          } else {
-            column =
-                path.isMeasurementAliasExists()
-                    ? aggregations.get(i) + "(" + paths.get(i).getFullPathWithAlias() + ")"
-                    : aggregations.get(i) + "(" + paths.get(i).getFullPath() + ")";
-          }
-          respColumns.add(column);
+        for (ResultColumn resultColumn : resultColumns) {
+          respColumns.add(resultColumn.getResultColumnName());
         }
         seriesTypes = getSeriesTypesByPaths(paths, aggregations);
         break;
@@ -1038,15 +1021,9 @@ public class TSServiceImpl implements TSIService.Iface {
         seriesTypes = new ArrayList<>();
         UDTFPlan udtfPlan = (UDTFPlan) plan;
         for (int i = 0; i < paths.size(); i++) {
-          respColumns.add(
-              paths.get(i) != null
-                  ? paths.get(i).getFullPath()
-                  : udtfPlan
-                      .getExecutorByOriginalOutputColumnIndex(i)
-                      .getContext()
-                      .getColumnName());
+          respColumns.add(resultColumns.get(i).getResultColumnName());
           seriesTypes.add(
-              paths.get(i) != null
+              resultColumns.get(i).getExpression() instanceof TimeSeriesOperand
                   ? udtfPlan.getDataTypes().get(i)
                   : udtfPlan
                       .getExecutorByOriginalOutputColumnIndex(i)
diff --git a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBUDFManagementIT.java b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBUDFManagementIT.java
index f85e012..b970eaf 100644
--- a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBUDFManagementIT.java
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBUDFManagementIT.java
@@ -223,7 +223,10 @@ public class IoTDBUDFManagementIT {
       statement.execute("create function aVg as \"org.apache.iotdb.db.query.udf.example.Adder\"");
       fail();
     } catch (SQLException throwable) {
-      assertTrue(throwable.getMessage().contains("expecting ID"));
+      assertTrue(
+          throwable
+              .getMessage()
+              .contains("the given function name conflicts with the built-in function name"));
     }
   }
 
@@ -237,7 +240,10 @@ public class IoTDBUDFManagementIT {
           "create function MAX_VALUE as \"org.apache.iotdb.db.query.udf.example.Adder\"");
       fail();
     } catch (SQLException throwable) {
-      assertTrue(throwable.getMessage().contains("expecting ID"));
+      assertTrue(
+          throwable
+              .getMessage()
+              .contains("the given function name conflicts with the built-in function name"));
     }
   }
 
diff --git a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBUDTFHybridQueryIT.java b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBUDTFHybridQueryIT.java
index 4ab33dd..0710b24 100644
--- a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBUDTFHybridQueryIT.java
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBUDTFHybridQueryIT.java
@@ -155,7 +155,8 @@ public class IoTDBUDTFHybridQueryIT {
       statement.executeQuery(sql);
       fail();
     } catch (SQLException throwable) {
-      assertTrue(throwable.getMessage().contains("parsing SQL to physical plan"));
+      assertTrue(
+          throwable.getMessage().contains("Last queries can only be applied on raw time series."));
     }
   }
 
diff --git a/server/src/test/java/org/apache/iotdb/db/qp/logical/IndexLogicalPlanTest.java b/server/src/test/java/org/apache/iotdb/db/qp/logical/IndexLogicalPlanTest.java
index 1e09934..38593cb 100644
--- a/server/src/test/java/org/apache/iotdb/db/qp/logical/IndexLogicalPlanTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/qp/logical/IndexLogicalPlanTest.java
@@ -41,24 +41,19 @@ import static org.apache.iotdb.db.index.common.IndexConstant.TOP_K;
 
 public class IndexLogicalPlanTest {
 
-  private LogicalGenerator generator;
-
   @Before
-  public void before() {
-    generator = new LogicalGenerator();
-  }
+  public void before() {}
 
   @Test
   public void testParseCreateIndexWholeMatching() {
     String sqlStr =
         "CREATE INDEX ON root.Ery.*.Glu WHERE time > 50 WITH INDEX=RTREE_PAA, PAA_dim=8";
-    Operator op = generator.generate(sqlStr, ZoneId.systemDefault());
+    Operator op = LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(CreateIndexOperator.class, op.getClass());
     CreateIndexOperator createOperator = (CreateIndexOperator) op;
     Assert.assertEquals(OperatorType.CREATE_INDEX, createOperator.getType());
-    Assert.assertNotNull(createOperator.getSelectedPaths());
-    Assert.assertEquals("root.Ery.*.Glu", createOperator.getSelectedPaths().get(0).toString());
-    Assert.assertNull(createOperator.getFromOperator());
+    Assert.assertNotNull(createOperator.getPaths());
+    Assert.assertEquals("root.Ery.*.Glu", createOperator.getPaths().get(0).toString());
     Assert.assertEquals(IndexType.RTREE_PAA, createOperator.getIndexType());
     Assert.assertEquals(50, createOperator.getTime());
     Assert.assertEquals(1, createOperator.getProps().size());
@@ -68,14 +63,12 @@ public class IndexLogicalPlanTest {
   @Test
   public void testParseCreateIndexSubMatching() {
     String sqlStr = "CREATE INDEX ON root.Wind.AZQ02.Speed WITH INDEX=ELB_INDEX, BLOCK_SIZE=5";
-    Operator op = generator.generate(sqlStr, ZoneId.systemDefault());
+    Operator op = LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(CreateIndexOperator.class, op.getClass());
     CreateIndexOperator createOperator = (CreateIndexOperator) op;
     Assert.assertEquals(OperatorType.CREATE_INDEX, createOperator.getType());
-    Assert.assertNotNull(createOperator.getSelectedPaths());
-    Assert.assertEquals(
-        "root.Wind.AZQ02.Speed", createOperator.getSelectedPaths().get(0).toString());
-    Assert.assertNull(createOperator.getFromOperator());
+    Assert.assertNotNull(createOperator.getPaths());
+    Assert.assertEquals("root.Wind.AZQ02.Speed", createOperator.getPaths().get(0).toString());
     Assert.assertEquals(IndexType.ELB_INDEX, createOperator.getIndexType());
     Assert.assertEquals(1, createOperator.getProps().size());
     Assert.assertEquals("5", createOperator.getProps().get(BLOCK_SIZE));
@@ -84,27 +77,24 @@ public class IndexLogicalPlanTest {
   @Test
   public void testParseDropIndexWholeMatching() {
     String sqlStr = "DROP INDEX RTREE_PAA ON root.Ery.*.Glu";
-    Operator op = generator.generate(sqlStr, ZoneId.systemDefault());
+    Operator op = LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(DropIndexOperator.class, op.getClass());
     DropIndexOperator dropIndexOperator = (DropIndexOperator) op;
     Assert.assertEquals(OperatorType.DROP_INDEX, dropIndexOperator.getType());
-    Assert.assertNotNull(dropIndexOperator.getSelectedPaths());
-    Assert.assertEquals("root.Ery.*.Glu", dropIndexOperator.getSelectedPaths().get(0).toString());
-    Assert.assertNull(dropIndexOperator.getFromOperator());
+    Assert.assertNotNull(dropIndexOperator.getPaths());
+    Assert.assertEquals("root.Ery.*.Glu", dropIndexOperator.getPaths().get(0).toString());
     Assert.assertEquals(IndexType.RTREE_PAA, dropIndexOperator.getIndexType());
   }
 
   @Test
   public void testParseDropIndexSubMatching() {
     String sqlStr = "DROP INDEX ELB_INDEX ON root.Wind.AZQ02.Speed";
-    Operator op = generator.generate(sqlStr, ZoneId.systemDefault());
+    Operator op = LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(DropIndexOperator.class, op.getClass());
     DropIndexOperator dropIndexOperator = (DropIndexOperator) op;
     Assert.assertEquals(OperatorType.DROP_INDEX, dropIndexOperator.getType());
-    Assert.assertNotNull(dropIndexOperator.getSelectedPaths());
-    Assert.assertEquals(
-        "root.Wind.AZQ02.Speed", dropIndexOperator.getSelectedPaths().get(0).toString());
-    Assert.assertNull(dropIndexOperator.getFromOperator());
+    Assert.assertNotNull(dropIndexOperator.getPaths());
+    Assert.assertEquals("root.Wind.AZQ02.Speed", dropIndexOperator.getPaths().get(0).toString());
     Assert.assertEquals(IndexType.ELB_INDEX, dropIndexOperator.getIndexType());
   }
 
@@ -112,11 +102,13 @@ public class IndexLogicalPlanTest {
   public void testParseQueryIndexWholeMatching() {
     String sqlStr =
         "SELECT TOP 2 Glu FROM root.Ery.* WHERE Glu LIKE (0, 120, 20, 80, 120, 100, 80, 0)";
-    Operator op = generator.generate(sqlStr, ZoneId.systemDefault());
+    Operator op = LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, op.getClass());
     QueryOperator queryOperator = (QueryOperator) op;
     Assert.assertEquals(OperatorType.QUERY, queryOperator.getType());
-    Assert.assertEquals("Glu", queryOperator.getSelectedPaths().get(0).getFullPath());
+    Assert.assertEquals(
+        "Glu",
+        queryOperator.getSelectOperator().getResultColumns().get(0).getExpression().toString());
     Assert.assertEquals(
         "root.Ery.*", queryOperator.getFromOperator().getPrefixPaths().get(0).getFullPath());
     Assert.assertEquals(IndexType.RTREE_PAA, queryOperator.getIndexType());
@@ -134,11 +126,13 @@ public class IndexLogicalPlanTest {
             + "CONTAIN (15, 14, 12, 12, 12, 11) WITH TOLERANCE 1 "
             + "CONCAT (10, 20, 25, 24, 14, 8) WITH TOLERANCE 2 "
             + "CONCAT  (8, 9, 10, 14, 15, 15) WITH TOLERANCE 1";
-    Operator op = generator.generate(sqlStr, ZoneId.systemDefault());
+    Operator op = LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, op.getClass());
     QueryOperator queryOperator = (QueryOperator) op;
     Assert.assertEquals(OperatorType.QUERY, queryOperator.getType());
-    Assert.assertEquals("Speed", queryOperator.getSelectedPaths().get(0).getFullPath());
+    Assert.assertEquals(
+        "Speed",
+        queryOperator.getSelectOperator().getResultColumns().get(0).getExpression().toString());
     Assert.assertEquals(
         "root.Wind.AZQ02", queryOperator.getFromOperator().getPrefixPaths().get(0).getFullPath());
     Assert.assertEquals(IndexType.ELB_INDEX, queryOperator.getIndexType());
diff --git a/server/src/test/java/org/apache/iotdb/db/qp/logical/LogicalPlanSmallTest.java b/server/src/test/java/org/apache/iotdb/db/qp/logical/LogicalPlanSmallTest.java
index e384edd..d2e428d 100644
--- a/server/src/test/java/org/apache/iotdb/db/qp/logical/LogicalPlanSmallTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/qp/logical/LogicalPlanSmallTest.java
@@ -33,7 +33,6 @@ import org.apache.iotdb.db.service.IoTDB;
 
 import org.antlr.v4.runtime.misc.ParseCancellationException;
 import org.junit.Assert;
-import org.junit.Before;
 import org.junit.Test;
 
 import java.time.ZoneId;
@@ -41,18 +40,11 @@ import java.util.ArrayList;
 
 public class LogicalPlanSmallTest {
 
-  private LogicalGenerator logicalGenerator;
-
-  @Before
-  public void before() {
-    logicalGenerator = new LogicalGenerator();
-  }
-
   @Test
   public void testLimit() {
     String sqlStr = "select * from root.vehicle.d1 limit 10";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, operator.getClass());
     Assert.assertEquals(10, ((QueryOperator) operator).getRowLimit());
     Assert.assertEquals(0, ((QueryOperator) operator).getRowOffset());
@@ -64,7 +56,7 @@ public class LogicalPlanSmallTest {
   public void testOffset() {
     String sqlStr = "select * from root.vehicle.d1 limit 10 offset 20";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, operator.getClass());
     Assert.assertEquals(10, ((QueryOperator) operator).getRowLimit());
     Assert.assertEquals(20, ((QueryOperator) operator).getRowOffset());
@@ -76,7 +68,7 @@ public class LogicalPlanSmallTest {
   public void testSlimit() {
     String sqlStr = "select * from root.vehicle.d1 limit 10 slimit 1";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, operator.getClass());
     Assert.assertEquals(10, ((QueryOperator) operator).getRowLimit());
     Assert.assertEquals(0, ((QueryOperator) operator).getRowOffset());
@@ -89,7 +81,7 @@ public class LogicalPlanSmallTest {
     String sqlStr =
         "select * from root.vehicle.d1 where s1 < 20 and time <= now() limit 50 slimit 10 soffset 100";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, operator.getClass());
     Assert.assertEquals(50, ((QueryOperator) operator).getRowLimit());
     Assert.assertEquals(0, ((QueryOperator) operator).getRowOffset());
@@ -102,7 +94,7 @@ public class LogicalPlanSmallTest {
     String sqlStr =
         "select * from root.vehicle.d1 where s1 < 20 and timestamp <= now() limit 50 slimit 10 soffset 100";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, operator.getClass());
     Assert.assertEquals(50, ((QueryOperator) operator).getRowLimit());
     Assert.assertEquals(0, ((QueryOperator) operator).getRowOffset());
@@ -115,7 +107,7 @@ public class LogicalPlanSmallTest {
     String sqlStr =
         "select * from root.vehicle.d1 where s1 < 20 and time <= now() limit 1111111111111111111111";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     // expected to throw SQLParserException: Out of range. LIMIT <N>: N should be Int32.
   }
 
@@ -123,7 +115,7 @@ public class LogicalPlanSmallTest {
   public void testLimitNotPositive() {
     String sqlStr = "select * from root.vehicle.d1 where s1 < 20 and time <= now() limit 0";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     // expected to throw SQLParserException: LIMIT <N>: N should be greater than 0.
   }
 
@@ -133,7 +125,7 @@ public class LogicalPlanSmallTest {
         "select * from root.vehicle.d1 where s1 < 20 and time <= now() "
             + "limit 1 offset 1111111111111111111111";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     // expected to throw SQLParserException: Out of range. OFFSET <OFFSETValue>: OFFSETValue should
     // be Int32.
   }
@@ -143,7 +135,7 @@ public class LogicalPlanSmallTest {
     String sqlStr =
         "select * from root.vehicle.d1 where s1 < 20 and time <= now() limit 1 offset -1";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     // expected to throw SQLParserException: OFFSET <OFFSETValue>: OFFSETValue should >= 0.
   }
 
@@ -152,7 +144,7 @@ public class LogicalPlanSmallTest {
     String sqlStr =
         "select * from root.vehicle.d1 where s1 < 20 and time <= now() slimit 1111111111111111111111";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     // expected to throw SQLParserException: Out of range. SLIMIT <SN>: SN should be Int32.
   }
 
@@ -160,7 +152,7 @@ public class LogicalPlanSmallTest {
   public void testSlimitNotPositive() {
     String sqlStr = "select * from root.vehicle.d1 where s1 < 20 and time <= now() slimit 0";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     // expected to throw SQLParserException: SLIMIT <SN>: SN should be greater than 0.
   }
 
@@ -170,7 +162,7 @@ public class LogicalPlanSmallTest {
         "select * from root.vehicle.d1 where s1 < 20 and time <= now() "
             + "slimit 1 soffset 1111111111111111111111";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     // expected to throw SQLParserException: Out of range. SOFFSET <SOFFSETValue>: SOFFSETValue
     // should be Int32.
   }
@@ -180,7 +172,7 @@ public class LogicalPlanSmallTest {
     String sqlStr =
         "select * from root.vehicle.d1 where s1 < 20 and time <= now() slimit 1 soffset 1";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(1, ((QueryOperator) operator).getSeriesOffset());
     Assert.assertEquals(1, ((QueryOperator) operator).getSeriesLimit());
   }
@@ -190,7 +182,7 @@ public class LogicalPlanSmallTest {
     String sqlStr =
         "select s1 from root.vehicle.d1 where s1 < 20 and time <= now() slimit 2 soffset 1";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     IoTDB.metaManager.init();
     ConcatPathOptimizer concatPathOptimizer = new ConcatPathOptimizer();
     concatPathOptimizer.transform(operator, 1000);
@@ -203,7 +195,7 @@ public class LogicalPlanSmallTest {
   public void testDeleteStorageGroup() throws IllegalPathException {
     String sqlStr = "delete storage group root.vehicle.d1";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(DeleteStorageGroupOperator.class, operator.getClass());
     PartialPath path = new PartialPath("root.vehicle.d1");
     Assert.assertEquals(path, ((DeleteStorageGroupOperator) operator).getDeletePathList().get(0));
@@ -213,7 +205,7 @@ public class LogicalPlanSmallTest {
   public void testDisableAlign() {
     String sqlStr = "select * from root.vehicle disable align";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, operator.getClass());
     Assert.assertFalse(((QueryOperator) operator).isAlignByTime());
   }
@@ -222,7 +214,7 @@ public class LogicalPlanSmallTest {
   public void testNotDisableAlign() {
     String sqlStr = "select * from root.vehicle";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, operator.getClass());
     Assert.assertTrue(((QueryOperator) operator).isAlignByTime());
   }
@@ -231,23 +223,23 @@ public class LogicalPlanSmallTest {
   public void testDisableAlignConflictAlignByDevice() {
     String sqlStr = "select * from root.vehicle disable align align by device";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr, ZoneId.systemDefault());
   }
 
   @Test
   public void testChineseCharacter() throws IllegalPathException {
     String sqlStr1 = "set storage group to root.一级";
     RootOperator operator =
-        (RootOperator) logicalGenerator.generate(sqlStr1, ZoneId.systemDefault());
+        (RootOperator) LogicalGenerator.generate(sqlStr1, ZoneId.systemDefault());
     Assert.assertEquals(SetStorageGroupOperator.class, operator.getClass());
     Assert.assertEquals(new PartialPath("root.一级"), ((SetStorageGroupOperator) operator).getPath());
 
     String sqlStr2 = "select * from root.一级.设备1 limit 10 offset 20";
-    operator = (RootOperator) logicalGenerator.generate(sqlStr2, ZoneId.systemDefault());
+    operator = (RootOperator) LogicalGenerator.generate(sqlStr2, ZoneId.systemDefault());
     Assert.assertEquals(QueryOperator.class, operator.getClass());
     ArrayList<PartialPath> paths = new ArrayList<>();
     paths.add(new PartialPath("*"));
-    Assert.assertEquals(paths, ((QueryOperator) operator).getSelectedPaths());
+    Assert.assertEquals(paths, ((QueryOperator) operator).getSelectOperator().getPaths());
   }
 
   @Test
@@ -263,7 +255,7 @@ public class LogicalPlanSmallTest {
               + "FIRST_VALUE.SUM.LAST_VALUE.LAST.DISABLE.ALIGN.COMPRESSION.TIME.ATTRIBUTES.TAGS.RENAME.FULL.CLEAR.CACHE."
               + "SNAPSHOT.FOR.SCHEMA.TRACING.OFF where time>=1 and time < 3";
 
-      Operator op = logicalGenerator.generate(sql, ZoneId.systemDefault());
+      Operator op = LogicalGenerator.generate(sql, ZoneId.systemDefault());
       Assert.assertEquals(DeleteDataOperator.class, op.getClass());
     } catch (ParseCancellationException ignored) {
     }
@@ -272,47 +264,47 @@ public class LogicalPlanSmallTest {
   @Test
   public void testRangeDelete() throws IllegalPathException {
     String sql1 = "delete from root.d1.s1 where time>=1 and time < 3";
-    Operator op = logicalGenerator.generate(sql1, ZoneId.systemDefault());
+    Operator op = LogicalGenerator.generate(sql1, ZoneId.systemDefault());
     Assert.assertEquals(DeleteDataOperator.class, op.getClass());
     ArrayList<PartialPath> paths = new ArrayList<>();
     paths.add(new PartialPath("root.d1.s1"));
-    Assert.assertEquals(paths, ((DeleteDataOperator) op).getSelectedPaths());
+    Assert.assertEquals(paths, ((DeleteDataOperator) op).getPaths());
     Assert.assertEquals(1, ((DeleteDataOperator) op).getStartTime());
     Assert.assertEquals(2, ((DeleteDataOperator) op).getEndTime());
 
     String sql2 = "delete from root.d1.s1 where time>=1";
-    op = logicalGenerator.generate(sql2, ZoneId.systemDefault());
-    Assert.assertEquals(paths, ((DeleteDataOperator) op).getSelectedPaths());
+    op = LogicalGenerator.generate(sql2, ZoneId.systemDefault());
+    Assert.assertEquals(paths, ((DeleteDataOperator) op).getPaths());
     Assert.assertEquals(1, ((DeleteDataOperator) op).getStartTime());
     Assert.assertEquals(Long.MAX_VALUE, ((DeleteDataOperator) op).getEndTime());
 
     String sql3 = "delete from root.d1.s1 where time>1";
-    op = logicalGenerator.generate(sql3, ZoneId.systemDefault());
-    Assert.assertEquals(paths, ((DeleteDataOperator) op).getSelectedPaths());
+    op = LogicalGenerator.generate(sql3, ZoneId.systemDefault());
+    Assert.assertEquals(paths, ((DeleteDataOperator) op).getPaths());
     Assert.assertEquals(2, ((DeleteDataOperator) op).getStartTime());
     Assert.assertEquals(Long.MAX_VALUE, ((DeleteDataOperator) op).getEndTime());
 
     String sql4 = "delete from root.d1.s1 where time <= 1";
-    op = logicalGenerator.generate(sql4, ZoneId.systemDefault());
-    Assert.assertEquals(paths, ((DeleteDataOperator) op).getSelectedPaths());
+    op = LogicalGenerator.generate(sql4, ZoneId.systemDefault());
+    Assert.assertEquals(paths, ((DeleteDataOperator) op).getPaths());
     Assert.assertEquals(Long.MIN_VALUE, ((DeleteDataOperator) op).getStartTime());
     Assert.assertEquals(1, ((DeleteDataOperator) op).getEndTime());
 
     String sql5 = "delete from root.d1.s1 where time<1";
-    op = logicalGenerator.generate(sql5, ZoneId.systemDefault());
-    Assert.assertEquals(paths, ((DeleteDataOperator) op).getSelectedPaths());
+    op = LogicalGenerator.generate(sql5, ZoneId.systemDefault());
+    Assert.assertEquals(paths, ((DeleteDataOperator) op).getPaths());
     Assert.assertEquals(Long.MIN_VALUE, ((DeleteDataOperator) op).getStartTime());
     Assert.assertEquals(0, ((DeleteDataOperator) op).getEndTime());
 
     String sql6 = "delete from root.d1.s1 where time = 3";
-    op = logicalGenerator.generate(sql6, ZoneId.systemDefault());
-    Assert.assertEquals(paths, ((DeleteDataOperator) op).getSelectedPaths());
+    op = LogicalGenerator.generate(sql6, ZoneId.systemDefault());
+    Assert.assertEquals(paths, ((DeleteDataOperator) op).getPaths());
     Assert.assertEquals(3, ((DeleteDataOperator) op).getStartTime());
     Assert.assertEquals(3, ((DeleteDataOperator) op).getEndTime());
 
     String sql7 = "delete from root.d1.s1 where time > 5 and time >= 2";
-    op = logicalGenerator.generate(sql7, ZoneId.systemDefault());
-    Assert.assertEquals(paths, ((DeleteDataOperator) op).getSelectedPaths());
+    op = LogicalGenerator.generate(sql7, ZoneId.systemDefault());
+    Assert.assertEquals(paths, ((DeleteDataOperator) op).getPaths());
     Assert.assertEquals(6, ((DeleteDataOperator) op).getStartTime());
     Assert.assertEquals(Long.MAX_VALUE, ((DeleteDataOperator) op).getEndTime());
   }
@@ -322,7 +314,7 @@ public class LogicalPlanSmallTest {
     String sql = "delete from root.d1.s1 where time>=1 and time < 3 or time >1";
     String errorMsg = null;
     try {
-      logicalGenerator.generate(sql, ZoneId.systemDefault());
+      LogicalGenerator.generate(sql, ZoneId.systemDefault());
     } catch (SQLParserException e) {
       errorMsg = e.getMessage();
     }
@@ -334,7 +326,7 @@ public class LogicalPlanSmallTest {
     sql = "delete from root.d1.s1 where time>=1 or time < 3";
     errorMsg = null;
     try {
-      logicalGenerator.generate(sql, ZoneId.systemDefault());
+      LogicalGenerator.generate(sql, ZoneId.systemDefault());
     } catch (SQLParserException e) {
       errorMsg = e.getMessage();
     }
@@ -346,7 +338,7 @@ public class LogicalPlanSmallTest {
     String sql7 = "delete from root.d1.s1 where time = 1 and time < -1";
     errorMsg = null;
     try {
-      logicalGenerator.generate(sql7, ZoneId.systemDefault());
+      LogicalGenerator.generate(sql7, ZoneId.systemDefault());
     } catch (RuntimeException e) {
       errorMsg = e.getMessage();
     }
@@ -355,7 +347,7 @@ public class LogicalPlanSmallTest {
     sql = "delete from root.d1.s1 where time > 5 and time <= 0";
     errorMsg = null;
     try {
-      logicalGenerator.generate(sql, ZoneId.systemDefault());
+      LogicalGenerator.generate(sql, ZoneId.systemDefault());
     } catch (SQLParserException e) {
       errorMsg = e.getMessage();
     }
diff --git a/server/src/test/java/org/apache/iotdb/db/query/dataset/UDTFAlignByTimeDataSetTest.java b/server/src/test/java/org/apache/iotdb/db/query/dataset/UDTFAlignByTimeDataSetTest.java
index f879746..47e7ade 100644
--- a/server/src/test/java/org/apache/iotdb/db/query/dataset/UDTFAlignByTimeDataSetTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/query/dataset/UDTFAlignByTimeDataSetTest.java
@@ -171,7 +171,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
@@ -233,7 +233,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
@@ -280,7 +280,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
@@ -339,7 +339,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
@@ -412,7 +412,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
@@ -468,7 +468,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
@@ -533,7 +533,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
@@ -604,7 +604,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
@@ -657,7 +657,7 @@ public class UDTFAlignByTimeDataSetTest {
         Path path = queryPlan.getPaths().get(i);
         String columnName =
             path == null
-                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getContext().getColumnName()
+                ? queryPlan.getExecutorByOriginalOutputColumnIndex(i).getExpression().toString()
                 : path.getFullPath();
         originalIndex2FieldIndex.add(path2Index.get(columnName));
       }
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/iotdb/IoTDBInterpreterTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/iotdb/IoTDBInterpreterTest.java
index 1525b3c..75fcf73 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/iotdb/IoTDBInterpreterTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/iotdb/IoTDBInterpreterTest.java
@@ -209,25 +209,34 @@ public class IoTDBInterpreterTest {
     actual = interpreter.internalInterpret(wrongSql, null);
     Assert.assertNotNull(actual);
     Assert.assertEquals(Code.ERROR, actual.code());
-    Assert.assertEquals(
-        "SQLException: 401: Error occurred while parsing SQL to physical plan: line 1:13 missing ROOT at '<EOF>'",
-        actual.message().get(0).getData());
+    Assert.assertTrue(
+        actual
+            .message()
+            .get(0)
+            .getData()
+            .contains("SQLException: 401: Error occurred while parsing SQL to physical plan"));
 
     wrongSql = "select * from a";
     actual = interpreter.internalInterpret(wrongSql, null);
     Assert.assertNotNull(actual);
     Assert.assertEquals(Code.ERROR, actual.code());
-    Assert.assertEquals(
-        "SQLException: 401: Error occurred while parsing SQL to physical plan: line 1:14 mismatched input 'a' expecting {FROM, ',', '.'}",
-        actual.message().get(0).getData());
+    Assert.assertTrue(
+        actual
+            .message()
+            .get(0)
+            .getData()
+            .contains("SQLException: 401: Error occurred while parsing SQL to physical plan"));
 
     wrongSql = "select * from root a";
     actual = interpreter.internalInterpret(wrongSql, null);
     Assert.assertNotNull(actual);
     Assert.assertEquals(Code.ERROR, actual.code());
-    Assert.assertEquals(
-        "SQLException: 401: Error occurred while parsing SQL to physical plan: line 1:19 extraneous input 'a' expecting {<EOF>, ';'}",
-        actual.message().get(0).getData());
+    Assert.assertTrue(
+        actual
+            .message()
+            .get(0)
+            .getData()
+            .contains("SQLException: 401: Error occurred while parsing SQL to physical plan"));
   }
 
   @Test