You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ca...@apache.org on 2024/04/11 03:25:08 UTC

(iotdb) branch ty/TableModelGrammar updated: add SimplifyExpressions and NormalizeOrExpressionRewriter

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

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


The following commit(s) were added to refs/heads/ty/TableModelGrammar by this push:
     new 8b24378c7fb add SimplifyExpressions and NormalizeOrExpressionRewriter
8b24378c7fb is described below

commit 8b24378c7fbc94754c675573f64e940575837e20
Author: Beyyes <cg...@foxmail.com>
AuthorDate: Thu Apr 11 11:24:49 2024 +0800

    add SimplifyExpressions and NormalizeOrExpressionRewriter
---
 .../plan/planner/plan/node/PlanNode.java           |   5 +
 .../plan/planner/plan/node/PlanVisitor.java        |  11 +
 .../plan/relational/planner/LogicalPlanner.java    |  15 +-
 .../plan/relational/planner/RelationPlanner.java   |  63 ++-
 .../relational/planner/RelationalPlanVisitor.java  |  77 +++
 .../relational/planner/ir/ExpressionRewriter.java  | 142 ++++++
 .../planner/ir/ExpressionTreeRewriter.java         | 563 +++++++++++++++++++++
 .../plan/relational/planner/ir/IrUtils.java        | 261 ++++++++++
 .../plan/relational/planner/ir/IrVisitor.java      | 136 +++++
 .../planner/ir/NormalizeOrExpressionRewriter.java  | 136 +++++
 .../plan/relational/planner/node/FilterNode.java   |  35 +-
 .../relational/planner/node/MergeSortNode.java     |  58 +++
 .../relational/planner/node/TableScanNode.java     |   6 +
 .../plan/relational/planner/node/TopKNode.java     |  59 +++
 .../optimizations/RelationalPlanOptimizer.java     |  23 +
 .../planner/optimizations/SimplifyExpressions.java |  63 +++
 .../iotdb/db/relational/sql/tree/Expression.java   |   2 +-
 .../db/relational/sql/tree/GenericDataType.java    |   2 +-
 18 files changed, 1619 insertions(+), 38 deletions(-)

diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNode.java
index 877db9bf543..d63c60e9c11 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNode.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNode.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.db.queryengine.plan.planner.plan.node;
 import org.apache.iotdb.commons.exception.runtime.SerializationRunTimeException;
 import org.apache.iotdb.consensus.common.request.IConsensusRequest;
 import org.apache.iotdb.db.queryengine.plan.analyze.TypeProvider;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.RelationalPlanVisitor;
 import org.apache.iotdb.tsfile.utils.PublicBAOS;
 import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
 
@@ -127,6 +128,10 @@ public abstract class PlanNode implements IConsensusRequest {
     return visitor.visitPlan(this, context);
   }
 
+  public <R, C> R accept(RelationalPlanVisitor<R, C> visitor, C context) {
+    return visitor.visitPlan(this, context);
+  }
+
   public void serialize(ByteBuffer byteBuffer) {
     serializeAttributes(byteBuffer);
     id.serialize(byteBuffer);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java
index 608ca14df24..9e893bbc653 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java
@@ -108,6 +108,7 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNod
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsNode;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsOfOneDeviceNode;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertTabletNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.TableScanNode;
 
 @SuppressWarnings("java:S6539") // suppress "Monster class" warning
 public abstract class PlanVisitor<R, C> {
@@ -507,4 +508,14 @@ public abstract class PlanVisitor<R, C> {
   public R visitPipeOperateSchemaQueueNode(PipeOperateSchemaQueueNode node, C context) {
     return visitPlan(node, context);
   }
+
+  // =============================== Used for Relation Model ====================================
+  public R visitFilter(
+      org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode node, C context) {
+    return visitSingleChildProcess(node, context);
+  }
+
+  public R visitTableScan(TableScanNode node, C context) {
+    return visitPlan(node, context);
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/LogicalPlanner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/LogicalPlanner.java
index 1df569c2967..f11c0d86f98 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/LogicalPlanner.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/LogicalPlanner.java
@@ -24,6 +24,8 @@ import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Field;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.RelationType;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.OutputNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.optimizations.RelationalPlanOptimizer;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.optimizations.SimplifyExpressions;
 import org.apache.iotdb.db.relational.sql.tree.Query;
 import org.apache.iotdb.db.relational.sql.tree.Statement;
 import org.apache.iotdb.db.relational.sql.tree.Table;
@@ -32,6 +34,9 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import io.airlift.log.Logger;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import static java.util.Objects.requireNonNull;
 
 public class LogicalPlanner {
@@ -39,6 +44,7 @@ public class LogicalPlanner {
   private final MPPQueryContext context;
   private final SessionInfo sessionInfo;
   private final SymbolAllocator symbolAllocator = new SymbolAllocator();
+  private final List<RelationalPlanOptimizer> relationalPlanOptimizers;
   private final Metadata metadata;
   private final WarningCollector warningCollector;
 
@@ -51,10 +57,17 @@ public class LogicalPlanner {
     this.metadata = metadata;
     this.sessionInfo = requireNonNull(sessionInfo, "session is null");
     this.warningCollector = requireNonNull(warningCollector, "warningCollector is null");
+
+    this.relationalPlanOptimizers = new ArrayList<>();
+    this.relationalPlanOptimizers.add(new SimplifyExpressions());
   }
 
   public LogicalQueryPlan plan(Analysis analysis) throws IoTDBException {
-    return new LogicalQueryPlan(context, planStatement(analysis, analysis.getStatement()));
+    PlanNode planNode = planStatement(analysis, analysis.getStatement());
+
+    relationalPlanOptimizers.forEach(optimizer -> optimizer.optimize(planNode, analysis, context));
+
+    return new LogicalQueryPlan(context, planNode);
   }
 
   private PlanNode planStatement(Analysis analysis, Statement statement) throws IoTDBException {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java
index b916c753b2e..9e180d3dc4c 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java
@@ -22,11 +22,9 @@ import org.apache.iotdb.db.queryengine.plan.relational.analyzer.NodeRef;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Scope;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnHandle;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
-import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.TableScanNode;
 import org.apache.iotdb.db.relational.sql.tree.AliasedRelation;
 import org.apache.iotdb.db.relational.sql.tree.AstVisitor;
-import org.apache.iotdb.db.relational.sql.tree.Expression;
 import org.apache.iotdb.db.relational.sql.tree.Node;
 import org.apache.iotdb.db.relational.sql.tree.Query;
 import org.apache.iotdb.db.relational.sql.tree.QuerySpecification;
@@ -39,11 +37,8 @@ import com.google.common.collect.ImmutableMap;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Function;
 
 import static java.util.Objects.requireNonNull;
-import static org.apache.iotdb.db.queryengine.plan.relational.planner.PlanBuilder.newPlanBuilder;
-import static org.apache.iotdb.db.queryengine.plan.relational.planner.QueryPlanner.coerceIfNecessary;
 
 class RelationPlanner extends AstVisitor<RelationPlan, Void> {
   private final Analysis analysis;
@@ -124,36 +119,36 @@ class RelationPlanner extends AstVisitor<RelationPlan, Void> {
     return plan;
   }
 
-  private RelationPlan addRowFilters(Table node, RelationPlan plan) {
-    return addRowFilters(node, plan, Function.identity());
-  }
-
-  public RelationPlan addRowFilters(
-      Table node, RelationPlan plan, Function<Expression, Expression> predicateTransformation) {
-    List<Expression> filters = null;
-    // analysis.getRowFilters(node);
-
-    if (filters.isEmpty()) {
-      return plan;
-    }
-
-    // The fields in the access control scope has the same layout as those for the table scope
-    PlanBuilder planBuilder = newPlanBuilder(plan, analysis, session);
-    // .withScope(accessControlScope.apply(node), plan.getFieldMappings());
-
-    for (Expression filter : filters) {
-      // planBuilder = subqueryPlanner.handleSubqueries(planBuilder, filter,
-      // analysis.getSubqueries(filter));
-
-      Expression predicate = coerceIfNecessary(analysis, filter, filter);
-      predicate = predicateTransformation.apply(predicate);
-      planBuilder =
-          planBuilder.withNewRoot(
-              new FilterNode(idAllocator.genPlanNodeId(), planBuilder.getRoot(), predicate));
-    }
+  //  private RelationPlan addRowFilters(Table node, RelationPlan plan) {
+  //    return addRowFilters(node, plan, Function.identity());
+  //  }
 
-    return new RelationPlan(planBuilder.getRoot(), plan.getScope(), plan.getFieldMappings());
-  }
+  //  public RelationPlan addRowFilters(
+  //      Table node, RelationPlan plan, Function<Expression, Expression> predicateTransformation) {
+  //    List<Expression> filters = null;
+  //    // analysis.getRowFilters(node);
+  //
+  //    if (filters.isEmpty()) {
+  //      return plan;
+  //    }
+  //
+  //    // The fields in the access control scope has the same layout as those for the table scope
+  //    PlanBuilder planBuilder = newPlanBuilder(plan, analysis, session);
+  //    // .withScope(accessControlScope.apply(node), plan.getFieldMappings());
+  //
+  //    for (Expression filter : filters) {
+  //      // planBuilder = subqueryPlanner.handleSubqueries(planBuilder, filter,
+  //      // analysis.getSubqueries(filter));
+  //
+  //      Expression predicate = coerceIfNecessary(analysis, filter, filter);
+  //      predicate = predicateTransformation.apply(predicate);
+  //      planBuilder =
+  //          planBuilder.withNewRoot(
+  //              new FilterNode(idAllocator.genPlanNodeId(), planBuilder.getRoot(), predicate));
+  //    }
+  //
+  //    return new RelationPlan(planBuilder.getRoot(), plan.getScope(), plan.getFieldMappings());
+  //  }
 
   //    private RelationPlan addColumnMasks(Table table, RelationPlan plan) {
   //        Map<String, Expression> columnMasks = analysis.getColumnMasks(table);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationalPlanVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationalPlanVisitor.java
new file mode 100644
index 00000000000..e36dbddf0fa
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationalPlanVisitor.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.planner;
+
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.MultiChildProcessNode;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.SingleChildProcessNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.LimitNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.OffsetNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.OutputNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ProjectNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.SortNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.TableScanNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.TopKNode;
+
+public abstract class RelationalPlanVisitor<R, C> {
+
+  public abstract R visitPlan(PlanNode node, C context);
+
+  public R visitSingleChildProcess(SingleChildProcessNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitMultiChildProcess(MultiChildProcessNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitFilter(FilterNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitProject(ProjectNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitOutput(OutputNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitOffset(OffsetNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitLimit(LimitNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitTableScan(TableScanNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitSort(SortNode node, C context) {
+    return visitPlan(node, context);
+  }
+
+  public R visitTopK(TopKNode node, C context) {
+    return visitPlan(node, context);
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionRewriter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionRewriter.java
new file mode 100644
index 00000000000..e0d72f48928
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionRewriter.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.planner.ir;
+
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticBinaryExpression;
+import org.apache.iotdb.db.relational.sql.tree.BetweenPredicate;
+import org.apache.iotdb.db.relational.sql.tree.Cast;
+import org.apache.iotdb.db.relational.sql.tree.CoalesceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
+import org.apache.iotdb.db.relational.sql.tree.InPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.LogicalExpression;
+import org.apache.iotdb.db.relational.sql.tree.NotExpression;
+import org.apache.iotdb.db.relational.sql.tree.NullIfExpression;
+import org.apache.iotdb.db.relational.sql.tree.Row;
+import org.apache.iotdb.db.relational.sql.tree.SearchedCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SymbolReference;
+
+public class ExpressionRewriter<C> {
+  protected Expression rewriteExpression(
+      Expression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return null;
+  }
+
+  public Expression rewriteRow(Row node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  //    public Expression rewriteArithmeticUnary(ArithmeticNegation node, C context,
+  // ExpressionTreeRewriter<C> treeRewriter) {
+  //        return rewriteExpression(node, context, treeRewriter);
+  //    }
+
+  public Expression rewriteArithmeticBinary(
+      ArithmeticBinaryExpression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteComparisonExpression(
+      ComparisonExpression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteBetweenPredicate(
+      BetweenPredicate node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteLogicalExpression(
+      LogicalExpression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteNotExpression(
+      NotExpression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteIsNullPredicate(
+      IsNullPredicate node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteNullIfExpression(
+      NullIfExpression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteSearchedCaseExpression(
+      SearchedCaseExpression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteSimpleCaseExpression(
+      SimpleCaseExpression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteCoalesceExpression(
+      CoalesceExpression node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteFunctionCall(
+      FunctionCall node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  //    public Expression rewriteLambdaExpression(LambdaExpression node, C context,
+  // ExpressionTreeRewriter<C> treeRewriter) {
+  //        return rewriteExpression(node, context, treeRewriter);
+  //    }
+
+  //    public Expression rewriteBindExpression(BindExpression node, C context,
+  // ExpressionTreeRewriter<C> treeRewriter) {
+  //        return rewriteExpression(node, context, treeRewriter);
+  //    }
+
+  public Expression rewriteInPredicate(
+      InPredicate node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  //    public Expression rewriteConstant(Constant node, C context, ExpressionTreeRewriter<C>
+  // treeRewriter) {
+  //        return rewriteExpression(node, context, treeRewriter);
+  //    }
+
+  //    public Expression rewriteSubscriptExpression(SubscriptExpression node, C context,
+  // ExpressionTreeRewriter<C> treeRewriter) {
+  //        return rewriteExpression(node, context, treeRewriter);
+  //    }
+
+  public Expression rewriteCast(Cast node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
+  public Expression rewriteSymbolReference(
+      SymbolReference node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionTreeRewriter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionTreeRewriter.java
new file mode 100644
index 00000000000..b39bf4482ca
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionTreeRewriter.java
@@ -0,0 +1,563 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.queryengine.plan.relational.planner.ir;
+
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticBinaryExpression;
+import org.apache.iotdb.db.relational.sql.tree.BetweenPredicate;
+import org.apache.iotdb.db.relational.sql.tree.Cast;
+import org.apache.iotdb.db.relational.sql.tree.CoalesceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
+import org.apache.iotdb.db.relational.sql.tree.InPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.LogicalExpression;
+import org.apache.iotdb.db.relational.sql.tree.NotExpression;
+import org.apache.iotdb.db.relational.sql.tree.NullIfExpression;
+import org.apache.iotdb.db.relational.sql.tree.Row;
+import org.apache.iotdb.db.relational.sql.tree.SearchedCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SymbolReference;
+import org.apache.iotdb.db.relational.sql.tree.WhenClause;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+
+public final class ExpressionTreeRewriter<C> {
+  private final ExpressionRewriter<C> rewriter;
+  private final IrVisitor<Expression, Context<C>> visitor;
+
+  public static <T extends Expression> T rewriteWith(ExpressionRewriter<Void> rewriter, T node) {
+    return new ExpressionTreeRewriter<>(rewriter).rewrite(node, null);
+  }
+
+  public static <C, T extends Expression> T rewriteWith(
+      ExpressionRewriter<C> rewriter, T node, C context) {
+    return new ExpressionTreeRewriter<>(rewriter).rewrite(node, context);
+  }
+
+  public ExpressionTreeRewriter(ExpressionRewriter<C> rewriter) {
+    this.rewriter = rewriter;
+    this.visitor = new RewritingVisitor();
+  }
+
+  private List<Expression> rewrite(List<Expression> items, Context<C> context) {
+    ImmutableList.Builder<Expression> builder = ImmutableList.builder();
+    for (Expression expression : items) {
+      builder.add(rewrite(expression, context.get()));
+    }
+    return builder.build();
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T extends Expression> T rewrite(T node, C context) {
+    return (T) visitor.process(node, new Context<>(context, false));
+  }
+
+  /**
+   * Invoke the default rewrite logic explicitly. Specifically, it skips the invocation of the
+   * expression rewriter for the provided node.
+   */
+  @SuppressWarnings("unchecked")
+  public <T extends Expression> T defaultRewrite(T node, C context) {
+    return (T) visitor.process(node, new Context<>(context, true));
+  }
+
+  private class RewritingVisitor extends IrVisitor<Expression, Context<C>> {
+    @Override
+    protected Expression visitExpression(Expression node, Context<C> context) {
+      // RewritingVisitor must have explicit support for each expression type, with a dedicated
+      // visit method,
+      // so visitExpression() should never be called.
+      throw new UnsupportedOperationException(
+          "visit() not implemented for " + node.getClass().getName());
+    }
+
+    @Override
+    protected Expression visitRow(Row node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result = rewriter.rewriteRow(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      List<Expression> items = rewrite(node.getItems(), context);
+
+      if (!sameElements(node.getItems(), items)) {
+        return new Row(items);
+      }
+
+      return node;
+    }
+
+    //        @Override
+    //        protected Expression visitArithmeticNegation(ArithmeticNegation node, Context<C>
+    // context)
+    //        {
+    //            if (!context.isDefaultRewrite()) {
+    //                Expression result = rewriter.rewriteArithmeticUnary(node, context.get(),
+    // ExpressionTreeRewriter.this);
+    //                if (result != null) {
+    //                    return result;
+    //                }
+    //            }
+    //
+    //            Expression child = rewrite(node.getValue(), context.get());
+    //            if (child != node.getValue()) {
+    //                return new ArithmeticNegation(child);
+    //            }
+    //
+    //            return node;
+    //        }
+
+    @Override
+    public Expression visitArithmeticBinary(ArithmeticBinaryExpression node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteArithmeticBinary(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression left = rewrite(node.getLeft(), context.get());
+      Expression right = rewrite(node.getRight(), context.get());
+
+      if (left != node.getLeft() || right != node.getRight()) {
+        // node.getLocation().get() may be null
+        return new ArithmeticBinaryExpression(
+            node.getLocation().get(), node.getOperator(), left, right);
+      }
+
+      return node;
+    }
+
+    //        @Override
+    //        protected Expression visitSubscriptExpression(SubscriptExpression node, Context<C>
+    // context)
+    //        {
+    //            if (!context.isDefaultRewrite()) {
+    //                Expression result = rewriter.rewriteSubscriptExpression(node, context.get(),
+    // ExpressionTreeRewriter.this);
+    //                if (result != null) {
+    //                    return result;
+    //                }
+    //            }
+    //
+    //            Expression base = rewrite(node.getBase(), context.get());
+    //            Expression index = rewrite(node.getIndex(), context.get());
+    //
+    //            if (base != node.getBase() || index != node.getIndex()) {
+    //                return new SubscriptExpression(base, index);
+    //            }
+    //
+    //            return node;
+    //        }
+
+    @Override
+    public Expression visitComparisonExpression(ComparisonExpression node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteComparisonExpression(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression left = rewrite(node.getLeft(), context.get());
+      Expression right = rewrite(node.getRight(), context.get());
+
+      if (left != node.getLeft() || right != node.getRight()) {
+        return new ComparisonExpression(node.getOperator(), left, right);
+      }
+
+      return node;
+    }
+
+    @Override
+    protected Expression visitBetweenPredicate(BetweenPredicate node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteBetweenPredicate(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression value = rewrite(node.getValue(), context.get());
+      Expression min = rewrite(node.getMin(), context.get());
+      Expression max = rewrite(node.getMax(), context.get());
+
+      if (value != node.getValue() || min != node.getMin() || max != node.getMax()) {
+        return new BetweenPredicate(value, min, max);
+      }
+
+      return node;
+    }
+
+    @Override
+    public Expression visitLogicalExpression(LogicalExpression node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteLogicalExpression(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      List<Expression> terms = rewrite(node.getTerms(), context);
+      if (!sameElements(node.getTerms(), terms)) {
+        return new LogicalExpression(node.getOperator(), terms);
+      }
+
+      return node;
+    }
+
+    @Override
+    public Expression visitNotExpression(NotExpression node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteNotExpression(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression value = rewrite(node.getValue(), context.get());
+
+      if (value != node.getValue()) {
+        return new NotExpression(value);
+      }
+
+      return node;
+    }
+
+    @Override
+    protected Expression visitIsNullPredicate(IsNullPredicate node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteIsNullPredicate(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression value = rewrite(node.getValue(), context.get());
+
+      if (value != node.getValue()) {
+        return new IsNullPredicate(value);
+      }
+
+      return node;
+    }
+
+    @Override
+    protected Expression visitNullIfExpression(NullIfExpression node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteNullIfExpression(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression first = rewrite(node.getFirst(), context.get());
+      Expression second = rewrite(node.getSecond(), context.get());
+
+      if (first != node.getFirst() || second != node.getSecond()) {
+        return new NullIfExpression(first, second);
+      }
+
+      return node;
+    }
+
+    @Override
+    protected Expression visitSearchedCaseExpression(
+        SearchedCaseExpression node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteSearchedCaseExpression(
+                node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      ImmutableList.Builder<WhenClause> builder = ImmutableList.builder();
+      for (WhenClause expression : node.getWhenClauses()) {
+        builder.add(rewriteWhenClause(expression, context));
+      }
+
+      Optional<Expression> defaultValue =
+          node.getDefaultValue().map(value -> rewrite(value, context.get()));
+
+      if (!sameElements(node.getDefaultValue(), defaultValue)
+          || !sameElements(node.getWhenClauses(), builder.build())) {
+        // defaultValue.get() may be null
+        return new SearchedCaseExpression(builder.build(), defaultValue.get());
+      }
+
+      return node;
+    }
+
+    @Override
+    protected Expression visitSimpleCaseExpression(SimpleCaseExpression node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteSimpleCaseExpression(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression operand = rewrite(node.getOperand(), context.get());
+
+      ImmutableList.Builder<WhenClause> builder = ImmutableList.builder();
+      for (WhenClause expression : node.getWhenClauses()) {
+        builder.add(rewriteWhenClause(expression, context));
+      }
+
+      Optional<Expression> defaultValue =
+          node.getDefaultValue().map(value -> rewrite(value, context.get()));
+
+      if (operand != node.getOperand()
+          || !sameElements(node.getDefaultValue(), defaultValue)
+          || !sameElements(node.getWhenClauses(), builder.build())) {
+        // defaultValue.get() may be null
+        return new SimpleCaseExpression(operand, builder.build(), defaultValue.get());
+      }
+
+      return node;
+    }
+
+    protected WhenClause rewriteWhenClause(WhenClause node, Context<C> context) {
+      Expression operand = rewrite(node.getOperand(), context.get());
+      Expression result = rewrite(node.getResult(), context.get());
+
+      if (operand != node.getOperand() || result != node.getResult()) {
+        return new WhenClause(operand, result);
+      }
+      return node;
+    }
+
+    @Override
+    protected Expression visitCoalesceExpression(CoalesceExpression node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteCoalesceExpression(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      List<Expression> operands = rewrite(node.getOperands(), context);
+
+      if (!sameElements(node.getOperands(), operands)) {
+        return new CoalesceExpression(operands);
+      }
+
+      return node;
+    }
+
+    @Override
+    public Expression visitFunctionCall(FunctionCall node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteFunctionCall(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      List<Expression> arguments = rewrite(node.getArguments(), context);
+
+      if (!sameElements(node.getArguments(), arguments)) {
+        return new FunctionCall(node.getName(), arguments);
+      }
+      return node;
+    }
+
+    //        @Override
+    //        protected Expression visitLambdaExpression(LambdaExpression node, Context<C> context)
+    //        {
+    //            if (!context.isDefaultRewrite()) {
+    //                Expression result = rewriter.rewriteLambdaExpression(node, context.get(),
+    // ExpressionTreeRewriter.this);
+    //                if (result != null) {
+    //                    return result;
+    //                }
+    //            }
+    //
+    //            List<String> arguments = node.getArguments().stream()
+    //                    .map(SymbolReference::new)
+    //                    .map(expression -> rewrite(expression, context.get()))
+    //                    .map(SymbolReference::getName)
+    //                    .collect(toImmutableList());
+    //
+    //            Expression body = rewrite(node.getBody(), context.get());
+    //            if (body != node.getBody()) {
+    //                return new LambdaExpression(arguments, body);
+    //            }
+    //
+    //            return node;
+    //        }
+
+    //        @Override
+    //        protected Expression visitBindExpression(BindExpression node, Context<C> context)
+    //        {
+    //            if (!context.isDefaultRewrite()) {
+    //                Expression result = rewriter.rewriteBindExpression(node, context.get(),
+    // ExpressionTreeRewriter.this);
+    //                if (result != null) {
+    //                    return result;
+    //                }
+    //            }
+    //
+    //            List<Expression> values = node.getValues().stream()
+    //                    .map(value -> rewrite(value, context.get()))
+    //                    .collect(toImmutableList());
+    //            Expression function = rewrite(node.getFunction(), context.get());
+    //
+    //            if (!sameElements(values, node.getValues()) || (function != node.getFunction())) {
+    //                return new BindExpression(values, function);
+    //            }
+    //            return node;
+    //        }
+
+    @Override
+    public Expression visitInPredicate(InPredicate node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteInPredicate(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression value = rewrite(node.getValue(), context.get());
+      //            List<Expression> values = node.getValueList().stream()
+      //                    .map(entry -> rewrite(entry, context.get()))
+      //                    .collect(toImmutableList());
+
+      if (node.getValue() != value
+          || !sameElements(Optional.of(value), Optional.of(node.getValue()))) {
+        return new InPredicate(value, value);
+      }
+
+      return node;
+    }
+
+    //        @Override
+    //        public Expression visitConstant(Constant node, Context<C> context)
+    //        {
+    //            if (!context.isDefaultRewrite()) {
+    //                Expression result = rewriter.rewriteConstant(node, context.get(),
+    // ExpressionTreeRewriter.this);
+    //                if (result != null) {
+    //                    return result;
+    //                }
+    //            }
+    //
+    //            return node;
+    //        }
+
+    @Override
+    public Expression visitCast(Cast node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result = rewriter.rewriteCast(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression expression = rewrite(node.getExpression(), context.get());
+
+      if (node.getExpression() != expression) {
+        return new Cast(expression, node.getType(), node.isSafe());
+      }
+
+      return node;
+    }
+
+    @Override
+    protected Expression visitSymbolReference(SymbolReference node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteSymbolReference(node, context.get(), ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      return node;
+    }
+  }
+
+  public static class Context<C> {
+    private final boolean defaultRewrite;
+    private final C context;
+
+    private Context(C context, boolean defaultRewrite) {
+      this.context = context;
+      this.defaultRewrite = defaultRewrite;
+    }
+
+    public C get() {
+      return context;
+    }
+
+    public boolean isDefaultRewrite() {
+      return defaultRewrite;
+    }
+  }
+
+  private static <T> boolean sameElements(Optional<T> a, Optional<T> b) {
+    if (!a.isPresent() && !b.isPresent()) {
+      return true;
+    }
+    if (a.isPresent() != b.isPresent()) {
+      return false;
+    }
+
+    return a.get() == b.get();
+  }
+
+  @SuppressWarnings("ObjectEquality")
+  private static <T> boolean sameElements(Iterable<? extends T> a, Iterable<? extends T> b) {
+    if (Iterables.size(a) != Iterables.size(b)) {
+      return false;
+    }
+
+    Iterator<? extends T> first = a.iterator();
+    Iterator<? extends T> second = b.iterator();
+
+    while (first.hasNext() && second.hasNext()) {
+      if (first.next() != second.next()) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/IrUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/IrUtils.java
new file mode 100644
index 00000000000..0bbd5c03d75
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/IrUtils.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.planner.ir;
+
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.LogicalExpression;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
+import static org.apache.iotdb.db.relational.sql.tree.BooleanLiteral.FALSE_LITERAL;
+import static org.apache.iotdb.db.relational.sql.tree.BooleanLiteral.TRUE_LITERAL;
+
+public final class IrUtils {
+  private IrUtils() {}
+
+  public static List<Expression> extractConjuncts(Expression expression) {
+    return extractPredicates(LogicalExpression.Operator.AND, expression);
+  }
+
+  public static List<Expression> extractDisjuncts(Expression expression) {
+    return extractPredicates(LogicalExpression.Operator.OR, expression);
+  }
+
+  public static List<Expression> extractPredicates(LogicalExpression expression) {
+    return extractPredicates(expression.getOperator(), expression);
+  }
+
+  public static List<Expression> extractPredicates(
+      LogicalExpression.Operator operator, Expression expression) {
+    ImmutableList.Builder<Expression> resultBuilder = ImmutableList.builder();
+    extractPredicates(operator, expression, resultBuilder);
+    return resultBuilder.build();
+  }
+
+  private static void extractPredicates(
+      LogicalExpression.Operator operator,
+      Expression expression,
+      ImmutableList.Builder<Expression> resultBuilder) {
+    if (expression instanceof LogicalExpression
+        && ((LogicalExpression) expression).getOperator() == operator) {
+      for (Expression term : ((LogicalExpression) expression).getTerms()) {
+        extractPredicates(operator, term, resultBuilder);
+      }
+    } else {
+      resultBuilder.add(expression);
+    }
+  }
+
+  public static Expression and(Expression... expressions) {
+    return and(Arrays.asList(expressions));
+  }
+
+  public static Expression and(Collection<Expression> expressions) {
+    return logicalExpression(LogicalExpression.Operator.AND, expressions);
+  }
+
+  public static Expression or(Expression... expressions) {
+    return or(Arrays.asList(expressions));
+  }
+
+  public static Expression or(Collection<Expression> expressions) {
+    return logicalExpression(LogicalExpression.Operator.OR, expressions);
+  }
+
+  public static Expression logicalExpression(
+      LogicalExpression.Operator operator, Collection<Expression> expressions) {
+    requireNonNull(operator, "operator is null");
+    requireNonNull(expressions, "expressions is null");
+
+    if (expressions.isEmpty()) {
+      switch (operator) {
+        case AND:
+          return TRUE_LITERAL;
+        case OR:
+          return FALSE_LITERAL;
+      }
+      throw new IllegalArgumentException("Unsupported LogicalExpression operator");
+    }
+
+    if (expressions.size() == 1) {
+      return Iterables.getOnlyElement(expressions);
+    }
+
+    return new LogicalExpression(operator, ImmutableList.copyOf(expressions));
+  }
+
+  public static Expression combinePredicates(
+      LogicalExpression.Operator operator, Collection<Expression> expressions) {
+    if (operator == LogicalExpression.Operator.AND) {
+      return combineConjuncts(expressions);
+    }
+
+    return combineDisjuncts(expressions);
+  }
+
+  public static Expression combineConjuncts(Expression... expressions) {
+    return combineConjuncts(Arrays.asList(expressions));
+  }
+
+  public static Expression combineConjuncts(Collection<Expression> expressions) {
+    requireNonNull(expressions, "expressions is null");
+
+    List<Expression> conjuncts =
+        expressions.stream()
+            .flatMap(e -> extractConjuncts(e).stream())
+            .filter(e -> !e.equals(TRUE_LITERAL))
+            .collect(toList());
+
+    // TODO add removeDuplicates impl
+    // conjuncts = removeDuplicates(conjuncts);
+
+    if (conjuncts.contains(FALSE_LITERAL)) {
+      return FALSE_LITERAL;
+    }
+
+    return and(conjuncts);
+  }
+
+  public static Expression combineConjunctsWithDuplicates(Collection<Expression> expressions) {
+    requireNonNull(expressions, "expressions is null");
+
+    List<Expression> conjuncts =
+        expressions.stream()
+            .flatMap(e -> extractConjuncts(e).stream())
+            .filter(e -> !e.equals(TRUE_LITERAL))
+            .collect(toList());
+
+    if (conjuncts.contains(FALSE_LITERAL)) {
+      return FALSE_LITERAL;
+    }
+
+    return and(conjuncts);
+  }
+
+  public static Expression combineDisjuncts(Expression... expressions) {
+    return combineDisjuncts(Arrays.asList(expressions));
+  }
+
+  public static Expression combineDisjuncts(Collection<Expression> expressions) {
+    return combineDisjunctsWithDefault(expressions, FALSE_LITERAL);
+  }
+
+  public static Expression combineDisjunctsWithDefault(
+      Collection<Expression> expressions, Expression emptyDefault) {
+    requireNonNull(expressions, "expressions is null");
+
+    List<Expression> disjuncts =
+        expressions.stream()
+            .flatMap(e -> extractDisjuncts(e).stream())
+            .filter(e -> !e.equals(FALSE_LITERAL))
+            .collect(toList());
+
+    // TODO add removeDuplicates impl
+    // disjuncts = removeDuplicates(disjuncts);
+
+    if (disjuncts.contains(TRUE_LITERAL)) {
+      return TRUE_LITERAL;
+    }
+
+    return disjuncts.isEmpty() ? emptyDefault : or(disjuncts);
+  }
+
+  //    public static Expression filterDeterministicConjuncts(Metadata metadata, Expression
+  // expression)
+  //    {
+  //        return filterConjuncts(expression, expression1 ->
+  // DeterminismEvaluator.isDeterministic(expression1));
+  //    }
+  //
+  //    public static Expression filterNonDeterministicConjuncts(Metadata metadata, Expression
+  // expression)
+  //    {
+  //        return filterConjuncts(expression, not(testExpression ->
+  // DeterminismEvaluator.isDeterministic(testExpression)));
+  //    }
+
+  public static Expression filterConjuncts(Expression expression, Predicate<Expression> predicate) {
+    List<Expression> conjuncts =
+        extractConjuncts(expression).stream().filter(predicate).collect(toList());
+
+    return combineConjuncts(conjuncts);
+  }
+
+  //    @SafeVarargs
+  //    public static Function<Expression, Expression> expressionOrNullSymbols(Predicate<Symbol>...
+  // nullSymbolScopes)
+  //    {
+  //        return expression -> {
+  //            ImmutableList.Builder<Expression> resultDisjunct = ImmutableList.builder();
+  //            resultDisjunct.add(expression);
+  //
+  //            for (Predicate<Symbol> nullSymbolScope : nullSymbolScopes) {
+  //                List<Symbol> symbols = SymbolsExtractor.extractUnique(expression).stream()
+  //                        .filter(nullSymbolScope)
+  //                        .collect(toImmutableList());
+  //
+  //                if (symbols.isEmpty()) {
+  //                    continue;
+  //                }
+  //
+  //                ImmutableList.Builder<Expression> nullConjuncts = ImmutableList.builder();
+  //                for (Symbol symbol : symbols) {
+  //                    nullConjuncts.add(new IsNullPredicate(symbol.toSymbolReference()));
+  //                }
+  //
+  //                resultDisjunct.add(and(nullConjuncts.build()));
+  //            }
+  //
+  //            return or(resultDisjunct.build());
+  //        };
+  //    }
+
+  /**
+   * Removes duplicate deterministic expressions. Preserves the relative order of the expressions in
+   * the list.
+   */
+  //    private static List<Expression> removeDuplicates(List<Expression> expressions)
+  //    {
+  //        Set<Expression> seen = new HashSet<>();
+  //
+  //        ImmutableList.Builder<Expression> result = ImmutableList.builder();
+  //        for (Expression expression : expressions) {
+  //            if (!DeterminismEvaluator.isDeterministic(expression)) {
+  //                result.add(expression);
+  //            }
+  //            else if (!seen.contains(expression)) {
+  //                result.add(expression);
+  //                seen.add(expression);
+  //            }
+  //        }
+  //
+  //        return result.build();
+  //    }
+
+  //    public static Stream<Expression> preOrder(Expression node)
+  //    {
+  //        return stream(
+  //                Traverser.forTree((SuccessorsFunction<Expression>) Expression::getChildren)
+  //                        .depthFirstPreOrder(requireNonNull(node, "node is null")));
+  //    }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/IrVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/IrVisitor.java
new file mode 100644
index 00000000000..96bc02b17e0
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/IrVisitor.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.queryengine.plan.relational.planner.ir;
+
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticBinaryExpression;
+import org.apache.iotdb.db.relational.sql.tree.AstVisitor;
+import org.apache.iotdb.db.relational.sql.tree.BetweenPredicate;
+import org.apache.iotdb.db.relational.sql.tree.Cast;
+import org.apache.iotdb.db.relational.sql.tree.CoalesceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
+import org.apache.iotdb.db.relational.sql.tree.InPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.LogicalExpression;
+import org.apache.iotdb.db.relational.sql.tree.NotExpression;
+import org.apache.iotdb.db.relational.sql.tree.NullIfExpression;
+import org.apache.iotdb.db.relational.sql.tree.Row;
+import org.apache.iotdb.db.relational.sql.tree.SearchedCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SymbolReference;
+
+public abstract class IrVisitor<R, C> extends AstVisitor<R, C> {
+  public R process(Expression node) {
+    return process(node, null);
+  }
+
+  public R process(Expression node, C context) {
+    return node.accept(this, context);
+  }
+
+  protected R visitExpression(Expression node, C context) {
+    return null;
+  }
+
+  protected R visitArithmeticBinary(ArithmeticBinaryExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitBetweenPredicate(BetweenPredicate node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitCoalesceExpression(CoalesceExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitComparisonExpression(ComparisonExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
+  //    protected R visitConstant(Constant node, C context)
+  //    {
+  //        return visitExpression(node, context);
+  //    }
+
+  protected R visitInPredicate(InPredicate node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitFunctionCall(FunctionCall node, C context) {
+    return visitExpression(node, context);
+  }
+
+  //    protected R visitLambdaExpression(LambdaExpression node, C context)
+  //    {
+  //        return visitExpression(node, context);
+  //    }
+
+  protected R visitSimpleCaseExpression(SimpleCaseExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitNullIfExpression(NullIfExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
+  //    protected R visitArithmeticNegation(ArithmeticNegation node, C context)
+  //    {
+  //        return visitExpression(node, context);
+  //    }
+
+  protected R visitNotExpression(NotExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitSearchedCaseExpression(SearchedCaseExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitIsNullPredicate(IsNullPredicate node, C context) {
+    return visitExpression(node, context);
+  }
+
+  //    protected R visitSubscriptExpression(SubscriptExpression node, C context)
+  //    {
+  //        return visitExpression(node, context);
+  //    }
+
+  protected R visitLogicalExpression(LogicalExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitRow(Row node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitCast(Cast node, C context) {
+    return visitExpression(node, context);
+  }
+
+  protected R visitSymbolReference(SymbolReference node, C context) {
+    return visitExpression(node, context);
+  }
+
+  //    protected R visitBindExpression(BindExpression node, C context)
+  //    {
+  //        return visitExpression(node, context);
+  //    }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/NormalizeOrExpressionRewriter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/NormalizeOrExpressionRewriter.java
new file mode 100644
index 00000000000..314da530323
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/NormalizeOrExpressionRewriter.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.planner.ir;
+
+import org.apache.iotdb.db.relational.sql.tree.ComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.InPredicate;
+import org.apache.iotdb.db.relational.sql.tree.LogicalExpression;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.and;
+import static org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.or;
+import static org.apache.iotdb.db.relational.sql.tree.ComparisonExpression.Operator.EQUAL;
+import static org.apache.iotdb.db.relational.sql.tree.LogicalExpression.Operator.AND;
+
+/** Transfer to conjunction normal form. */
+public final class NormalizeOrExpressionRewriter {
+  public static Expression normalizeOrExpression(Expression expression) {
+    return ExpressionTreeRewriter.rewriteWith(new Visitor(), expression);
+  }
+
+  private NormalizeOrExpressionRewriter() {}
+
+  private static class Visitor extends ExpressionRewriter<Void> {
+    @Override
+    public Expression rewriteLogicalExpression(
+        LogicalExpression node, Void context, ExpressionTreeRewriter<Void> treeRewriter) {
+      List<Expression> terms =
+          node.getTerms().stream()
+              .map(expression -> treeRewriter.rewrite(expression, context))
+              .collect(toImmutableList());
+
+      if (node.getOperator() == AND) {
+        return and(terms);
+      }
+
+      ImmutableList.Builder<InPredicate> inPredicateBuilder = ImmutableList.builder();
+      ImmutableSet.Builder<Expression> expressionToSkipBuilder = ImmutableSet.builder();
+      ImmutableList.Builder<Expression> othersExpressionBuilder = ImmutableList.builder();
+      groupComparisonAndInPredicate(terms)
+          .forEach(
+              (expression, values) -> {
+                if (values.size() > 1) {
+                  // TODO mergeToInListExpression may have more than one value
+                  inPredicateBuilder.add(
+                      new InPredicate(expression, mergeToInListExpression(values).get(0)));
+                  expressionToSkipBuilder.add(expression);
+                }
+              });
+
+      Set<Expression> expressionToSkip = expressionToSkipBuilder.build();
+      for (Expression expression : terms) {
+        if (expression instanceof ComparisonExpression
+            && ((ComparisonExpression) expression).getOperator() == EQUAL) {
+
+          if (!expressionToSkip.contains(((ComparisonExpression) expression).getLeft())) {
+            othersExpressionBuilder.add(expression);
+          }
+        } else if (expression instanceof InPredicate) {
+          if (!expressionToSkip.contains(((InPredicate) expression).getValue())) {
+            othersExpressionBuilder.add(expression);
+          }
+        } else {
+          othersExpressionBuilder.add(expression);
+        }
+      }
+
+      return or(
+          ImmutableList.<Expression>builder()
+              .addAll(othersExpressionBuilder.build())
+              .addAll(inPredicateBuilder.build())
+              .build());
+    }
+
+    private List<Expression> mergeToInListExpression(Collection<Expression> expressions) {
+      LinkedHashSet<Expression> expressionValues = new LinkedHashSet<>();
+      for (Expression expression : expressions) {
+        if (expression instanceof ComparisonExpression
+            && ((ComparisonExpression) expression).getOperator() == EQUAL) {
+          expressionValues.add(((ComparisonExpression) expression).getRight());
+        } else if (expression instanceof InPredicate) {
+          // TODO inPredicate has getValues method
+          expressionValues.add(((InPredicate) expression).getValue());
+        } else {
+          throw new IllegalStateException("Unexpected expression: " + expression);
+        }
+      }
+
+      return ImmutableList.copyOf(expressionValues);
+    }
+
+    private Map<Expression, Collection<Expression>> groupComparisonAndInPredicate(
+        List<Expression> terms) {
+      ImmutableMultimap.Builder<Expression, Expression> expressionBuilder =
+          ImmutableMultimap.builder();
+      for (Expression expression : terms) {
+        if (expression instanceof ComparisonExpression
+            && ((ComparisonExpression) expression).getOperator() == EQUAL) {
+          expressionBuilder.put(((ComparisonExpression) expression).getLeft(), expression);
+        } else if (expression instanceof InPredicate) {
+          InPredicate inPredicate = (InPredicate) expression;
+          expressionBuilder.put(inPredicate.getValue(), inPredicate);
+        }
+      }
+
+      return expressionBuilder.build().asMap();
+    }
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/FilterNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/FilterNode.java
index e56946216fc..3c49baa4880 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/FilterNode.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/FilterNode.java
@@ -1,7 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.apache.iotdb.db.queryengine.plan.relational.planner.node;
 
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.SingleChildProcessNode;
 import org.apache.iotdb.db.relational.sql.tree.Expression;
 
@@ -11,13 +31,18 @@ import java.nio.ByteBuffer;
 import java.util.List;
 
 public class FilterNode extends SingleChildProcessNode {
-  private final Expression predicate;
+  private Expression predicate;
 
   public FilterNode(PlanNodeId id, PlanNode child, Expression predicate) {
     super(id, child);
     this.predicate = predicate;
   }
 
+  @Override
+  public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+    return visitor.visitFilter(this, context);
+  }
+
   @Override
   public PlanNode clone() {
     return null;
@@ -33,4 +58,12 @@ public class FilterNode extends SingleChildProcessNode {
 
   @Override
   protected void serializeAttributes(DataOutputStream stream) throws IOException {}
+
+  public Expression getPredicate() {
+    return predicate;
+  }
+
+  public void setPredicate(Expression predicate) {
+    this.predicate = predicate;
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/MergeSortNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/MergeSortNode.java
new file mode 100644
index 00000000000..58a94fc3b9c
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/MergeSortNode.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.queryengine.plan.relational.planner.node;
+
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.MultiChildProcessNode;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.OrderByParameter;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class MergeSortNode extends MultiChildProcessNode {
+  private final OrderByParameter mergeOrderParameter;
+
+  private final List<String> outputColumns;
+
+  public MergeSortNode(
+      PlanNodeId id, OrderByParameter mergeOrderParameter, List<String> outputColumns) {
+    super(id);
+    this.mergeOrderParameter = mergeOrderParameter;
+    this.outputColumns = outputColumns;
+  }
+
+  @Override
+  public PlanNode clone() {
+    return null;
+  }
+
+  @Override
+  public List<String> getOutputColumnNames() {
+    return null;
+  }
+
+  @Override
+  protected void serializeAttributes(ByteBuffer byteBuffer) {}
+
+  @Override
+  protected void serializeAttributes(DataOutputStream stream) throws IOException {}
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/TableScanNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/TableScanNode.java
index 860bbbab443..1b7c2476ba3 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/TableScanNode.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/TableScanNode.java
@@ -2,6 +2,7 @@ package org.apache.iotdb.db.queryengine.plan.relational.planner.node;
 
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnHandle;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.Symbol;
@@ -28,6 +29,11 @@ public class TableScanNode extends PlanNode {
     this.assignments = assignments;
   }
 
+  @Override
+  public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+    return visitor.visitTableScan(this, context);
+  }
+
   @Override
   public List<PlanNode> getChildren() {
     return null;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/TopKNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/TopKNode.java
new file mode 100644
index 00000000000..d356d25f1d0
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/TopKNode.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.planner.node;
+
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.MultiChildProcessNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.OrderingScheme;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class TopKNode extends MultiChildProcessNode {
+
+  private final OrderingScheme orderingScheme;
+
+  private final long count;
+
+  public TopKNode(PlanNodeId id, OrderingScheme scheme, long count) {
+    super(id);
+    this.orderingScheme = scheme;
+    this.count = count;
+  }
+
+  @Override
+  public PlanNode clone() {
+    return null;
+  }
+
+  @Override
+  public List<String> getOutputColumnNames() {
+    return null;
+  }
+
+  @Override
+  protected void serializeAttributes(ByteBuffer byteBuffer) {}
+
+  @Override
+  protected void serializeAttributes(DataOutputStream stream) throws IOException {}
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/RelationalPlanOptimizer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/RelationalPlanOptimizer.java
new file mode 100644
index 00000000000..df7cb945167
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/RelationalPlanOptimizer.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.planner.optimizations;
+
+import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
+import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis;
+
+public interface RelationalPlanOptimizer {
+  PlanNode optimize(PlanNode planNode, Analysis analysis, MPPQueryContext context);
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/SimplifyExpressions.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/SimplifyExpressions.java
new file mode 100644
index 00000000000..e9932be033f
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/SimplifyExpressions.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.planner.optimizations;
+
+import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor;
+import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.TableScanNode;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+
+import static org.apache.iotdb.db.queryengine.plan.relational.planner.ir.NormalizeOrExpressionRewriter.normalizeOrExpression;
+
+public class SimplifyExpressions implements RelationalPlanOptimizer {
+
+  @Override
+  public PlanNode optimize(PlanNode planNode, Analysis analysis, MPPQueryContext context) {
+    // TODO add query statement pruning
+    return planNode.accept(new Rewriter(), new RewriterContext());
+  }
+
+  private static class Rewriter extends PlanVisitor<PlanNode, RewriterContext> {
+
+    @Override
+    public PlanNode visitPlan(PlanNode node, RewriterContext context) {
+      // PlanNode newNode = node.clone();
+      if (node.getChildren() == null) {
+        System.out.println("aa");
+      }
+      for (PlanNode child : node.getChildren()) {
+        child.accept(this, context);
+      }
+      return node;
+    }
+
+    @Override
+    public PlanNode visitFilter(FilterNode node, RewriterContext context) {
+      Expression newPredicate = normalizeOrExpression(node.getPredicate());
+      node.setPredicate(newPredicate);
+      return node;
+    }
+
+    @Override
+    public PlanNode visitTableScan(TableScanNode node, RewriterContext context) {
+      return node;
+    }
+  }
+
+  private static class RewriterContext {}
+}
diff --git a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/Expression.java b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/Expression.java
index 98aee549f2e..29ab6b83674 100644
--- a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/Expression.java
+++ b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/Expression.java
@@ -31,7 +31,7 @@ public abstract class Expression extends Node {
 
   /** Accessible for {@link AstVisitor}, use {@link AstVisitor#process(Node, Object)} instead. */
   @Override
-  protected <R, C> R accept(AstVisitor<R, C> visitor, C context) {
+  public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
     return visitor.visitExpression(this, context);
   }
 
diff --git a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/GenericDataType.java b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/GenericDataType.java
index 99059ba7c5f..f5c39eae80e 100644
--- a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/GenericDataType.java
+++ b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/GenericDataType.java
@@ -60,7 +60,7 @@ public final class GenericDataType extends DataType {
   }
 
   @Override
-  protected <R, C> R accept(AstVisitor<R, C> visitor, C context) {
+  public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
     return visitor.visitGenericDataType(this, context);
   }