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/01 11:29:39 UTC

(iotdb) branch beyyes/TableModelGrammar updated: add logical planner node impl

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

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


The following commit(s) were added to refs/heads/beyyes/TableModelGrammar by this push:
     new 73efd98bca5 add logical planner node impl
73efd98bca5 is described below

commit 73efd98bca5e9489516c55c0b905f4bece4e75a1
Author: Beyyes <cg...@foxmail.com>
AuthorDate: Mon Apr 1 19:29:15 2024 +0800

    add logical planner node impl
---
 iotdb-core/datanode/pom.xml                        |    2 +-
 .../relational/cost/CachingTableStatsProvider.java |   46 +-
 .../plan/relational/cost/ColumnStatistics.java     |  242 ++--
 .../plan/relational/cost/DoubleRange.java          |  138 +--
 .../queryengine/plan/relational/cost/Estimate.java |  106 +-
 .../plan/relational/cost/StatsUtil.java            |   35 +-
 .../plan/relational/cost/TableStatistics.java      |  158 ++-
 .../plan/relational/cost/TableStatsProvider.java   |    2 +-
 .../plan/relational/planner/Assignments.java       |  351 +++---
 .../plan/relational/planner/LogicalPlanner.java    |  173 ++-
 .../plan/relational/planner/OrderingScheme.java    |   89 ++
 .../relational/planner/OrderingTranslator.java     |   34 +
 .../plan/relational/planner/PlanBuilder.java       |  161 +--
 .../plan/relational/planner/QueryPlanner.java      | 1271 +++++---------------
 .../plan/relational/planner/RelationPlan.java      |   83 +-
 .../plan/relational/planner/RelationPlanner.java   |  405 +++----
 .../plan/relational/planner/SortOrder.java         |   44 +
 .../plan/relational/planner/SymbolAllocator.java   |  169 ++-
 .../plan/relational/planner/node/FilterNode.java   |   39 +-
 .../plan/relational/planner/node/LimitNode.java    |   43 +
 .../plan/relational/planner/node/OffsetNode.java   |   35 +
 .../plan/relational/planner/node/OutputNode.java   |   59 +-
 .../plan/relational/planner/node/ProjectNode.java  |   38 +-
 .../plan/relational/planner/node/SortNode.java     |   38 +
 .../relational/planner/node/TableScanNode.java     |   82 +-
 .../plan/relational/analyzer/AnalyzerTest.java     |    4 +-
 .../plan/relational/analyzer/TestMatadata.java     |   93 +-
 27 files changed, 1638 insertions(+), 2302 deletions(-)

diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml
index 32b150ee56f..3f6f142ba83 100644
--- a/iotdb-core/datanode/pom.xml
+++ b/iotdb-core/datanode/pom.xml
@@ -528,7 +528,7 @@
                         <usedDependency>org.glassfish.jersey.inject:jersey-hk2</usedDependency>
                     </usedDependencies>
                 </configuration>
-            </plugin>
+            </plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>8</source><target>8</target></configuration></plugin>
         </plugins>
     </build>
     <profiles>
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/CachingTableStatsProvider.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/CachingTableStatsProvider.java
index 0706a8ac3e4..d3c9da443ab 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/CachingTableStatsProvider.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/CachingTableStatsProvider.java
@@ -13,43 +13,39 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.cost;
 
-import com.google.common.collect.ImmutableMap;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
 import org.apache.iotdb.session.Session;
 
+import com.google.common.collect.ImmutableMap;
+
 import java.util.Map;
 import java.util.WeakHashMap;
 
 import static java.util.Objects.requireNonNull;
 
-public class CachingTableStatsProvider
-        implements TableStatsProvider
-{
-    private final Metadata metadata;
-    private final Session session;
+public class CachingTableStatsProvider implements TableStatsProvider {
+  private final Metadata metadata;
+  private final Session session;
 
-    private final Map<TableHandle, TableStatistics> cache = new WeakHashMap<>();
+  private final Map<TableHandle, TableStatistics> cache = new WeakHashMap<>();
 
-    public CachingTableStatsProvider(Metadata metadata, Session session)
-    {
-        this.metadata = requireNonNull(metadata, "metadata is null");
-        this.session = requireNonNull(session, "session is null");
-    }
+  public CachingTableStatsProvider(Metadata metadata, Session session) {
+    this.metadata = requireNonNull(metadata, "metadata is null");
+    this.session = requireNonNull(session, "session is null");
+  }
 
-    @Override
-    public TableStatistics getTableStatistics(TableHandle tableHandle)
-    {
-        TableStatistics stats = cache.get(tableHandle);
-        if (stats == null) {
-            // stats = metadata.getTableStatistics(session, tableHandle);
-            cache.put(tableHandle, TableStatistics.empty());
-        }
-        return stats;
+  @Override
+  public TableStatistics getTableStatistics(TableHandle tableHandle) {
+    TableStatistics stats = cache.get(tableHandle);
+    if (stats == null) {
+      // stats = metadata.getTableStatistics(session, tableHandle);
+      cache.put(tableHandle, TableStatistics.empty());
     }
+    return stats;
+  }
 
-    public Map<TableHandle, TableStatistics> getCachedTableStatistics()
-    {
-        return ImmutableMap.copyOf(cache);
-    }
+  public Map<TableHandle, TableStatistics> getCachedTableStatistics() {
+    return ImmutableMap.copyOf(cache);
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/ColumnStatistics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/ColumnStatistics.java
index 37afd7b1dbe..4895d9096e6 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/ColumnStatistics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/ColumnStatistics.java
@@ -19,146 +19,138 @@ import java.util.Optional;
 import static java.lang.String.format;
 import static java.util.Objects.requireNonNull;
 
-public final class ColumnStatistics
-{
-    private static final ColumnStatistics EMPTY = new ColumnStatistics(Estimate.unknown(), Estimate.unknown(), Estimate.unknown(), Optional.empty());
-
-    private final Estimate nullsFraction;
-    private final Estimate distinctValuesCount;
-    private final Estimate dataSize;
-    private final Optional<DoubleRange> range;
-
-    public static ColumnStatistics empty()
-    {
-        return EMPTY;
+public final class ColumnStatistics {
+  private static final ColumnStatistics EMPTY =
+      new ColumnStatistics(
+          Estimate.unknown(), Estimate.unknown(), Estimate.unknown(), Optional.empty());
+
+  private final Estimate nullsFraction;
+  private final Estimate distinctValuesCount;
+  private final Estimate dataSize;
+  private final Optional<DoubleRange> range;
+
+  public static ColumnStatistics empty() {
+    return EMPTY;
+  }
+
+  public ColumnStatistics(
+      Estimate nullsFraction,
+      Estimate distinctValuesCount,
+      Estimate dataSize,
+      Optional<DoubleRange> range) {
+    this.nullsFraction = requireNonNull(nullsFraction, "nullsFraction is null");
+    if (!nullsFraction.isUnknown()) {
+      if (nullsFraction.getValue() < 0 || nullsFraction.getValue() > 1) {
+        throw new IllegalArgumentException(
+            format("nullsFraction must be between 0 and 1: %s", nullsFraction.getValue()));
+      }
     }
-
-    public ColumnStatistics(
-            Estimate nullsFraction,
-            Estimate distinctValuesCount,
-            Estimate dataSize,
-            Optional<DoubleRange> range)
-    {
-        this.nullsFraction = requireNonNull(nullsFraction, "nullsFraction is null");
-        if (!nullsFraction.isUnknown()) {
-            if (nullsFraction.getValue() < 0 || nullsFraction.getValue() > 1) {
-                throw new IllegalArgumentException(format("nullsFraction must be between 0 and 1: %s", nullsFraction.getValue()));
-            }
-        }
-        this.distinctValuesCount = requireNonNull(distinctValuesCount, "distinctValuesCount is null");
-        if (!distinctValuesCount.isUnknown() && distinctValuesCount.getValue() < 0) {
-            throw new IllegalArgumentException(format("distinctValuesCount must be greater than or equal to 0: %s", distinctValuesCount.getValue()));
-        }
-        this.dataSize = requireNonNull(dataSize, "dataSize is null");
-        if (!dataSize.isUnknown() && dataSize.getValue() < 0) {
-            throw new IllegalArgumentException(format("dataSize must be greater than or equal to 0: %s", dataSize.getValue()));
-        }
-        this.range = requireNonNull(range, "range is null");
+    this.distinctValuesCount = requireNonNull(distinctValuesCount, "distinctValuesCount is null");
+    if (!distinctValuesCount.isUnknown() && distinctValuesCount.getValue() < 0) {
+      throw new IllegalArgumentException(
+          format(
+              "distinctValuesCount must be greater than or equal to 0: %s",
+              distinctValuesCount.getValue()));
     }
-
-    public Estimate getNullsFraction()
-    {
-        return nullsFraction;
+    this.dataSize = requireNonNull(dataSize, "dataSize is null");
+    if (!dataSize.isUnknown() && dataSize.getValue() < 0) {
+      throw new IllegalArgumentException(
+          format("dataSize must be greater than or equal to 0: %s", dataSize.getValue()));
     }
+    this.range = requireNonNull(range, "range is null");
+  }
 
-    public Estimate getDistinctValuesCount()
-    {
-        return distinctValuesCount;
-    }
+  public Estimate getNullsFraction() {
+    return nullsFraction;
+  }
 
-    public Estimate getDataSize()
-    {
-        return dataSize;
-    }
+  public Estimate getDistinctValuesCount() {
+    return distinctValuesCount;
+  }
 
-    public Optional<DoubleRange> getRange()
-    {
-        return range;
+  public Estimate getDataSize() {
+    return dataSize;
+  }
+
+  public Optional<DoubleRange> getRange() {
+    return range;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ColumnStatistics that = (ColumnStatistics) o;
+    return Objects.equals(nullsFraction, that.nullsFraction)
+        && Objects.equals(distinctValuesCount, that.distinctValuesCount)
+        && Objects.equals(dataSize, that.dataSize)
+        && Objects.equals(range, that.range);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(nullsFraction, distinctValuesCount, dataSize, range);
+  }
+
+  @Override
+  public String toString() {
+    return "ColumnStatistics{"
+        + "nullsFraction="
+        + nullsFraction
+        + ", distinctValuesCount="
+        + distinctValuesCount
+        + ", dataSize="
+        + dataSize
+        + ", range="
+        + range
+        + '}';
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /**
+   * If one of the estimates below is unspecified (i.e. left as "unknown"), the optimizer may not be
+   * able to derive the statistics needed for performing cost-based query plan optimizations.
+   */
+  public static final class Builder {
+    private Estimate nullsFraction = Estimate.unknown();
+    private Estimate distinctValuesCount = Estimate.unknown();
+    private Estimate dataSize = Estimate.unknown();
+    private Optional<DoubleRange> range = Optional.empty();
+
+    public Builder setNullsFraction(Estimate nullsFraction) {
+      this.nullsFraction = requireNonNull(nullsFraction, "nullsFraction is null");
+      return this;
     }
 
-    @Override
-    public boolean equals(Object o)
-    {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        ColumnStatistics that = (ColumnStatistics) o;
-        return Objects.equals(nullsFraction, that.nullsFraction) &&
-                Objects.equals(distinctValuesCount, that.distinctValuesCount) &&
-                Objects.equals(dataSize, that.dataSize) &&
-                Objects.equals(range, that.range);
+    public Builder setDistinctValuesCount(Estimate distinctValuesCount) {
+      this.distinctValuesCount = requireNonNull(distinctValuesCount, "distinctValuesCount is null");
+      return this;
     }
 
-    @Override
-    public int hashCode()
-    {
-        return Objects.hash(nullsFraction, distinctValuesCount, dataSize, range);
+    public Builder setDataSize(Estimate dataSize) {
+      this.dataSize = requireNonNull(dataSize, "dataSize is null");
+      return this;
     }
 
-    @Override
-    public String toString()
-    {
-        return "ColumnStatistics{" +
-                "nullsFraction=" + nullsFraction +
-                ", distinctValuesCount=" + distinctValuesCount +
-                ", dataSize=" + dataSize +
-                ", range=" + range +
-                '}';
+    public Builder setRange(DoubleRange range) {
+      this.range = Optional.of(requireNonNull(range, "range is null"));
+      return this;
     }
 
-    public static Builder builder()
-    {
-        return new Builder();
+    public Builder setRange(Optional<DoubleRange> range) {
+      this.range = requireNonNull(range, "range is null");
+      return this;
     }
 
-    /**
-     * If one of the estimates below is unspecified (i.e. left as "unknown"),
-     * the optimizer may not be able to derive the statistics needed for
-     * performing cost-based query plan optimizations.
-     */
-    public static final class Builder
-    {
-        private Estimate nullsFraction = Estimate.unknown();
-        private Estimate distinctValuesCount = Estimate.unknown();
-        private Estimate dataSize = Estimate.unknown();
-        private Optional<DoubleRange> range = Optional.empty();
-
-        public Builder setNullsFraction(Estimate nullsFraction)
-        {
-            this.nullsFraction = requireNonNull(nullsFraction, "nullsFraction is null");
-            return this;
-        }
-
-        public Builder setDistinctValuesCount(Estimate distinctValuesCount)
-        {
-            this.distinctValuesCount = requireNonNull(distinctValuesCount, "distinctValuesCount is null");
-            return this;
-        }
-
-        public Builder setDataSize(Estimate dataSize)
-        {
-            this.dataSize = requireNonNull(dataSize, "dataSize is null");
-            return this;
-        }
-
-        public Builder setRange(DoubleRange range)
-        {
-            this.range = Optional.of(requireNonNull(range, "range is null"));
-            return this;
-        }
-
-        public Builder setRange(Optional<DoubleRange> range)
-        {
-            this.range = requireNonNull(range, "range is null");
-            return this;
-        }
-
-        public ColumnStatistics build()
-        {
-            return new ColumnStatistics(nullsFraction, distinctValuesCount, dataSize, range);
-        }
+    public ColumnStatistics build() {
+      return new ColumnStatistics(nullsFraction, distinctValuesCount, dataSize, range);
     }
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/DoubleRange.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/DoubleRange.java
index 11e97226224..fb3ea2a1366 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/DoubleRange.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/DoubleRange.java
@@ -26,93 +26,79 @@ import static java.lang.String.format;
 import static java.util.Objects.requireNonNull;
 import static org.apache.iotdb.db.queryengine.plan.relational.cost.StatsUtil.toStatsRepresentation;
 
-public class DoubleRange
-{
-    private final double min;
-    private final double max;
+public class DoubleRange {
+  private final double min;
+  private final double max;
 
-    /**
-     * Creates DoubleRange from Trino native representation.
-     */
-    public static Optional<DoubleRange> from(Type type, Object minTrinoNativeValue, Object maxTrinoNativeValue)
-    {
-        requireNonNull(minTrinoNativeValue, "minTrinoNativeValue is null");
-        requireNonNull(maxTrinoNativeValue, "maxTrinoNativeValue is null");
+  /** Creates DoubleRange from Trino native representation. */
+  public static Optional<DoubleRange> from(
+      Type type, Object minTrinoNativeValue, Object maxTrinoNativeValue) {
+    requireNonNull(minTrinoNativeValue, "minTrinoNativeValue is null");
+    requireNonNull(maxTrinoNativeValue, "maxTrinoNativeValue is null");
 
-        OptionalDouble min = toStatsRepresentation(type, minTrinoNativeValue);
-        OptionalDouble max = toStatsRepresentation(type, maxTrinoNativeValue);
+    OptionalDouble min = toStatsRepresentation(type, minTrinoNativeValue);
+    OptionalDouble max = toStatsRepresentation(type, maxTrinoNativeValue);
 
-        if (!min.isPresent() && !max.isPresent()) {
-            return Optional.empty();
-        }
-        if (!min.isPresent() || !max.isPresent()) {
-            throw new IllegalStateException(format(
-                    "One of min/max was converted to stats representation while the other was not for type %s: %s, %s",
-                    type,
-                    min,
-                    max));
-        }
-        return Optional.of(new DoubleRange(min.getAsDouble(), max.getAsDouble()));
+    if (!min.isPresent() && !max.isPresent()) {
+      return Optional.empty();
     }
-
-    public DoubleRange(double min, double max)
-    {
-        if (isNaN(min)) {
-            throw new IllegalArgumentException("min must not be NaN");
-        }
-        if (isNaN(max)) {
-            throw new IllegalArgumentException("max must not be NaN");
-        }
-        if (min > max) {
-            throw new IllegalArgumentException(format("max must be greater than or equal to min. min: %s. max: %s. ", min, max));
-        }
-        this.min = min;
-        this.max = max;
+    if (!min.isPresent() || !max.isPresent()) {
+      throw new IllegalStateException(
+          format(
+              "One of min/max was converted to stats representation while the other was not for type %s: %s, %s",
+              type, min, max));
     }
+    return Optional.of(new DoubleRange(min.getAsDouble(), max.getAsDouble()));
+  }
 
-    public double getMin()
-    {
-        return min;
+  public DoubleRange(double min, double max) {
+    if (isNaN(min)) {
+      throw new IllegalArgumentException("min must not be NaN");
     }
-
-    public double getMax()
-    {
-        return max;
+    if (isNaN(max)) {
+      throw new IllegalArgumentException("max must not be NaN");
     }
-
-    public static DoubleRange union(DoubleRange first, DoubleRange second)
-    {
-        requireNonNull(first, "first is null");
-        requireNonNull(second, "second is null");
-        return new DoubleRange(min(first.min, second.min), max(first.max, second.max));
+    if (min > max) {
+      throw new IllegalArgumentException(
+          format("max must be greater than or equal to min. min: %s. max: %s. ", min, max));
     }
+    this.min = min;
+    this.max = max;
+  }
 
-    @Override
-    public boolean equals(Object o)
-    {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        DoubleRange range = (DoubleRange) o;
-        return Double.compare(range.min, min) == 0 &&
-                Double.compare(range.max, max) == 0;
-    }
+  public double getMin() {
+    return min;
+  }
 
-    @Override
-    public int hashCode()
-    {
-        return Objects.hash(min, max);
-    }
+  public double getMax() {
+    return max;
+  }
 
-    @Override
-    public String toString()
-    {
-        return "DoubleRange{" +
-                "min=" + min +
-                ", max=" + max +
-                '}';
+  public static DoubleRange union(DoubleRange first, DoubleRange second) {
+    requireNonNull(first, "first is null");
+    requireNonNull(second, "second is null");
+    return new DoubleRange(min(first.min, second.min), max(first.max, second.max));
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
     }
+    DoubleRange range = (DoubleRange) o;
+    return Double.compare(range.min, min) == 0 && Double.compare(range.max, max) == 0;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(min, max);
+  }
+
+  @Override
+  public String toString() {
+    return "DoubleRange{" + "min=" + min + ", max=" + max + '}';
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/Estimate.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/Estimate.java
index 93ce50eb182..85366b89090 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/Estimate.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/Estimate.java
@@ -20,75 +20,65 @@ import static java.lang.Double.NaN;
 import static java.lang.Double.isInfinite;
 import static java.lang.Double.isNaN;
 
-public final class Estimate
-{
-    // todo eventually add some notion of statistic reliability
-    //      Skipping for now as there hard to compute it properly and so far we do not have
-    //      usecase for that.
+public final class Estimate {
+  // todo eventually add some notion of statistic reliability
+  //      Skipping for now as there hard to compute it properly and so far we do not have
+  //      usecase for that.
 
-    private static final Estimate UNKNOWN = new Estimate(NaN);
-    private static final Estimate ZERO = new Estimate(0);
+  private static final Estimate UNKNOWN = new Estimate(NaN);
+  private static final Estimate ZERO = new Estimate(0);
 
-    private final double value;
+  private final double value;
 
-    public static Estimate unknown()
-    {
-        return UNKNOWN;
-    }
+  public static Estimate unknown() {
+    return UNKNOWN;
+  }
 
-    public static Estimate zero()
-    {
-        return ZERO;
-    }
+  public static Estimate zero() {
+    return ZERO;
+  }
 
-    public static Estimate of(double value)
-    {
-        if (isNaN(value)) {
-            throw new IllegalArgumentException("value is NaN");
-        }
-        if (isInfinite(value)) {
-            throw new IllegalArgumentException("value is infinite");
-        }
-        return new Estimate(value);
+  public static Estimate of(double value) {
+    if (isNaN(value)) {
+      throw new IllegalArgumentException("value is NaN");
     }
-
-    private Estimate(double value)
-    {
-        this.value = value;
+    if (isInfinite(value)) {
+      throw new IllegalArgumentException("value is infinite");
     }
+    return new Estimate(value);
+  }
 
-    public boolean isUnknown()
-    {
-        return isNaN(value);
-    }
+  private Estimate(double value) {
+    this.value = value;
+  }
 
-    public double getValue()
-    {
-        return value;
-    }
+  public boolean isUnknown() {
+    return isNaN(value);
+  }
 
-    @Override
-    public boolean equals(Object o)
-    {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        Estimate estimate = (Estimate) o;
-        return Double.compare(estimate.value, value) == 0;
-    }
+  public double getValue() {
+    return value;
+  }
 
-    @Override
-    public int hashCode()
-    {
-        return Objects.hash(value);
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
     }
-
-    @Override
-    public String toString()
-    {
-        return String.valueOf(value);
+    if (o == null || getClass() != o.getClass()) {
+      return false;
     }
+    Estimate estimate = (Estimate) o;
+    return Double.compare(estimate.value, value) == 0;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(value);
+  }
+
+  @Override
+  public String toString() {
+    return String.valueOf(value);
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/StatsUtil.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/StatsUtil.java
index 4d4ee8b3388..94d89301ba9 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/StatsUtil.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/StatsUtil.java
@@ -13,36 +13,31 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.cost;
 
-
 import org.apache.iotdb.tsfile.read.common.type.Type;
 
 import java.util.OptionalDouble;
 
-import static java.lang.Float.intBitsToFloat;
-import static java.lang.Math.toIntExact;
 import static java.util.Objects.requireNonNull;
 import static org.apache.iotdb.tsfile.read.common.type.BooleanType.BOOLEAN;
 import static org.apache.iotdb.tsfile.read.common.type.DoubleType.DOUBLE;
 import static org.apache.iotdb.tsfile.read.common.type.IntType.INT32;
 
-public final class StatsUtil
-{
-    private StatsUtil() {}
+public final class StatsUtil {
+  private StatsUtil() {}
 
-    public static OptionalDouble toStatsRepresentation(Type type, Object value)
-    {
-        requireNonNull(type, "type is null");
-        requireNonNull(value, "value is null");
+  public static OptionalDouble toStatsRepresentation(Type type, Object value) {
+    requireNonNull(type, "type is null");
+    requireNonNull(value, "value is null");
 
-        if (type == BOOLEAN) {
-            return OptionalDouble.of((boolean) value ? 1 : 0);
-        }
-        if (type == INT32) {
-            return OptionalDouble.of((long) value);
-        }
-        if (type == DOUBLE) {
-            return OptionalDouble.of((double) value);
-        }
-        return OptionalDouble.empty();
+    if (type == BOOLEAN) {
+      return OptionalDouble.of((boolean) value ? 1 : 0);
+    }
+    if (type == INT32) {
+      return OptionalDouble.of((long) value);
+    }
+    if (type == DOUBLE) {
+      return OptionalDouble.of((double) value);
     }
+    return OptionalDouble.empty();
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/TableStatistics.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/TableStatistics.java
index 5a3db41a242..9a610107d91 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/TableStatistics.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/TableStatistics.java
@@ -14,7 +14,6 @@
 
 package org.apache.iotdb.db.queryengine.plan.relational.cost;
 
-
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnHandle;
 
 import java.util.LinkedHashMap;
@@ -25,98 +24,89 @@ import static java.lang.String.format;
 import static java.util.Collections.unmodifiableMap;
 import static java.util.Objects.requireNonNull;
 
-public final class TableStatistics
-{
-    private static final TableStatistics EMPTY = TableStatistics.builder().build();
-
-    private final Estimate rowCount;
-    private final Map<ColumnHandle, ColumnStatistics> columnStatistics;
+public final class TableStatistics {
+  private static final TableStatistics EMPTY = TableStatistics.builder().build();
 
-    public static TableStatistics empty()
-    {
-        return EMPTY;
-    }
+  private final Estimate rowCount;
+  private final Map<ColumnHandle, ColumnStatistics> columnStatistics;
 
-    public TableStatistics(Estimate rowCount, Map<ColumnHandle, ColumnStatistics> columnStatistics)
-    {
-        this.rowCount = requireNonNull(rowCount, "rowCount cannot be null");
-        if (!rowCount.isUnknown() && rowCount.getValue() < 0) {
-            throw new IllegalArgumentException(format("rowCount must be greater than or equal to 0: %s", rowCount.getValue()));
-        }
-        this.columnStatistics = unmodifiableMap(requireNonNull(columnStatistics, "columnStatistics cannot be null"));
-    }
+  public static TableStatistics empty() {
+    return EMPTY;
+  }
 
-    public Estimate getRowCount()
-    {
-        return rowCount;
+  public TableStatistics(Estimate rowCount, Map<ColumnHandle, ColumnStatistics> columnStatistics) {
+    this.rowCount = requireNonNull(rowCount, "rowCount cannot be null");
+    if (!rowCount.isUnknown() && rowCount.getValue() < 0) {
+      throw new IllegalArgumentException(
+          format("rowCount must be greater than or equal to 0: %s", rowCount.getValue()));
     }
-
-    public Map<ColumnHandle, ColumnStatistics> getColumnStatistics()
-    {
-        return columnStatistics;
+    this.columnStatistics =
+        unmodifiableMap(requireNonNull(columnStatistics, "columnStatistics cannot be null"));
+  }
+
+  public Estimate getRowCount() {
+    return rowCount;
+  }
+
+  public Map<ColumnHandle, ColumnStatistics> getColumnStatistics() {
+    return columnStatistics;
+  }
+
+  public boolean isEmpty() {
+    return equals(empty());
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
     }
-
-    public boolean isEmpty()
-    {
-        return equals(empty());
+    if (o == null || getClass() != o.getClass()) {
+      return false;
     }
-
-    @Override
-    public boolean equals(Object o)
-    {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        TableStatistics that = (TableStatistics) o;
-        return Objects.equals(rowCount, that.rowCount) &&
-                Objects.equals(columnStatistics, that.columnStatistics);
-    }
-
-    @Override
-    public int hashCode()
-    {
-        return Objects.hash(rowCount, columnStatistics);
-    }
-
-    @Override
-    public String toString()
-    {
-        return "TableStatistics{" +
-                "rowCount=" + rowCount +
-                ", columnStatistics=" + columnStatistics +
-                '}';
+    TableStatistics that = (TableStatistics) o;
+    return Objects.equals(rowCount, that.rowCount)
+        && Objects.equals(columnStatistics, that.columnStatistics);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(rowCount, columnStatistics);
+  }
+
+  @Override
+  public String toString() {
+    return "TableStatistics{"
+        + "rowCount="
+        + rowCount
+        + ", columnStatistics="
+        + columnStatistics
+        + '}';
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static final class Builder {
+    private final Map<ColumnHandle, ColumnStatistics> columnStatisticsMap = new LinkedHashMap<>();
+    private Estimate rowCount = Estimate.unknown();
+
+    public Builder setRowCount(Estimate rowCount) {
+      this.rowCount = requireNonNull(rowCount, "rowCount cannot be null");
+      return this;
     }
 
-    public static Builder builder()
-    {
-        return new Builder();
+    public Builder setColumnStatistics(
+        ColumnHandle columnHandle, ColumnStatistics columnStatistics) {
+      requireNonNull(columnHandle, "columnHandle cannot be null");
+      requireNonNull(columnStatistics, "columnStatistics cannot be null");
+      this.columnStatisticsMap.put(columnHandle, columnStatistics);
+      return this;
     }
 
-    public static final class Builder
-    {
-        private final Map<ColumnHandle, ColumnStatistics> columnStatisticsMap = new LinkedHashMap<>();
-        private Estimate rowCount = Estimate.unknown();
-
-        public Builder setRowCount(Estimate rowCount)
-        {
-            this.rowCount = requireNonNull(rowCount, "rowCount cannot be null");
-            return this;
-        }
-
-        public Builder setColumnStatistics(ColumnHandle columnHandle, ColumnStatistics columnStatistics)
-        {
-            requireNonNull(columnHandle, "columnHandle cannot be null");
-            requireNonNull(columnStatistics, "columnStatistics cannot be null");
-            this.columnStatisticsMap.put(columnHandle, columnStatistics);
-            return this;
-        }
-
-        public TableStatistics build()
-        {
-            return new TableStatistics(rowCount, columnStatisticsMap);
-        }
+    public TableStatistics build() {
+      return new TableStatistics(rowCount, columnStatisticsMap);
     }
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/TableStatsProvider.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/TableStatsProvider.java
index 899b5683bcf..72fd4d6932e 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/TableStatsProvider.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/cost/TableStatsProvider.java
@@ -16,5 +16,5 @@ package org.apache.iotdb.db.queryengine.plan.relational.cost;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
 
 public interface TableStatsProvider {
-    TableStatistics getTableStatistics(TableHandle tableHandle);
+  TableStatistics getTableStatistics(TableHandle tableHandle);
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Assignments.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Assignments.java
index 50703a756ab..992ada2a745 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Assignments.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Assignments.java
@@ -13,12 +13,13 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.planner;
 
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
-import org.apache.iotdb.db.relational.sql.tree.Expression;
 
 import java.util.Collection;
 import java.util.LinkedHashMap;
@@ -35,234 +36,196 @@ import static com.google.common.base.Preconditions.checkState;
 import static java.util.Arrays.asList;
 import static java.util.Objects.requireNonNull;
 
-public class Assignments
-{
-    public static Builder builder()
-    {
-        return new Builder();
-    }
+public class Assignments {
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Assignments identity(Symbol... symbols) {
+    return identity(asList(symbols));
+  }
+
+  public static Assignments identity(Iterable<Symbol> symbols) {
+    return builder().putIdentities(symbols).build();
+  }
+
+  public static Assignments copyOf(Map<Symbol, Expression> assignments) {
+    return builder().putAll(assignments).build();
+  }
+
+  public static Assignments of() {
+    return builder().build();
+  }
+
+  public static Assignments of(Symbol symbol, Expression expression) {
+    return builder().put(symbol, expression).build();
+  }
+
+  public static Assignments of(
+      Symbol symbol1, Expression expression1, Symbol symbol2, Expression expression2) {
+    return builder().put(symbol1, expression1).put(symbol2, expression2).build();
+  }
 
-    public static Assignments identity(Symbol... symbols)
-    {
-        return identity(asList(symbols));
-    }
+  private final Map<Symbol, Expression> assignments;
 
-    public static Assignments identity(Iterable<Symbol> symbols)
-    {
-        return builder().putIdentities(symbols).build();
-    }
+  @JsonCreator
+  public Assignments(@JsonProperty("assignments") Map<Symbol, Expression> assignments) {
+    this.assignments = ImmutableMap.copyOf(requireNonNull(assignments, "assignments is null"));
+  }
 
-    public static Assignments copyOf(Map<Symbol, Expression> assignments)
-    {
-        return builder()
-                .putAll(assignments)
-                .build();
-    }
+  public List<Symbol> getOutputs() {
+    return ImmutableList.copyOf(assignments.keySet());
+  }
 
-    public static Assignments of()
-    {
-        return builder().build();
-    }
+  @JsonProperty("assignments")
+  public Map<Symbol, Expression> getMap() {
+    return assignments;
+  }
 
-    public static Assignments of(Symbol symbol, Expression expression)
-    {
-        return builder().put(symbol, expression).build();
-    }
+  public Assignments rewrite(Function<Expression, Expression> rewrite) {
+    return assignments.entrySet().stream()
+        .map(entry -> Maps.immutableEntry(entry.getKey(), rewrite.apply(entry.getValue())))
+        .collect(toAssignments());
+  }
 
-    public static Assignments of(Symbol symbol1, Expression expression1, Symbol symbol2, Expression expression2)
-    {
-        return builder().put(symbol1, expression1).put(symbol2, expression2).build();
-    }
+  public Assignments filter(Collection<Symbol> symbols) {
+    return filter(symbols::contains);
+  }
 
-    private final Map<Symbol, Expression> assignments;
+  public Assignments filter(Predicate<Symbol> predicate) {
+    return assignments.entrySet().stream()
+        .filter(entry -> predicate.test(entry.getKey()))
+        .collect(toAssignments());
+  }
 
-    @JsonCreator
-    public Assignments(@JsonProperty("assignments") Map<Symbol, Expression> assignments)
-    {
-        this.assignments = ImmutableMap.copyOf(requireNonNull(assignments, "assignments is null"));
-    }
+  private Collector<Entry<Symbol, Expression>, Builder, Assignments> toAssignments() {
+    return Collector.of(
+        Assignments::builder,
+        (builder, entry) -> builder.put(entry.getKey(), entry.getValue()),
+        (left, right) -> {
+          left.putAll(right.build());
+          return left;
+        },
+        Builder::build);
+  }
 
-    public List<Symbol> getOutputs()
-    {
-        return ImmutableList.copyOf(assignments.keySet());
-    }
+  public Collection<Expression> getExpressions() {
+    return assignments.values();
+  }
 
-    @JsonProperty("assignments")
-    public Map<Symbol, Expression> getMap()
-    {
-        return assignments;
-    }
+  public Set<Symbol> getSymbols() {
+    return assignments.keySet();
+  }
 
-    public Assignments rewrite(Function<Expression, Expression> rewrite)
-    {
-        return assignments.entrySet().stream()
-                .map(entry -> Maps.immutableEntry(entry.getKey(), rewrite.apply(entry.getValue())))
-                .collect(toAssignments());
-    }
+  public Set<Entry<Symbol, Expression>> entrySet() {
+    return assignments.entrySet();
+  }
 
-    public Assignments filter(Collection<Symbol> symbols)
-    {
-        return filter(symbols::contains);
-    }
+  public Expression get(Symbol symbol) {
+    return assignments.get(symbol);
+  }
 
-    public Assignments filter(Predicate<Symbol> predicate)
-    {
-        return assignments.entrySet().stream()
-                .filter(entry -> predicate.test(entry.getKey()))
-                .collect(toAssignments());
-    }
+  public int size() {
+    return assignments.size();
+  }
 
-    private Collector<Entry<Symbol, Expression>, Builder, Assignments> toAssignments()
-    {
-        return Collector.of(
-                Assignments::builder,
-                (builder, entry) -> builder.put(entry.getKey(), entry.getValue()),
-                (left, right) -> {
-                    left.putAll(right.build());
-                    return left;
-                },
-                Builder::build);
-    }
+  public boolean isEmpty() {
+    return size() == 0;
+  }
 
-    public Collection<Expression> getExpressions()
-    {
-        return assignments.values();
-    }
+  public void forEach(BiConsumer<Symbol, Expression> consumer) {
+    assignments.forEach(consumer);
+  }
 
-    public Set<Symbol> getSymbols()
-    {
-        return assignments.keySet();
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
     }
 
-    public Set<Entry<Symbol, Expression>> entrySet()
-    {
-        return assignments.entrySet();
+    Assignments that = (Assignments) o;
+
+    return assignments.equals(that.assignments);
+  }
+
+  @Override
+  public int hashCode() {
+    return assignments.hashCode();
+  }
+
+  public static class Assignment {
+    private final Symbol output;
+    private final Expression expression;
+
+    public Assignment(Symbol output, Expression expression) {
+      this.output = requireNonNull(output, "output is null");
+      this.expression = requireNonNull(expression, "expression is null");
     }
 
-    public Expression get(Symbol symbol)
-    {
-        return assignments.get(symbol);
+    public Symbol getOutput() {
+      return output;
     }
 
-    public int size()
-    {
-        return assignments.size();
+    public Expression getExpression() {
+      return expression;
     }
+  }
 
-    public boolean isEmpty()
-    {
-        return size() == 0;
+  public static class Builder {
+    private final Map<Symbol, Expression> assignments = new LinkedHashMap<>();
+
+    public Builder putAll(Assignments assignments) {
+      return putAll(assignments.getMap());
     }
 
-    public void forEach(BiConsumer<Symbol, Expression> consumer)
-    {
-        assignments.forEach(consumer);
+    public Builder putAll(Map<Symbol, ? extends Expression> assignments) {
+      for (Entry<Symbol, ? extends Expression> assignment : assignments.entrySet()) {
+        put(assignment.getKey(), assignment.getValue());
+      }
+      return this;
     }
 
-    @Override
-    public boolean equals(Object o)
-    {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
+    public Builder put(Symbol symbol, Expression expression) {
+      if (assignments.containsKey(symbol)) {
+        Expression assignment = assignments.get(symbol);
+        checkState(
+            assignment.equals(expression),
+            "Symbol %s already has assignment %s, while adding %s",
+            symbol,
+            assignment,
+            expression);
+      }
+      assignments.put(symbol, expression);
+      return this;
+    }
 
-        Assignments that = (Assignments) o;
+    public Builder put(Entry<Symbol, Expression> assignment) {
+      put(assignment.getKey(), assignment.getValue());
+      return this;
+    }
 
-        return assignments.equals(that.assignments);
+    public Builder putIdentities(Iterable<Symbol> symbols) {
+      for (Symbol symbol : symbols) {
+        putIdentity(symbol);
+      }
+      return this;
     }
 
-    @Override
-    public int hashCode()
-    {
-        return assignments.hashCode();
+    public Builder putIdentity(Symbol symbol) {
+      put(symbol, symbol.toSymbolReference());
+      return this;
     }
 
-    public static class Assignment
-    {
-        private final Symbol output;
-        private final Expression expression;
-
-        public Assignment(Symbol output, Expression expression)
-        {
-            this.output = requireNonNull(output, "output is null");
-            this.expression = requireNonNull(expression, "expression is null");
-        }
-
-        public Symbol getOutput()
-        {
-            return output;
-        }
-
-        public Expression getExpression()
-        {
-            return expression;
-        }
+    public Assignments build() {
+      return new Assignments(assignments);
     }
 
-    public static class Builder
-    {
-        private final Map<Symbol, Expression> assignments = new LinkedHashMap<>();
-
-        public Builder putAll(Assignments assignments)
-        {
-            return putAll(assignments.getMap());
-        }
-
-        public Builder putAll(Map<Symbol, ? extends Expression> assignments)
-        {
-            for (Entry<Symbol, ? extends Expression> assignment : assignments.entrySet()) {
-                put(assignment.getKey(), assignment.getValue());
-            }
-            return this;
-        }
-
-        public Builder put(Symbol symbol, Expression expression)
-        {
-            if (assignments.containsKey(symbol)) {
-                Expression assignment = assignments.get(symbol);
-                checkState(
-                        assignment.equals(expression),
-                        "Symbol %s already has assignment %s, while adding %s",
-                        symbol,
-                        assignment,
-                        expression);
-            }
-            assignments.put(symbol, expression);
-            return this;
-        }
-
-        public Builder put(Entry<Symbol, Expression> assignment)
-        {
-            put(assignment.getKey(), assignment.getValue());
-            return this;
-        }
-
-        public Builder putIdentities(Iterable<Symbol> symbols)
-        {
-            for (Symbol symbol : symbols) {
-                putIdentity(symbol);
-            }
-            return this;
-        }
-
-        public Builder putIdentity(Symbol symbol)
-        {
-            put(symbol, symbol.toSymbolReference());
-            return this;
-        }
-
-        public Assignments build()
-        {
-            return new Assignments(assignments);
-        }
-
-        public Builder add(Assignment assignment)
-        {
-            put(assignment.getOutput(), assignment.getExpression());
-            return this;
-        }
+    public Builder add(Assignment assignment) {
+      put(assignment.getOutput(), assignment.getExpression());
+      return this;
     }
+  }
 }
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 e89bad05779..1f578074143 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
@@ -13,123 +13,102 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.planner;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.errorprone.annotations.MustBeClosed;
-import io.airlift.log.Logger;
 import org.apache.iotdb.commons.exception.IoTDBException;
 import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
 import org.apache.iotdb.db.queryengine.execution.warnings.WarningCollector;
-import org.apache.iotdb.db.queryengine.plan.analyze.TypeProvider;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Field;
-import org.apache.iotdb.db.queryengine.plan.relational.analyzer.NodeRef;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.RelationType;
-import org.apache.iotdb.db.queryengine.plan.relational.cost.CachingTableStatsProvider;
-import org.apache.iotdb.db.queryengine.plan.relational.cost.TableStatistics;
-import org.apache.iotdb.db.queryengine.plan.relational.cost.TableStatsProvider;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
-import org.apache.iotdb.db.queryengine.plan.relational.metadata.ResolvedFunction;
-import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.OutputNode;
-import org.apache.iotdb.db.relational.sql.tree.Expression;
 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;
 import org.apache.iotdb.session.Session;
 
-import javax.annotation.Nonnull;
-import java.util.AbstractMap.SimpleImmutableEntry;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.function.Function;
-
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Verify.verify;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.ImmutableMap.toImmutableMap;
-import static com.google.common.collect.Streams.forEachPair;
-import static com.google.common.collect.Streams.zip;
-import static java.util.Objects.requireNonNull;
-
-public class LogicalPlanner
-{
-    private static final Logger LOG = Logger.get(LogicalPlanner.class);
-
-    public enum Stage {
-        CREATED, OPTIMIZED, OPTIMIZED_AND_VALIDATED
-    }
-
-    private final MPPQueryContext context;
-
-    private final Session session;
-    private final SymbolAllocator symbolAllocator = new SymbolAllocator();
-    private final Metadata metadata;
-    private final WarningCollector warningCollector;
-
-    public LogicalPlanner(
-            MPPQueryContext context,
-            Metadata metadata,
-            Session session,
-            WarningCollector warningCollector) {
-        this.context = context;
-        this.metadata = metadata;
-        this.session = requireNonNull(session, "session is null");
-        this.warningCollector = requireNonNull(warningCollector, "warningCollector is null");
-    }
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import io.airlift.log.Logger;
 
-    public PlanNode plan(Analysis analysis) throws IoTDBException {
-        return planStatement(analysis, analysis.getStatement());
-    }
+import static java.util.Objects.requireNonNull;
 
-    public PlanNode planStatement(Analysis analysis, Statement statement) throws IoTDBException {
-        return createOutputPlan(planStatementWithoutOutput(analysis, statement), analysis);
+public class LogicalPlanner {
+  private static final Logger LOG = Logger.get(LogicalPlanner.class);
+
+  public enum Stage {
+    CREATED,
+    OPTIMIZED,
+    OPTIMIZED_AND_VALIDATED
+  }
+
+  private final MPPQueryContext context;
+
+  private final Session session;
+  private final SymbolAllocator symbolAllocator = new SymbolAllocator();
+  private final Metadata metadata;
+  private final WarningCollector warningCollector;
+
+  public LogicalPlanner(
+      MPPQueryContext context,
+      Metadata metadata,
+      Session session,
+      WarningCollector warningCollector) {
+    this.context = context;
+    this.metadata = metadata;
+    this.session = requireNonNull(session, "session is null");
+    this.warningCollector = requireNonNull(warningCollector, "warningCollector is null");
+  }
+
+  public PlanNode plan(Analysis analysis) throws IoTDBException {
+    return planStatement(analysis, analysis.getStatement());
+  }
+
+  public PlanNode planStatement(Analysis analysis, Statement statement) throws IoTDBException {
+    return createOutputPlan(planStatementWithoutOutput(analysis, statement), analysis);
+  }
+
+  private RelationPlan planStatementWithoutOutput(Analysis analysis, Statement statement)
+      throws IoTDBException {
+    if (statement instanceof Query) {
+      return createRelationPlan(analysis, (Query) statement);
     }
-
-    private RelationPlan planStatementWithoutOutput(Analysis analysis, Statement statement) throws IoTDBException {
-        if (statement instanceof Query) {
-            return createRelationPlan(analysis, (Query) statement);
-        }
-        throw new IoTDBException("Unsupported statement type " + statement.getClass().getSimpleName(), -1);
+    throw new IoTDBException(
+        "Unsupported statement type " + statement.getClass().getSimpleName(), -1);
+  }
+
+  private PlanNode createOutputPlan(RelationPlan plan, Analysis analysis) {
+    ImmutableList.Builder<Symbol> outputs = ImmutableList.builder();
+    ImmutableList.Builder<String> names = ImmutableList.builder();
+
+    int columnNumber = 0;
+    // TODO perfect the logic of outputDescriptor
+    RelationType outputDescriptor = analysis.getOutputDescriptor();
+    for (Field field : outputDescriptor.getVisibleFields()) {
+      String name = field.getName().orElse("_col" + columnNumber);
+      names.add(name);
+
+      int fieldIndex = outputDescriptor.indexOf(field);
+      Symbol symbol = plan.getSymbol(fieldIndex);
+      outputs.add(symbol);
+
+      columnNumber++;
     }
 
-    private PlanNode createOutputPlan(RelationPlan plan, Analysis analysis) {
-        ImmutableList.Builder<Symbol> outputs = ImmutableList.builder();
-        ImmutableList.Builder<String> names = ImmutableList.builder();
+    return new OutputNode(
+        context.getQueryId().genPlanNodeId(), plan.getRoot(), names.build(), outputs.build());
+  }
 
-        int columnNumber = 0;
-        // TODO perfect the logic of outputDescriptor
-        RelationType outputDescriptor = analysis.getOutputDescriptor();
-        for (Field field : outputDescriptor.getVisibleFields()) {
-            String name = field.getName().orElse("_col" + columnNumber);
-            names.add(name);
+  private RelationPlan createRelationPlan(Analysis analysis, Query query) {
+    return getRelationPlanner(analysis).process(query, null);
+  }
 
-            int fieldIndex = outputDescriptor.indexOf(field);
-            Symbol symbol = plan.getSymbol(fieldIndex);
-            outputs.add(symbol);
-
-            columnNumber++;
-        }
-
-        return new OutputNode(context.getQueryId().genPlanNodeId(), plan.getRoot(), names.build(), outputs.build());
-    }
+  private RelationPlan createRelationPlan(Analysis analysis, Table table) {
+    return getRelationPlanner(analysis).process(table, null);
+  }
 
-    private RelationPlan createRelationPlan(Analysis analysis, Query query) {
-        return getRelationPlanner(analysis).process(query, null);
-    }
-
-    private RelationPlan createRelationPlan(Analysis analysis, Table table) {
-        return getRelationPlanner(analysis).process(table, null);
-    }
-
-    private RelationPlanner getRelationPlanner(Analysis analysis) {
-        return new RelationPlanner(analysis, symbolAllocator, context.getQueryId(), session, ImmutableMap.of());
-    }
+  private RelationPlanner getRelationPlanner(Analysis analysis) {
+    return new RelationPlanner(
+        analysis, symbolAllocator, context.getQueryId(), session, ImmutableMap.of());
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/OrderingScheme.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/OrderingScheme.java
new file mode 100644
index 00000000000..73ba7044f35
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/OrderingScheme.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.util.Objects.requireNonNull;
+
+public class OrderingScheme {
+  private final List<Symbol> orderBy;
+  private final Map<Symbol, SortOrder> orderings;
+
+  @JsonCreator
+  public OrderingScheme(
+      @JsonProperty("orderBy") List<Symbol> orderBy,
+      @JsonProperty("orderings") Map<Symbol, SortOrder> orderings) {
+    requireNonNull(orderBy, "orderBy is null");
+    requireNonNull(orderings, "orderings is null");
+    checkArgument(!orderBy.isEmpty(), "orderBy is empty");
+    checkArgument(
+        orderings.keySet().equals(ImmutableSet.copyOf(orderBy)),
+        "orderBy keys and orderings don't match");
+    this.orderBy = ImmutableList.copyOf(orderBy);
+    this.orderings = ImmutableMap.copyOf(orderings);
+  }
+
+  @JsonProperty
+  public List<Symbol> getOrderBy() {
+    return orderBy;
+  }
+
+  @JsonProperty
+  public Map<Symbol, SortOrder> getOrderings() {
+    return orderings;
+  }
+
+  public List<SortOrder> getOrderingList() {
+    return orderBy.stream().map(orderings::get).collect(toImmutableList());
+  }
+
+  public SortOrder getOrdering(Symbol symbol) {
+    checkArgument(orderings.containsKey(symbol), "No ordering for symbol: %s", symbol);
+    return orderings.get(symbol);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    OrderingScheme that = (OrderingScheme) o;
+    return Objects.equals(orderBy, that.orderBy) && Objects.equals(orderings, that.orderings);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(orderBy, orderings);
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this).add("orderBy", orderBy).add("orderings", orderings).toString();
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/OrderingTranslator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/OrderingTranslator.java
new file mode 100644
index 00000000000..faf4ba41387
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/OrderingTranslator.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import org.apache.iotdb.db.relational.sql.tree.SortItem;
+
+public class OrderingTranslator {
+  private OrderingTranslator() {}
+
+  public static SortOrder sortItemToSortOrder(SortItem sortItem) {
+    if (sortItem.getOrdering() == SortItem.Ordering.ASCENDING) {
+      if (sortItem.getNullOrdering() == SortItem.NullOrdering.FIRST) {
+        return SortOrder.ASC_NULLS_FIRST;
+      }
+      return SortOrder.ASC_NULLS_LAST;
+    }
+
+    if (sortItem.getNullOrdering() == SortItem.NullOrdering.FIRST) {
+      return SortOrder.DESC_NULLS_FIRST;
+    }
+    return SortOrder.DESC_NULLS_LAST;
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PlanBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PlanBuilder.java
index 4dcb6b30041..cd29f30a1c4 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PlanBuilder.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/PlanBuilder.java
@@ -13,7 +13,6 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.planner;
 
-import com.google.common.collect.ImmutableMap;
 import org.apache.iotdb.db.queryengine.common.QueryId;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis;
@@ -22,108 +21,70 @@ import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ProjectNode;
 import org.apache.iotdb.db.relational.sql.tree.Expression;
 import org.apache.iotdb.session.Session;
 
+import com.google.common.collect.ImmutableMap;
+
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.BiFunction;
-import java.util.function.BiPredicate;
 
 import static java.util.Objects.requireNonNull;
-import static org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware.scopeAwareKey;
-
-class PlanBuilder
-{
-    private final PlanNode root;
-
-    public PlanBuilder(PlanNode root)
-    {
-        requireNonNull(root, "root is null");
-
-        this.root = root;
-    }
-
-    public static PlanBuilder newPlanBuilder(RelationPlan plan, Analysis analysis,
-                                            Session session)
-    {
-        return newPlanBuilder(plan, analysis, ImmutableMap.of(), session);
-    }
-
-    public static PlanBuilder newPlanBuilder(RelationPlan plan, Analysis analysis,
-                                             Map<ScopeAware<Expression>, Symbol> mappings,
-                                             Session session)
-    {
-        return new PlanBuilder(plan.getRoot());
-    }
-
-    public PlanBuilder withNewRoot(PlanNode root)
-    {
-        return new PlanBuilder(root);
-    }
-
-    public PlanBuilder withScope(Scope scope, List<Symbol> fields)
-    {
-        return new PlanBuilder(root);
-    }
-
-    public PlanNode getRoot()
-    {
-        return root;
-    }
-
-    public boolean canTranslate(Expression expression)
-    {
-        return translations.canTranslate(expression);
-    }
-
-    public Symbol translate(Expression expression)
-    {
-        return Symbol.from(translations.rewrite(expression));
-    }
-
-    public Expression rewrite(Expression expression)
-    {
-        return translations.rewrite(expression);
-    }
-
-    public TranslationMap getTranslations()
-    {
-        return translations;
-    }
-
-    public Scope getScope()
-    {
-        return translations.getScope();
-    }
-
-    public PlanBuilder appendProjections(Expression expressions, SymbolAllocator symbolAllocator, QueryId idAllocator)
-    {
-        return appendProjections(expressions, symbolAllocator, idAllocator, TranslationMap::rewrite, TranslationMap::canTranslate);
-    }
-
-    public <T extends Expression> PlanBuilder appendProjections(
-            Iterable<T> expressions,
-            SymbolAllocator symbolAllocator,
-            QueryId idAllocator,
-            BiFunction<TranslationMap, T, Expression> rewriter,
-            BiPredicate<TranslationMap, T> alreadyHasTranslation)
-    {
-        Assignments.Builder projections = Assignments.builder();
-
-        // add an identity projection for underlying plan
-        projections.putIdentities(root.getOutputSymbols());
-
-        Map<ScopeAware<Expression>, Symbol> mappings = new HashMap<>();
-        for (T expression : expressions) {
-            // Skip any expressions that have already been translated and recorded in the translation map, or that are duplicated in the list of exp
-            if (!mappings.containsKey(scopeAwareKey(expression, translations.getAnalysis(), translations.getScope())) && !alreadyHasTranslation.test(translations, expression)) {
-                Symbol symbol = symbolAllocator.newSymbol("expr", translations.getAnalysis().getType(expression));
-                projections.put(symbol, rewriter.apply(translations, expression));
-                mappings.put(scopeAwareKey(expression, translations.getAnalysis(), translations.getScope()), symbol);
-            }
-        }
-
-        return new PlanBuilder(
-                getTranslations().withAdditionalMappings(mappings),
-                new ProjectNode(idAllocator.genPlanNodeId(), root, projections.build()));
-    }
+
+class PlanBuilder {
+  private final PlanNode root;
+
+  public PlanBuilder(PlanNode root) {
+    requireNonNull(root, "root is null");
+
+    this.root = root;
+  }
+
+  public static PlanBuilder newPlanBuilder(RelationPlan plan, Analysis analysis, Session session) {
+    return newPlanBuilder(plan, analysis, ImmutableMap.of(), session);
+  }
+
+  public static PlanBuilder newPlanBuilder(
+      RelationPlan plan,
+      Analysis analysis,
+      Map<ScopeAware<Expression>, Symbol> mappings,
+      Session session) {
+    return new PlanBuilder(plan.getRoot());
+  }
+
+  public PlanBuilder withNewRoot(PlanNode root) {
+    return new PlanBuilder(root);
+  }
+
+  public PlanBuilder withScope(Scope scope, List<Symbol> fields) {
+    return new PlanBuilder(root);
+  }
+
+  public PlanNode getRoot() {
+    return root;
+  }
+
+  public <T extends Expression> PlanBuilder appendProjections(
+      Iterable<T> expressions, SymbolAllocator symbolAllocator, QueryId idAllocator) {
+    Assignments.Builder projections = Assignments.builder();
+
+    // add an identity projection for underlying plan
+    // TODO needed?
+    // projections.putIdentities(root.getOutputSymbols());
+
+    Map<ScopeAware<Expression>, Symbol> mappings = new HashMap<>();
+    //        for (T expression : expressions) {
+    //            // Skip any expressions that have already been translated and recorded in the
+    // translation map, or that are duplicated in the list of exp
+    //            if (!mappings.containsKey(scopeAwareKey(expression, translations.getAnalysis(),
+    //                    translations.getScope())) && !alreadyHasTranslation.test(translations,
+    // expression)) {
+    //                Symbol symbol = symbolAllocator.newSymbol("expr",
+    // translations.getAnalysis().getType(expression));
+    //                projections.put(symbol, rewriter.apply(translations, expression));
+    //                mappings.put(scopeAwareKey(expression, translations.getAnalysis(),
+    // translations.getScope()), symbol);
+    //            }
+    //        }
+
+    return new PlanBuilder(new ProjectNode(idAllocator.genPlanNodeId(), root, projections.build()));
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java
index 10b1f2fdbd2..fde2f1530ae 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/QueryPlanner.java
@@ -13,1029 +13,298 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.planner;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
 import org.apache.iotdb.db.queryengine.common.QueryId;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.NodeRef;
 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.SortNode;
 import org.apache.iotdb.db.relational.sql.tree.Delete;
 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.Offset;
+import org.apache.iotdb.db.relational.sql.tree.OrderBy;
 import org.apache.iotdb.db.relational.sql.tree.Query;
 import org.apache.iotdb.db.relational.sql.tree.QuerySpecification;
-import org.apache.iotdb.db.relational.sql.tree.Relation;
-import org.apache.iotdb.db.relational.sql.tree.Union;
+import org.apache.iotdb.db.relational.sql.tree.SortItem;
 import org.apache.iotdb.session.Session;
+import org.apache.iotdb.tsfile.read.common.type.Type;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.ImmutableMap.toImmutableMap;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static java.lang.String.format;
 import static java.util.Objects.requireNonNull;
+import static org.apache.iotdb.db.queryengine.plan.relational.planner.OrderingTranslator.sortItemToSortOrder;
 import static org.apache.iotdb.db.queryengine.plan.relational.planner.PlanBuilder.newPlanBuilder;
 
-class QueryPlanner
-{
-    private static final int MAX_BIGINT_PRECISION = 19;
-    private final Analysis analysis;
-    private final SymbolAllocator symbolAllocator;
-    private final QueryId idAllocator;
-    private final Map<NodeRef<LambdaArgumentDeclaration>, Symbol> lambdaDeclarationToSymbolMap;
-    private final PlannerContext plannerContext;
-    private final Session session;
-    private final SubqueryPlanner subqueryPlanner;
-    private final Map<NodeRef<Node>, RelationPlan> recursiveSubqueries;
-
-    QueryPlanner(
-            Analysis analysis,
-            SymbolAllocator symbolAllocator,
-            QueryId idAllocator,
-            Session session,
-            Map<NodeRef<Node>, RelationPlan> recursiveSubqueries) {
-        requireNonNull(analysis, "analysis is null");
-        requireNonNull(symbolAllocator, "symbolAllocator is null");
-        requireNonNull(idAllocator, "idAllocator is null");
-        requireNonNull(session, "session is null");
-        requireNonNull(recursiveSubqueries, "recursiveSubqueries is null");
-
-        this.analysis = analysis;
-        this.symbolAllocator = symbolAllocator;
-        this.idAllocator = idAllocator;
-        this.session = session;
-        this.subqueryPlanner = null;
-        this.recursiveSubqueries = recursiveSubqueries;
-    }
-
-    public RelationPlan plan(Query query) {
-        PlanBuilder builder = planQueryBody(query);
-
-        List<Expression> orderBy = analysis.getOrderByExpressions(query);
-        // builder = subqueryPlanner.handleSubqueries(builder, orderBy, analysis.getSubqueries(query));
-
-        List<Analysis.SelectExpression> selectExpressions = analysis.getSelectExpressions(query);
-        List<Expression> outputs = selectExpressions.stream()
-                .map(SelectExpression::getExpression)
-                .collect(toImmutableList());
-        builder = builder.appendProjections(Iterables.concat(orderBy, outputs), symbolAllocator, idAllocator);
-
-        Optional<OrderingScheme> orderingScheme = orderingScheme(builder, query.getOrderBy(), analysis.getOrderByExpressions(query));
-        builder = sort(builder, orderingScheme);
-        builder = offset(builder, query.getOffset());
-        builder = limit(builder, query.getLimit(), orderingScheme);
-        builder = builder.appendProjections(outputs, symbolAllocator, idAllocator);
-
-        return new RelationPlan(
-                builder.getRoot(),
-                analysis.getScope(query),
-                computeOutputs(builder, outputs));
-    }
-
-    public RelationPlan planExpand(Query query) {
-        checkArgument(analysis.isExpandableQuery(query), "query is not registered as expandable");
-
-        Union union = (Union) query.getQueryBody();
-        ImmutableList.Builder<NodeAndMappings> recursionSteps = ImmutableList.builder();
-
-        // plan anchor relation
-        Relation anchorNode = union.getRelations().get(0);
-        RelationPlan anchorPlan = new RelationPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
-                .process(anchorNode, null);
-
-        // prune anchor plan outputs to contain only the symbols exposed in the scope
-        NodeAndMappings prunedAnchorPlan = pruneInvisibleFields(anchorPlan, idAllocator);
-
-        // if the anchor plan has duplicate output symbols, add projection on top to make the symbols unique
-        // This is necessary to successfully unroll recursion: the recursion step relation must follow
-        // the same layout while it might not have duplicate outputs where the anchor plan did
-        NodeAndMappings disambiguatedAnchorPlan = disambiguateOutputs(prunedAnchorPlan, symbolAllocator, idAllocator);
-        anchorPlan = new RelationPlan(disambiguatedAnchorPlan.getNode(), analysis.getScope(query), disambiguatedAnchorPlan.getFields());
-
-        recursionSteps.add(copy(anchorPlan.getRoot(), anchorPlan.getFieldMappings()));
-
-        // plan recursion step
-        Relation recursionStepRelation = union.getRelations().get(1);
-        RelationPlan recursionStepPlan = new RelationPlanner(
-                analysis,
-                symbolAllocator,
-                idAllocator,
-                session,
-                ImmutableMap.of(NodeRef.of(analysis.getRecursiveReference(query)), anchorPlan))
-                .process(recursionStepRelation, null);
-
-        // coerce recursion step outputs and prune them to contain only the symbols exposed in the scope
-        NodeAndMappings coercedRecursionStep;
-        List<Type> types = analysis.getRelationCoercion(recursionStepRelation);
-        if (types == null) {
-            coercedRecursionStep = pruneInvisibleFields(recursionStepPlan, idAllocator);
-        }
-        else {
-            coercedRecursionStep = coerce(recursionStepPlan, types, symbolAllocator, idAllocator);
-        }
-
-        NodeAndMappings replacementSpot = new NodeAndMappings(anchorPlan.getRoot(), anchorPlan.getFieldMappings());
-        PlanNode recursionStep = coercedRecursionStep.getNode();
-        List<Symbol> mappings = coercedRecursionStep.getFields();
-
-        // unroll recursion
-        int maxRecursionDepth = getMaxRecursionDepth(session);
-        for (int i = 0; i < maxRecursionDepth; i++) {
-            recursionSteps.add(copy(recursionStep, mappings));
-            NodeAndMappings replacement = copy(recursionStep, mappings);
-
-            // if the recursion step plan has duplicate output symbols, add projection on top to make the symbols unique
-            // This is necessary to successfully unroll recursion: the relation on the next recursion step must follow
-            // the same layout while it might not have duplicate outputs where the plan for this step did
-            replacement = disambiguateOutputs(replacement, symbolAllocator, idAllocator);
-            recursionStep = replace(recursionStep, replacementSpot, replacement);
-            replacementSpot = replacement;
-        }
-
-        // after the last recursion step, check if the recursion converged. the last step is expected to return empty result
-        // 1. append window to count rows
-        NodeAndMappings checkConvergenceStep = copy(recursionStep, mappings);
-        Symbol countSymbol = symbolAllocator.newSymbol("count", BIGINT);
-        ResolvedFunction function = .resolveBuiltinFunction("count", ImmutableList.of());
-        WindowNode.Function countFunction = new WindowNode.Function(function, ImmutableList.of(), DEFAULT_FRAME, false);
-
-        WindowNode windowNode = new WindowNode(
-                idAllocator.getNextId(),
-                checkConvergenceStep.getNode(),
-                new DataOrganizationSpecification(ImmutableList.of(), Optional.empty()),
-                ImmutableMap.of(countSymbol, countFunction),
-                Optional.empty(),
-                ImmutableSet.of(),
-                0);
-
-        // 2. append filter to fail on non-empty result
-        String recursionLimitExceededMessage = format("Recursion depth limit exceeded (%s). Use 'max_recursion_depth' session property to modify the limit.", maxRecursionDepth);
-        Expression predicate = ifExpression(
-                new ComparisonExpression(
-                        GREATER_THAN_OR_EQUAL,
-                        countSymbol.toSymbolReference(),
-                        new Constant(BIGINT, 0L)),
-                new Cast(
-                        failFunction(plannerContext.getMetadata(), NOT_SUPPORTED, recursionLimitExceededMessage),
-                        BOOLEAN),
-                TRUE_LITERAL);
-        FilterNode filterNode = new FilterNode(idAllocator.getNextId(), windowNode, predicate);
-
-        recursionSteps.add(new NodeAndMappings(filterNode, checkConvergenceStep.getFields()));
-
-        // union all the recursion steps
-        List<NodeAndMappings> recursionStepsToUnion = recursionSteps.build();
-
-        List<Symbol> unionOutputSymbols = anchorPlan.getFieldMappings().stream()
-                .map(symbol -> symbolAllocator.newSymbol(symbol, "_expanded"))
-                .collect(toImmutableList());
-
-        ImmutableListMultimap.Builder<Symbol, Symbol> unionSymbolMapping = ImmutableListMultimap.builder();
-        for (NodeAndMappings plan : recursionStepsToUnion) {
-            for (int i = 0; i < unionOutputSymbols.size(); i++) {
-                unionSymbolMapping.put(unionOutputSymbols.get(i), plan.getFields().get(i));
-            }
-        }
-
-        List<PlanNode> nodesToUnion = recursionStepsToUnion.stream()
-                .map(NodeAndMappings::getNode)
-                .collect(toImmutableList());
-
-        PlanNode result = new UnionNode(idAllocator.getNextId(), nodesToUnion, unionSymbolMapping.build(), unionOutputSymbols);
-
-        if (union.isDistinct()) {
-            result = singleAggregation(
-                    idAllocator.getNextId(),
-                    result,
-                    ImmutableMap.of(),
-                    singleGroupingSet(result.getOutputSymbols()));
-        }
-
-        return new RelationPlan(result, anchorPlan.getScope(), unionOutputSymbols, outerContext);
-    }
-
-    // Return a copy of the plan and remapped field mappings. In the copied plan:
-    // - all PlanNodeIds are replaced with new values,
-    // - all symbols are replaced with new symbols.
-    // Copying the plan might reorder symbols. The returned field mappings keep the original
-    // order and might be used to identify the original output symbols with their copies.
-    private NodeAndMappings copy(PlanNode plan, List<Symbol> fields)
-    {
-        return PlanCopier.copyPlan(plan, fields, symbolAllocator, idAllocator);
-    }
-
-    private PlanNode replace(PlanNode plan, NodeAndMappings replacementSpot, NodeAndMappings replacement)
-    {
-        checkArgument(
-                replacementSpot.getFields().size() == replacement.getFields().size(),
-                "mismatching outputs in replacement, expected: %s, got: %s",
-                replacementSpot.getFields().size(),
-                replacement.getFields().size());
-
-        return SimplePlanRewriter.rewriteWith(new SimplePlanRewriter<Void>()
-        {
-            @Override
-            protected PlanNode visitPlan(PlanNode node, RewriteContext<Void> context)
-            {
-                return node.replaceChildren(node.getSources().stream()
-                        .map(child -> {
-                            if (child == replacementSpot.getNode()) {
-                                // add projection to adjust symbols
-                                Assignments.Builder assignments = Assignments.builder();
-                                for (int i = 0; i < replacementSpot.getFields().size(); i++) {
-                                    assignments.put(replacementSpot.getFields().get(i), replacement.getFields().get(i).toSymbolReference());
-                                }
-                                return new ProjectNode(idAllocator.getNextId(), replacement.getNode(), assignments.build());
-                            }
-                            return context.rewrite(child);
-                        })
-                        .collect(toImmutableList()));
-            }
-        }, plan, null);
-    }
-
-    public RelationPlan plan(QuerySpecification node) {
-        PlanBuilder builder = planFrom(node);
-
-        builder = filter(builder, analysis.getWhere(node), node);
-        // builder = aggregate(builder, node);
-        builder = filter(builder, analysis.getHaving(node), node);
-        // builder = planWindowFunctions(node, builder, ImmutableList.copyOf(analysis.getWindowFunctions(node)));
-        // builder = planWindowMeasures(node, builder, ImmutableList.copyOf(analysis.getWindowMeasures(node)));
-
-        List<Analysis.SelectExpression> selectExpressions = analysis.getSelectExpressions(node);
-        List<Expression> expressions = selectExpressions.stream()
-                .map(SelectExpression::getExpression)
-                .collect(toImmutableList());
-        builder = subqueryPlanner.handleSubqueries(builder, expressions, analysis.getSubqueries(node));
-
-        if (hasExpressionsToUnfold(selectExpressions)) {
-            // pre-project the folded expressions to preserve any non-deterministic semantics of functions that might be referenced
-            builder = builder.appendProjections(expressions, symbolAllocator, idAllocator);
-        }
-
-        List<Expression> outputs = outputExpressions(selectExpressions);
-        if (node.getOrderBy().isPresent()) {
-            // ORDER BY requires outputs of SELECT to be visible.
-            // For queries with aggregation, it also requires grouping keys and translated aggregations.
-            if (analysis.isAggregation(node)) {
-                // Add projections for aggregations required by ORDER BY. After this step, grouping keys and translated
-                // aggregations are visible.
-                List<Expression> orderByAggregates = analysis.getOrderByAggregates(node.getOrderBy().get());
-                builder = builder.appendProjections(orderByAggregates, symbolAllocator, idAllocator);
-            }
-
-            // Add projections for the outputs of SELECT, but stack them on top of the ones from the FROM clause so both are visible
-            // when resolving the ORDER BY clause.
-            builder = builder.appendProjections(outputs, symbolAllocator, idAllocator);
-
-            // The new scope is the composite of the fields from the FROM and SELECT clause (local nested scopes). Fields from the bottom of
-            // the scope stack need to be placed first to match the expected layout for nested scopes.
-            List<Symbol> newFields = new ArrayList<>();
-            newFields.addAll(builder.getTranslations().getFieldSymbols());
-
-            outputs.stream()
-                    .map(builder::translate)
-                    .forEach(newFields::add);
-
-            builder = builder.withScope(analysis.getScope(node.getOrderBy().get()), newFields);
-
-            builder = planWindowFunctions(node, builder, ImmutableList.copyOf(analysis.getOrderByWindowFunctions(node.getOrderBy().get())));
-            builder = planWindowMeasures(node, builder, ImmutableList.copyOf(analysis.getOrderByWindowMeasures(node.getOrderBy().get())));
-        }
-
-        List<io.trino.sql.tree.Expression> orderBy = analysis.getOrderByExpressions(node);
-        builder = subqueryPlanner.handleSubqueries(builder, orderBy, analysis.getSubqueries(node));
-        builder = builder.appendProjections(Iterables.concat(orderBy, outputs), symbolAllocator, idAllocator);
-
-        builder = distinct(builder, node, outputs);
-        Optional<OrderingScheme> orderingScheme = orderingScheme(builder, node.getOrderBy(), analysis.getOrderByExpressions(node));
-        builder = sort(builder, orderingScheme);
-        builder = offset(builder, node.getOffset());
-        builder = limit(builder, node.getLimit(), orderingScheme);
-        builder = builder.appendProjections(outputs, symbolAllocator, idAllocator);
-
-        return new RelationPlan(
-                builder.getRoot(),
-                analysis.getScope(node),
-                computeOutputs(builder, outputs),
-                outerContext);
-    }
-
-    private static boolean hasExpressionsToUnfold(List<Analysis.SelectExpression> selectExpressions)
-    {
-        return selectExpressions.stream()
-                .map(SelectExpression::getUnfoldedExpressions)
-                .anyMatch(Optional::isPresent);
-    }
-
-    private static List<Expression> outputExpressions(List<Analysis.SelectExpression> selectExpressions)
-    {
-        ImmutableList.Builder<Expression> result = ImmutableList.builder();
-        for (Analysis.SelectExpression selectExpression : selectExpressions) {
-            if (selectExpression.getUnfoldedExpressions().isPresent()) {
-                result.addAll(selectExpression.getUnfoldedExpressions().get());
-            }
-            else {
-                result.add(selectExpression.getExpression());
-            }
-        }
-        return result.build();
-    }
-
-    public PlanNode plan(Delete node) {
-        // implement delete logic
-        return null;
-    }
-
-    private static List<Symbol> computeOutputs(PlanBuilder builder, List<Expression> outputExpressions) {
-        ImmutableList.Builder<Symbol> outputSymbols = ImmutableList.builder();
-        for (Expression expression : outputExpressions) {
-            outputSymbols.add(builder.translate(expression));
-        }
-        return outputSymbols.build();
-    }
-
-    private PlanBuilder planQueryBody(Query query) {
-        RelationPlan relationPlan = new RelationPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
-                .process(query.getQueryBody(), null);
-
-        return newPlanBuilder(relationPlan, analysis, session);
-    }
-
-    private PlanBuilder planFrom(QuerySpecification node) {
-        if (node.getFrom().isPresent()) {
-            RelationPlan relationPlan = new RelationPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
-                    .process(node.getFrom().get(), null);
-            return newPlanBuilder(relationPlan, analysis, session);
-        }
-
-        return new PlanBuilder(
-                new TranslationMap(outerContext, analysis.getImplicitFromScope(node), analysis, lambdaDeclarationToSymbolMap, ImmutableList.of(), session, plannerContext),
-                new ValuesNode(idAllocator.getNextId(), 1));
-    }
-
-    private PlanBuilder filter(PlanBuilder subPlan, Expression predicate, Node node)
-    {
-        if (predicate == null) {
-            return subPlan;
-        }
-
-        // subPlan = subqueryPlanner.handleSubqueries(subPlan, predicate, analysis.getSubqueries(node));
-
-        return subPlan.withNewRoot(new FilterNode(idAllocator.getNextId(), subPlan.getRoot(), coerceIfNecessary(analysis, predicate, subPlan.rewrite(predicate))));
-    }
-
-    private <T extends io.trino.sql.tree.Expression> List<T> scopeAwareDistinct(PlanBuilder subPlan, List<T> expressions)
-    {
-        return expressions.stream()
-                .map(function -> scopeAwareKey(function, analysis, subPlan.getScope()))
-                .distinct()
-                .map(ScopeAware::getNode)
-                .collect(toImmutableList());
-    }
-
-    private FrameBoundPlanAndSymbols planFrameBound(PlanBuilder subPlan, PlanAndMappings coercions, Optional<io.trino.sql.tree.Expression> frameOffset, ResolvedWindow window, Map<Type, Symbol> sortKeyCoercions)
-    {
-        Optional<ResolvedFunction> frameBoundCalculationFunction = frameOffset.map(analysis::getFrameBoundCalculation);
-
-        // Empty frameBoundCalculationFunction indicates that frame bound type is CURRENT ROW or UNBOUNDED.
-        // Handling it doesn't require any additional symbols.
-        if (frameBoundCalculationFunction.isEmpty()) {
-            return new FrameBoundPlanAndSymbols(subPlan, Optional.empty(), Optional.empty());
-        }
-
-        // Present frameBoundCalculationFunction indicates that frame bound type is <expression> PRECEDING or <expression> FOLLOWING.
-        // It requires adding certain projections to the plan so that the operator can determine frame bounds.
-
-        // First, append filter to validate offset values. They mustn't be negative or null.
-        Symbol offsetSymbol = coercions.get(frameOffset.get());
-        Expression zeroOffset = zeroOfType(symbolAllocator.getTypes().get(offsetSymbol));
-        Expression predicate = ifExpression(
-                new ComparisonExpression(
-                        GREATER_THAN_OR_EQUAL,
-                        offsetSymbol.toSymbolReference(),
-                        zeroOffset),
-                TRUE_LITERAL,
-                new Cast(
-                        failFunction(plannerContext.getMetadata(), INVALID_WINDOW_FRAME, "Window frame offset value must not be negative or null"),
-                        BOOLEAN));
-        subPlan = subPlan.withNewRoot(new FilterNode(
-                idAllocator.getNextId(),
-                subPlan.getRoot(),
-                predicate));
-
-        // Then, coerce the sortKey so that we can add / subtract the offset.
-        // Note: for that we cannot rely on the usual mechanism of using the coerce() method. The coerce() method can only handle one coercion for a node,
-        // while the sortKey node might require several different coercions, e.g. one for frame start and one for frame end.
-        io.trino.sql.tree.Expression sortKey = Iterables.getOnlyElement(window.getOrderBy().orElseThrow().getSortItems()).getSortKey();
-        Symbol sortKeyCoercedForFrameBoundCalculation = coercions.get(sortKey);
-        Optional<Type> coercion = frameOffset.map(analysis::getSortKeyCoercionForFrameBoundCalculation);
-        if (coercion.isPresent()) {
-            Type expectedType = coercion.get();
-            Symbol alreadyCoerced = sortKeyCoercions.get(expectedType);
-            if (alreadyCoerced != null) {
-                sortKeyCoercedForFrameBoundCalculation = alreadyCoerced;
-            }
-            else {
-                Expression cast = new Cast(
-                        coercions.get(sortKey).toSymbolReference(),
-                        expectedType,
-                        false);
-                sortKeyCoercedForFrameBoundCalculation = symbolAllocator.newSymbol(cast, expectedType);
-                sortKeyCoercions.put(expectedType, sortKeyCoercedForFrameBoundCalculation);
-                subPlan = subPlan.withNewRoot(new ProjectNode(
-                        idAllocator.getNextId(),
-                        subPlan.getRoot(),
-                        Assignments.builder()
-                                .putIdentities(subPlan.getRoot().getOutputSymbols())
-                                .put(sortKeyCoercedForFrameBoundCalculation, cast)
-                                .build()));
-            }
-        }
-
-        // Next, pre-project the function which combines sortKey with the offset.
-        // Note: if frameOffset needs a coercion, it was added before by a call to coerce() method.
-        ResolvedFunction function = frameBoundCalculationFunction.get();
-        Expression functionCall = new FunctionCall(
-                function,
-                ImmutableList.of(
-                        sortKeyCoercedForFrameBoundCalculation.toSymbolReference(),
-                        offsetSymbol.toSymbolReference()));
-        Symbol frameBoundSymbol = symbolAllocator.newSymbol(functionCall, function.getSignature().getReturnType());
-        subPlan = subPlan.withNewRoot(new ProjectNode(
-                idAllocator.getNextId(),
-                subPlan.getRoot(),
-                Assignments.builder()
-                        .putIdentities(subPlan.getRoot().getOutputSymbols())
-                        .put(frameBoundSymbol, functionCall)
-                        .build()));
-
-        // Finally, coerce the sortKey to the type of frameBound so that the operator can perform comparisons on them
-        Optional<Symbol> sortKeyCoercedForFrameBoundComparison = Optional.of(coercions.get(sortKey));
-        coercion = frameOffset.map(analysis::getSortKeyCoercionForFrameBoundComparison);
-        if (coercion.isPresent()) {
-            Type expectedType = coercion.get();
-            Symbol alreadyCoerced = sortKeyCoercions.get(expectedType);
-            if (alreadyCoerced != null) {
-                sortKeyCoercedForFrameBoundComparison = Optional.of(alreadyCoerced);
-            }
-            else {
-                Expression cast = new Cast(
-                        coercions.get(sortKey).toSymbolReference(),
-                        expectedType,
-                        false);
-                Symbol castSymbol = symbolAllocator.newSymbol(cast, expectedType);
-                sortKeyCoercions.put(expectedType, castSymbol);
-                subPlan = subPlan.withNewRoot(new ProjectNode(
-                        idAllocator.getNextId(),
-                        subPlan.getRoot(),
-                        Assignments.builder()
-                                .putIdentities(subPlan.getRoot().getOutputSymbols())
-                                .put(castSymbol, cast)
-                                .build()));
-                sortKeyCoercedForFrameBoundComparison = Optional.of(castSymbol);
-            }
-        }
-
-        return new FrameBoundPlanAndSymbols(subPlan, Optional.of(frameBoundSymbol), sortKeyCoercedForFrameBoundComparison);
-    }
-
-    private FrameOffsetPlanAndSymbol planFrameOffset(PlanBuilder subPlan, Optional<Symbol> frameOffset)
-    {
-        if (frameOffset.isEmpty()) {
-            return new FrameOffsetPlanAndSymbol(subPlan, Optional.empty());
-        }
-
-        Symbol offsetSymbol = frameOffset.get();
-        Type offsetType = symbolAllocator.getTypes().get(offsetSymbol);
-
-        // Append filter to validate offset values. They mustn't be negative or null.
-        Expression zeroOffset = zeroOfType(offsetType);
-        Expression predicate = ifExpression(
-                new ComparisonExpression(GREATER_THAN_OR_EQUAL, offsetSymbol.toSymbolReference(), zeroOffset),
-                TRUE_LITERAL,
-                new Cast(
-                        failFunction(plannerContext.getMetadata(), INVALID_WINDOW_FRAME, "Window frame offset value must not be negative or null"),
-                        BOOLEAN));
-        subPlan = subPlan.withNewRoot(new FilterNode(
-                idAllocator.getNextId(),
-                subPlan.getRoot(),
-                predicate));
-
-        if (offsetType.equals(BIGINT)) {
-            return new FrameOffsetPlanAndSymbol(subPlan, Optional.of(offsetSymbol));
-        }
-
-        Expression offsetToBigint;
-
-        if (offsetType instanceof DecimalType decimalType && !decimalType.isShort()) {
-            int actualPrecision = decimalType.getPrecision();
-
-            if (actualPrecision < MAX_BIGINT_PRECISION) {
-                offsetToBigint = new Cast(offsetSymbol.toSymbolReference(), BIGINT);
-            }
-            else if (actualPrecision > MAX_BIGINT_PRECISION) {
-                // If the offset value exceeds max bigint, it implies that the frame bound falls beyond the partition bound.
-                // In such case, the frame bound is set to the partition bound. Passing max bigint as the offset value has
-                // the same effect. The offset value can be truncated to max bigint for the purpose of cast.
-                offsetToBigint = new Constant(BIGINT, Long.MAX_VALUE);
-            }
-            else {
-                offsetToBigint = ifExpression(
-                        new ComparisonExpression(LESS_THAN_OR_EQUAL, offsetSymbol.toSymbolReference(), new Constant(decimalType, Int128.valueOf(Long.MAX_VALUE))),
-                        new Cast(offsetSymbol.toSymbolReference(), BIGINT),
-                        new Constant(BIGINT, Long.MAX_VALUE));
-            }
-        }
-        else {
-            offsetToBigint = new Cast(
-                    offsetSymbol.toSymbolReference(),
-                    BIGINT,
-                    false);
-        }
-
-        Symbol coercedOffsetSymbol = symbolAllocator.newSymbol(offsetToBigint, BIGINT);
-        subPlan = subPlan.withNewRoot(new ProjectNode(
-                idAllocator.getNextId(),
-                subPlan.getRoot(),
-                Assignments.builder()
-                        .putIdentities(subPlan.getRoot().getOutputSymbols())
-                        .put(coercedOffsetSymbol, offsetToBigint)
-                        .build()));
-
-        return new FrameOffsetPlanAndSymbol(subPlan, Optional.of(coercedOffsetSymbol));
-    }
-
-    private static Expression zeroOfType(Type type)
-    {
-        if (type.equals(BIGINT) ||
-                type.equals(INTEGER) ||
-                type.equals(SMALLINT) ||
-                type.equals(TINYINT) ||
-                type.equals(INTERVAL_DAY_TIME) ||
-                type.equals(INTERVAL_YEAR_MONTH)) {
-            return new Constant(type, 0L);
-        }
-
-        if (type.equals(DOUBLE)) {
-            return new Constant(DOUBLE, 0.0);
-        }
-
-        if (type instanceof DecimalType decimal) {
-            if (decimal.isShort()) {
-                return new Constant(type, 0L);
-            }
-
-            return new Constant(type, Int128.valueOf(0));
-        }
-
-        if (type.equals(REAL)) {
-            return new Constant(type, Reals.toReal(0));
-        }
-
-        throw new IllegalArgumentException("unexpected type: " + type);
-    }
-
-    private FrameBoundType mapFrameBoundType(FrameBound.Type type)
-    {
-        return switch (type) {
-            case UNBOUNDED_PRECEDING -> FrameBoundType.UNBOUNDED_PRECEDING;
-            case PRECEDING -> FrameBoundType.PRECEDING;
-            case CURRENT_ROW -> CURRENT_ROW;
-            case FOLLOWING -> FrameBoundType.FOLLOWING;
-            case UNBOUNDED_FOLLOWING -> FrameBoundType.UNBOUNDED_FOLLOWING;
-        };
-    }
-
-    public static DataOrganizationSpecification planWindowSpecification(List<io.trino.sql.tree.Expression> partitionBy, Optional<OrderBy> orderBy, Function<io.trino.sql.tree.Expression, Symbol> expressionRewrite)
-    {
-        // Rewrite PARTITION BY
-        ImmutableList.Builder<Symbol> partitionBySymbols = ImmutableList.builder();
-        for (io.trino.sql.tree.Expression expression : partitionBy) {
-            partitionBySymbols.add(expressionRewrite.apply(expression));
-        }
-
-        // Rewrite ORDER BY
-        LinkedHashMap<Symbol, SortOrder> orderings = new LinkedHashMap<>();
-        for (SortItem item : getSortItemsFromOrderBy(orderBy)) {
-            Symbol symbol = expressionRewrite.apply(item.getSortKey());
-            // don't override existing keys, i.e. when "ORDER BY a ASC, a DESC" is specified
-            orderings.putIfAbsent(symbol, sortItemToSortOrder(item));
-        }
-
-        Optional<OrderingScheme> orderingScheme = Optional.empty();
-        if (!orderings.isEmpty()) {
-            orderingScheme = Optional.of(new OrderingScheme(ImmutableList.copyOf(orderings.keySet()), orderings));
-        }
-
-        return new DataOrganizationSpecification(partitionBySymbols.build(), orderingScheme);
-    }
-
-    private PlanBuilder planWindowMeasures(Node node, PlanBuilder subPlan, List<WindowOperation> windowMeasures)
-    {
-        if (windowMeasures.isEmpty()) {
-            return subPlan;
-        }
-
-        for (WindowOperation windowMeasure : scopeAwareDistinct(subPlan, windowMeasures)) {
-            ResolvedWindow window = analysis.getWindow(windowMeasure);
-            checkState(window != null, "no resolved window for: " + windowMeasure);
-
-            // pre-project inputs
-            ImmutableList.Builder<io.trino.sql.tree.Expression> inputsBuilder = ImmutableList.<io.trino.sql.tree.Expression>builder()
-                    .addAll(window.getPartitionBy())
-                    .addAll(getSortItemsFromOrderBy(window.getOrderBy()).stream()
-                            .map(SortItem::getSortKey)
-                            .iterator());
-            WindowFrame frame = window.getFrame().orElseThrow();
-            Optional<io.trino.sql.tree.Expression> endValue = frame.getEnd().orElseThrow().getValue();
-
-            List<io.trino.sql.tree.Expression> inputs = inputsBuilder.build();
-
-            subPlan = subqueryPlanner.handleSubqueries(subPlan, inputs, analysis.getSubqueries(node));
-            subPlan = subPlan.appendProjections(inputs, symbolAllocator, idAllocator);
-
-            // Add projection for frame end, since WindowNode expects a symbol and does not support literals
-            // We don't use appendProjects because we don't want a mapping to be added for the literal
-            Optional<Symbol> endValueSymbol = Optional.empty();
-            if (endValue.isPresent()) {
-                io.trino.sql.tree.Expression expression = endValue.get();
-                Assignments.Builder assignments = Assignments.builder();
-                assignments.putIdentities(subPlan.getRoot().getOutputSymbols());
-                Symbol symbol = symbolAllocator.newSymbol("end", analysis.getType(expression));
-                assignments.put(symbol, subPlan.rewrite(expression));
-
-                endValueSymbol = Optional.of(symbol);
-                subPlan = subPlan.withNewRoot(new ProjectNode(idAllocator.getNextId(), subPlan.getRoot(), assignments.build()));
-            }
-
-            // process frame end
-            FrameOffsetPlanAndSymbol plan = planFrameOffset(subPlan, endValueSymbol);
-            subPlan = plan.getSubPlan();
-            Optional<Symbol> frameEnd = plan.getFrameOffsetSymbol();
-
-            subPlan = subqueryPlanner.handleSubqueries(subPlan, extractPatternRecognitionExpressions(frame.getVariableDefinitions(), frame.getMeasures()), analysis.getSubqueries(node));
-            subPlan = planPatternRecognition(subPlan, windowMeasure, window, frameEnd);
-        }
-
-        return subPlan;
-    }
-
-    public static List<io.trino.sql.tree.Expression> extractPatternRecognitionExpressions(List<VariableDefinition> variableDefinitions, List<MeasureDefinition> measureDefinitions)
-    {
-        ImmutableList.Builder<io.trino.sql.tree.Expression> expressions = ImmutableList.builder();
-
-        variableDefinitions.stream()
-                .map(VariableDefinition::getExpression)
-                .forEach(expressions::add);
-
-        measureDefinitions.stream()
-                .map(MeasureDefinition::getExpression)
-                .forEach(expressions::add);
-
-        return expressions.build();
-    }
-
-    /**
-     * Creates a projection with any additional coercions by identity of the provided expressions.
-     *
-     * @return the new subplan and a mapping of each expression to the symbol representing the coercion or an existing symbol if a coercion wasn't needed
-     */
-    public static PlanAndMappings coerce(PlanBuilder subPlan, List<io.trino.sql.tree.Expression> expressions, Analysis analysis, PlanNodeIdAllocator idAllocator, SymbolAllocator symbolAllocator)
-    {
-        Assignments.Builder assignments = Assignments.builder();
-        assignments.putIdentities(subPlan.getRoot().getOutputSymbols());
-
-        Map<NodeRef<io.trino.sql.tree.Expression>, Symbol> mappings = new HashMap<>();
-        for (io.trino.sql.tree.Expression expression : expressions) {
-            Type coercion = analysis.getCoercion(expression);
-
-            // expressions may be repeated, for example, when resolving ordinal references in a GROUP BY clause
-            if (!mappings.containsKey(NodeRef.of(expression))) {
-                if (coercion != null) {
-                    Symbol symbol = symbolAllocator.newSymbol("expr", coercion);
-
-                    assignments.put(symbol, new Cast(
-                            subPlan.rewrite(expression),
-                            coercion,
-                            false));
-
-                    mappings.put(NodeRef.of(expression), symbol);
-                }
-                else {
-                    mappings.put(NodeRef.of(expression), subPlan.translate(expression));
-                }
-            }
-        }
-
-        subPlan = subPlan.withNewRoot(
-                new ProjectNode(
-                        idAllocator.getNextId(),
-                        subPlan.getRoot(),
-                        assignments.build()));
-
-        return new PlanAndMappings(subPlan, mappings);
-    }
-
-    public static Expression coerceIfNecessary(Analysis analysis, io.trino.sql.tree.Expression original, Expression rewritten)
-    {
-        Type coercion = analysis.getCoercion(original);
-        if (coercion == null) {
-            return rewritten;
-        }
-
-        return new Cast(rewritten, coercion, false);
-    }
-
-    public static NodeAndMappings coerce(RelationPlan plan, List<Type> types, SymbolAllocator symbolAllocator, PlanNodeIdAllocator idAllocator)
-    {
-        List<Symbol> visibleFields = visibleFields(plan);
-        checkArgument(visibleFields.size() == types.size());
-
-        Assignments.Builder assignments = Assignments.builder();
-        ImmutableList.Builder<Symbol> mappings = ImmutableList.builder();
-        for (int i = 0; i < types.size(); i++) {
-            Symbol input = visibleFields.get(i);
-            Type type = types.get(i);
-
-            if (!symbolAllocator.getTypes().get(input).equals(type)) {
-                Symbol coerced = symbolAllocator.newSymbol(input.getName(), type);
-                assignments.put(coerced, new Cast(input.toSymbolReference(), type));
-                mappings.add(coerced);
-            }
-            else {
-                assignments.putIdentity(input);
-                mappings.add(input);
-            }
-        }
-
-        ProjectNode coerced = new ProjectNode(idAllocator.getNextId(), plan.getRoot(), assignments.build());
-        return new NodeAndMappings(coerced, mappings.build());
-    }
-
-    public static List<Symbol> visibleFields(RelationPlan subPlan)
-    {
-        RelationType descriptor = subPlan.getDescriptor();
-        return descriptor.getAllFields().stream()
-                .filter(field -> !field.isHidden())
-                .map(descriptor::indexOf)
-                .map(subPlan.getFieldMappings()::get)
-                .collect(toImmutableList());
-    }
-
-    public static NodeAndMappings pruneInvisibleFields(RelationPlan plan, PlanNodeIdAllocator idAllocator)
-    {
-        List<Symbol> visibleFields = visibleFields(plan);
-        ProjectNode pruned = new ProjectNode(idAllocator.getNextId(), plan.getRoot(), Assignments.identity(visibleFields));
-        return new NodeAndMappings(pruned, visibleFields);
-    }
-
-    public static NodeAndMappings disambiguateOutputs(NodeAndMappings plan, SymbolAllocator symbolAllocator, PlanNodeIdAllocator idAllocator)
-    {
-        Set<Symbol> distinctOutputs = ImmutableSet.copyOf(plan.getFields());
-
-        if (distinctOutputs.size() < plan.getFields().size()) {
-            Assignments.Builder assignments = Assignments.builder();
-            ImmutableList.Builder<Symbol> newOutputs = ImmutableList.builder();
-            Set<Symbol> uniqueOutputs = new HashSet<>();
-
-            for (Symbol output : plan.getFields()) {
-                if (uniqueOutputs.add(output)) {
-                    assignments.putIdentity(output);
-                    newOutputs.add(output);
-                }
-                else {
-                    Symbol newOutput = symbolAllocator.newSymbol(output);
-                    assignments.put(newOutput, output.toSymbolReference());
-                    newOutputs.add(newOutput);
-                }
-            }
-
-            return new NodeAndMappings(new ProjectNode(idAllocator.getNextId(), plan.getNode(), assignments.build()), newOutputs.build());
-        }
-
-        return plan;
-    }
-
-    private PlanBuilder distinct(PlanBuilder subPlan, QuerySpecification node, List<io.trino.sql.tree.Expression> expressions)
-    {
-        if (node.getSelect().isDistinct()) {
-            List<Symbol> symbols = expressions.stream()
-                    .map(subPlan::translate)
-                    .collect(Collectors.toList());
-
-            return subPlan.withNewRoot(
-                    singleAggregation(
-                            idAllocator.getNextId(),
-                            subPlan.getRoot(),
-                            ImmutableMap.of(),
-                            singleGroupingSet(symbols)));
-        }
-
-        return subPlan;
-    }
-
-    private Optional<OrderingScheme> orderingScheme(PlanBuilder subPlan, Optional<OrderBy> orderBy, List<io.trino.sql.tree.Expression> orderByExpressions)
-    {
-        if (orderBy.isEmpty() || (isSkipRedundantSort(session) && analysis.isOrderByRedundant(orderBy.get()))) {
-            return Optional.empty();
-        }
-
-        Iterator<SortItem> sortItems = orderBy.get().getSortItems().iterator();
-
-        ImmutableList.Builder<Symbol> orderBySymbols = ImmutableList.builder();
-        Map<Symbol, SortOrder> orderings = new HashMap<>();
-        for (io.trino.sql.tree.Expression fieldOrExpression : orderByExpressions) {
-            Symbol symbol = subPlan.translate(fieldOrExpression);
-
-            SortItem sortItem = sortItems.next();
-            if (!orderings.containsKey(symbol)) {
-                orderBySymbols.add(symbol);
-                orderings.put(symbol, sortItemToSortOrder(sortItem));
-            }
-        }
-        return Optional.of(new OrderingScheme(orderBySymbols.build(), orderings));
-    }
-
-    private PlanBuilder sort(PlanBuilder subPlan, Optional<OrderingScheme> orderingScheme)
-    {
-        if (orderingScheme.isEmpty()) {
-            return subPlan;
-        }
-
-        return subPlan.withNewRoot(
-                new SortNode(
-                        idAllocator.getNextId(),
-                        subPlan.getRoot(),
-                        orderingScheme.get(),
-                        false));
-    }
-
-    private PlanBuilder offset(PlanBuilder subPlan, Optional<Offset> offset)
-    {
-        if (offset.isEmpty()) {
-            return subPlan;
-        }
-
-        return subPlan.withNewRoot(
-                new OffsetNode(
-                        idAllocator.getNextId(),
-                        subPlan.getRoot(),
-                        analysis.getOffset(offset.get())));
-    }
-
-    private PlanBuilder limit(PlanBuilder subPlan, Optional<Node> limit, Optional<OrderingScheme> orderingScheme)
-    {
-        if (limit.isPresent() && analysis.getLimit(limit.get()).isPresent()) {
-            Optional<OrderingScheme> tiesResolvingScheme = Optional.empty();
-            if (limit.get() instanceof FetchFirst && ((FetchFirst) limit.get()).isWithTies()) {
-                tiesResolvingScheme = orderingScheme;
-            }
-            return subPlan.withNewRoot(
-                    new LimitNode(
-                            idAllocator.getNextId(),
-                            subPlan.getRoot(),
-                            analysis.getLimit(limit.get()).getAsLong(),
-                            tiesResolvingScheme,
-                            false,
-                            ImmutableList.of()));
-        }
-        return subPlan;
-    }
-
-    public static class PlanAndMappings
-    {
-        private final PlanBuilder subPlan;
-        private final Map<NodeRef<io.trino.sql.tree.Expression>, Symbol> mappings;
-
-        public PlanAndMappings(PlanBuilder subPlan, Map<NodeRef<io.trino.sql.tree.Expression>, Symbol> mappings)
-        {
-            this.subPlan = subPlan;
-            this.mappings = ImmutableMap.copyOf(mappings);
-        }
-
-        public PlanBuilder getSubPlan()
-        {
-            return subPlan;
-        }
-
-        public Symbol get(Expression expression)
-        {
-            return tryGet(expression)
-                    .orElseThrow(() -> new IllegalArgumentException(format("No mapping for expression: %s (%s)", expression, System.identityHashCode(expression))));
-        }
-
-        public Optional<Symbol> tryGet(Expression expression)
-        {
-            Symbol result = mappings.get(NodeRef.of(expression));
-
-            if (result != null) {
-                return Optional.of(result);
-            }
-
-            return Optional.empty();
-        }
-    }
-
-    private static class AggregationAssignment {
-        private final Symbol symbol;
-        private final Expression astExpression;
-        private final Aggregation aggregation;
-
-        public AggregationAssignment(Symbol symbol, Expression astExpression, Aggregation aggregation) {
-            this.astExpression = astExpression;
-            this.symbol = symbol;
-            this.aggregation = aggregation;
-        }
-
-        public Symbol getSymbol()
-        {
-            return symbol;
-        }
-
-        public Expression getAstExpression()
-        {
-            return astExpression;
-        }
-
-        public Aggregation getRewritten()
-        {
-            return aggregation;
-        }
-    }
-
-    private static class FrameBoundPlanAndSymbols
-    {
-        private final PlanBuilder subPlan;
-        private final Optional<Symbol> frameBoundSymbol;
-        private final Optional<Symbol> sortKeyCoercedForFrameBoundComparison;
-
-        public FrameBoundPlanAndSymbols(PlanBuilder subPlan, Optional<Symbol> frameBoundSymbol, Optional<Symbol> sortKeyCoercedForFrameBoundComparison)
-        {
-            this.subPlan = subPlan;
-            this.frameBoundSymbol = frameBoundSymbol;
-            this.sortKeyCoercedForFrameBoundComparison = sortKeyCoercedForFrameBoundComparison;
-        }
-
-        public PlanBuilder getSubPlan()
-        {
-            return subPlan;
-        }
-
-        public Optional<Symbol> getFrameBoundSymbol()
-        {
-            return frameBoundSymbol;
-        }
-
-        public Optional<Symbol> getSortKeyCoercedForFrameBoundComparison()
-        {
-            return sortKeyCoercedForFrameBoundComparison;
-        }
-    }
-
-    private static class FrameOffsetPlanAndSymbol
-    {
-        private final PlanBuilder subPlan;
-        private final Optional<Symbol> frameOffsetSymbol;
-
-        public FrameOffsetPlanAndSymbol(PlanBuilder subPlan, Optional<Symbol> frameOffsetSymbol)
-        {
-            this.subPlan = subPlan;
-            this.frameOffsetSymbol = frameOffsetSymbol;
-        }
-
-        public PlanBuilder getSubPlan()
-        {
-            return subPlan;
-        }
-
-        public Optional<Symbol> getFrameOffsetSymbol()
-        {
-            return frameOffsetSymbol;
-        }
-    }
+class QueryPlanner {
+  private static final int MAX_BIGINT_PRECISION = 19;
+  private final Analysis analysis;
+  private final SymbolAllocator symbolAllocator;
+  private final QueryId idAllocator;
+  // private final Map<NodeRef<LambdaArgumentDeclaration>, Symbol> lambdaDeclarationToSymbolMap;
+  // private final PlannerContext plannerContext;
+  private final Session session;
+  // private final SubqueryPlanner subqueryPlanner;
+  private final Map<NodeRef<Node>, RelationPlan> recursiveSubqueries;
+
+  QueryPlanner(
+      Analysis analysis,
+      SymbolAllocator symbolAllocator,
+      QueryId idAllocator,
+      Session session,
+      Map<NodeRef<Node>, RelationPlan> recursiveSubqueries) {
+    requireNonNull(analysis, "analysis is null");
+    requireNonNull(symbolAllocator, "symbolAllocator is null");
+    requireNonNull(idAllocator, "idAllocator is null");
+    requireNonNull(session, "session is null");
+    requireNonNull(recursiveSubqueries, "recursiveSubqueries is null");
+
+    this.analysis = analysis;
+    this.symbolAllocator = symbolAllocator;
+    this.idAllocator = idAllocator;
+    this.session = session;
+    // this.subqueryPlanner = null;
+    this.recursiveSubqueries = recursiveSubqueries;
+  }
+
+  public RelationPlan plan(Query query) {
+    PlanBuilder builder = planQueryBody(query);
+
+    List<Expression> orderBy = analysis.getOrderByExpressions(query);
+    // builder = subqueryPlanner.handleSubqueries(builder, orderBy, analysis.getSubqueries(query));
+
+    List<Analysis.SelectExpression> selectExpressions = analysis.getSelectExpressions(query);
+    List<Expression> outputs =
+        selectExpressions.stream()
+            .map(Analysis.SelectExpression::getExpression)
+            .collect(toImmutableList());
+
+    builder =
+        builder.appendProjections(Iterables.concat(orderBy, outputs), symbolAllocator, idAllocator);
+
+    Optional<OrderingScheme> orderingScheme =
+        orderingScheme(builder, query.getOrderBy(), analysis.getOrderByExpressions(query));
+    builder = sort(builder, orderingScheme);
+    builder = offset(builder, query.getOffset());
+    builder = limit(builder, query.getLimit(), orderingScheme);
+    builder = builder.appendProjections(outputs, symbolAllocator, idAllocator);
+
+    return new RelationPlan(
+        builder.getRoot(), analysis.getScope(query), computeOutputs(builder, outputs));
+  }
+
+  public RelationPlan plan(QuerySpecification node) {
+    PlanBuilder builder = planFrom(node);
+
+    builder = filter(builder, analysis.getWhere(node), node);
+    // builder = aggregate(builder, node);
+    builder = filter(builder, analysis.getHaving(node), node);
+    // builder = planWindowFunctions(node, builder,
+    // ImmutableList.copyOf(analysis.getWindowFunctions(node)));
+    // builder = planWindowMeasures(node, builder,
+    // ImmutableList.copyOf(analysis.getWindowMeasures(node)));
+
+    List<Analysis.SelectExpression> selectExpressions = analysis.getSelectExpressions(node);
+    List<Expression> expressions =
+        selectExpressions.stream()
+            .map(Analysis.SelectExpression::getExpression)
+            .collect(toImmutableList());
+    // builder = subqueryPlanner.handleSubqueries(builder, expressions,
+    // analysis.getSubqueries(node));
+
+    if (hasExpressionsToUnfold(selectExpressions)) {
+      // pre-project the folded expressions to preserve any non-deterministic semantics of functions
+      // that might be referenced
+      builder = builder.appendProjections(expressions, symbolAllocator, idAllocator);
+    }
+
+    List<Expression> outputs = outputExpressions(selectExpressions);
+    if (node.getOrderBy().isPresent()) {
+      // ORDER BY requires outputs of SELECT to be visible.
+      // For queries with aggregation, it also requires grouping keys and translated aggregations.
+      if (analysis.isAggregation(node)) {
+        // Add projections for aggregations required by ORDER BY. After this step, grouping keys and
+        // translated
+        // aggregations are visible.
+        List<Expression> orderByAggregates = analysis.getOrderByAggregates(node.getOrderBy().get());
+        builder = builder.appendProjections(orderByAggregates, symbolAllocator, idAllocator);
+      }
+
+      // Add projections for the outputs of SELECT, but stack them on top of the ones from the FROM
+      // clause so both are visible
+      // when resolving the ORDER BY clause.
+      builder = builder.appendProjections(outputs, symbolAllocator, idAllocator);
+
+      // The new scope is the composite of the fields from the FROM and SELECT clause (local nested
+      // scopes). Fields from the bottom of
+      // the scope stack need to be placed first to match the expected layout for nested scopes.
+      List<Symbol> newFields = new ArrayList<>();
+      // newFields.addAll(builder.getTranslations().getFieldSymbols());
+
+      //            outputs.stream()
+      //                    .map(builder::translate)
+      //                    .forEach(newFields::add);
+
+      builder = builder.withScope(analysis.getScope(node.getOrderBy().get()), newFields);
+    }
+
+    List<Expression> orderBy = analysis.getOrderByExpressions(node);
+    builder =
+        builder.appendProjections(Iterables.concat(orderBy, outputs), symbolAllocator, idAllocator);
+
+    // builder = distinct(builder, node, outputs);
+    Optional<OrderingScheme> orderingScheme =
+        orderingScheme(builder, node.getOrderBy(), analysis.getOrderByExpressions(node));
+    builder = sort(builder, orderingScheme);
+    builder = offset(builder, node.getOffset());
+    builder = limit(builder, node.getLimit(), orderingScheme);
+    builder = builder.appendProjections(outputs, symbolAllocator, idAllocator);
+
+    return new RelationPlan(
+        builder.getRoot(), analysis.getScope(node), computeOutputs(builder, outputs));
+  }
+
+  private static boolean hasExpressionsToUnfold(List<Analysis.SelectExpression> selectExpressions) {
+    return selectExpressions.stream()
+        .map(Analysis.SelectExpression::getUnfoldedExpressions)
+        .anyMatch(Optional::isPresent);
+  }
+
+  private static List<Expression> outputExpressions(
+      List<Analysis.SelectExpression> selectExpressions) {
+    ImmutableList.Builder<Expression> result = ImmutableList.builder();
+    for (Analysis.SelectExpression selectExpression : selectExpressions) {
+      if (selectExpression.getUnfoldedExpressions().isPresent()) {
+        result.addAll(selectExpression.getUnfoldedExpressions().get());
+      } else {
+        result.add(selectExpression.getExpression());
+      }
+    }
+    return result.build();
+  }
+
+  public PlanNode plan(Delete node) {
+    // implement delete logic
+    return null;
+  }
+
+  private static List<Symbol> computeOutputs(
+      PlanBuilder builder, List<Expression> outputExpressions) {
+    ImmutableList.Builder<Symbol> outputSymbols = ImmutableList.builder();
+    //        for (Expression expression : outputExpressions) {
+    //            // outputSymbols.add(builder.translate(expression));
+    //        }
+    return outputSymbols.build();
+  }
+
+  private PlanBuilder planQueryBody(Query query) {
+    RelationPlan relationPlan =
+        new RelationPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
+            .process(query.getQueryBody(), null);
+
+    return newPlanBuilder(relationPlan, analysis, session);
+  }
+
+  private PlanBuilder planFrom(QuerySpecification node) {
+    if (node.getFrom().isPresent()) {
+      RelationPlan relationPlan =
+          new RelationPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
+              .process(node.getFrom().get(), null);
+      return newPlanBuilder(relationPlan, analysis, session);
+    } else {
+      throw new RuntimeException("From clause must not by empty");
+    }
+  }
+
+  private PlanBuilder filter(PlanBuilder subPlan, Expression predicate, Node node) {
+    if (predicate == null) {
+      return subPlan;
+    }
+
+    // subPlan = subqueryPlanner.handleSubqueries(subPlan, predicate, analysis.getSubqueries(node));
+
+    return subPlan.withNewRoot(
+        new FilterNode(idAllocator.genPlanNodeId(), subPlan.getRoot(), predicate));
+  }
+
+  public static Expression coerceIfNecessary(
+      Analysis analysis, Expression original, Expression rewritten) {
+    Type coercion = analysis.getCoercion(original);
+    if (coercion == null) {
+      return rewritten;
+    } else {
+      throw new RuntimeException("Coercion result in analysis only can be empty");
+    }
+  }
+
+  private Optional<OrderingScheme> orderingScheme(
+      PlanBuilder subPlan, Optional<OrderBy> orderBy, List<Expression> orderByExpressions) {
+    if (!orderBy.isPresent() || (analysis.isOrderByRedundant(orderBy.get()))) {
+      return Optional.empty();
+    }
+
+    Iterator<SortItem> sortItems = orderBy.get().getSortItems().iterator();
+
+    ImmutableList.Builder<Symbol> orderBySymbols = ImmutableList.builder();
+    Map<Symbol, SortOrder> orderings = new HashMap<>();
+    for (Expression fieldOrExpression : orderByExpressions) {
+      Symbol symbol = null;
+      // subPlan.translate(fieldOrExpression);
+
+      SortItem sortItem = sortItems.next();
+      if (!orderings.containsKey(symbol)) {
+        orderBySymbols.add(symbol);
+        orderings.put(symbol, sortItemToSortOrder(sortItem));
+      }
+    }
+    return Optional.of(new OrderingScheme(orderBySymbols.build(), orderings));
+  }
+
+  private PlanBuilder sort(PlanBuilder subPlan, Optional<OrderingScheme> orderingScheme) {
+    if (!orderingScheme.isPresent()) {
+      return subPlan;
+    }
+
+    return subPlan.withNewRoot(
+        new SortNode(idAllocator.genPlanNodeId(), subPlan.getRoot(), orderingScheme.get(), false));
+  }
+
+  private PlanBuilder offset(PlanBuilder subPlan, Optional<Offset> offset) {
+    if (!offset.isPresent()) {
+      return subPlan;
+    }
+
+    return subPlan.withNewRoot(
+        new OffsetNode(
+            idAllocator.genPlanNodeId(), subPlan.getRoot(), analysis.getOffset(offset.get())));
+  }
+
+  private PlanBuilder limit(
+      PlanBuilder subPlan, Optional<Node> limit, Optional<OrderingScheme> orderingScheme) {
+    if (limit.isPresent() && analysis.getLimit(limit.get()).isPresent()) {
+      Optional<OrderingScheme> tiesResolvingScheme = Optional.empty();
+      //            if (limit.get() instanceof FetchFirst && ((FetchFirst)
+      // limit.get()).isWithTies()) {
+      //                tiesResolvingScheme = orderingScheme;
+      //            }
+      return subPlan.withNewRoot(
+          new LimitNode(
+              idAllocator.genPlanNodeId(),
+              subPlan.getRoot(),
+              analysis.getLimit(limit.get()).getAsLong(),
+              tiesResolvingScheme));
+    }
+    return subPlan;
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlan.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlan.java
index 7f292bf5b30..de035fd48fd 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlan.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlan.java
@@ -13,67 +13,66 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.planner;
 
-import com.google.common.collect.ImmutableList;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.RelationType;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Scope;
 
+import com.google.common.collect.ImmutableList;
+
 import java.util.List;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
 /**
- * The purpose of this class is to hold the current plan built so far
- * for a relation (query, table, values, etc.), and the mapping to
- * indicate how the fields (by position) in the relation map to
+ * The purpose of this class is to hold the current plan built so far for a relation (query, table,
+ * values, etc.), and the mapping to indicate how the fields (by position) in the relation map to
  * the outputs of the plan.
  */
 class RelationPlan {
-    private final PlanNode root;
-    private final List<Symbol> fieldMappings; // for each field in the relation, the corresponding symbol from "root"
-    private final Scope scope;
+  private final PlanNode root;
+  private final List<Symbol>
+      fieldMappings; // for each field in the relation, the corresponding symbol from "root"
+  private final Scope scope;
 
-    public RelationPlan(PlanNode root, Scope scope, List<Symbol> fieldMappings) {
-        requireNonNull(root, "root is null");
-        requireNonNull(fieldMappings, "fieldMappings is null");
-        requireNonNull(scope, "scope is null");
+  public RelationPlan(PlanNode root, Scope scope, List<Symbol> fieldMappings) {
+    requireNonNull(root, "root is null");
+    requireNonNull(fieldMappings, "fieldMappings is null");
+    requireNonNull(scope, "scope is null");
 
-        int allFieldCount = scope.getLocalScopeFieldCount();
-        checkArgument(allFieldCount == fieldMappings.size(),
-                "Number of outputs (%s) doesn't match number of fields in local scope (%s)",
-                fieldMappings.size(),
-                allFieldCount);
+    int allFieldCount = scope.getLocalScopeFieldCount();
+    checkArgument(
+        allFieldCount == fieldMappings.size(),
+        "Number of outputs (%s) doesn't match number of fields in local scope (%s)",
+        fieldMappings.size(),
+        allFieldCount);
 
-        this.root = root;
-        this.scope = scope;
-        this.fieldMappings = ImmutableList.copyOf(fieldMappings);
-    }
+    this.root = root;
+    this.scope = scope;
+    this.fieldMappings = ImmutableList.copyOf(fieldMappings);
+  }
 
-    public Symbol getSymbol(int fieldIndex)
-    {
-        checkArgument(fieldIndex >= 0 && fieldIndex < fieldMappings.size(),
-                "No field->symbol mapping for field %s", fieldIndex);
-        return fieldMappings.get(fieldIndex);
-    }
+  public Symbol getSymbol(int fieldIndex) {
+    checkArgument(
+        fieldIndex >= 0 && fieldIndex < fieldMappings.size(),
+        "No field->symbol mapping for field %s",
+        fieldIndex);
+    return fieldMappings.get(fieldIndex);
+  }
 
-    public PlanNode getRoot()
-    {
-        return root;
-    }
+  public PlanNode getRoot() {
+    return root;
+  }
 
-    public List<Symbol> getFieldMappings()
-    {
-        return fieldMappings;
-    }
+  public List<Symbol> getFieldMappings() {
+    return fieldMappings;
+  }
 
-    public RelationType getDescriptor()
-    {
-        return scope.getRelationType();
-    }
+  public RelationType getDescriptor() {
+    return scope.getRelationType();
+  }
 
-    public Scope getScope()
-    {
-        return scope;
-    }
+  public Scope getScope() {
+    return scope;
+  }
 }
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 f3b28dec1f4..a3ae2c9e715 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
@@ -13,282 +13,229 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.planner;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ListMultimap;
 import org.apache.iotdb.db.queryengine.common.QueryId;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Analysis;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Field;
 import org.apache.iotdb.db.queryengine.plan.relational.analyzer.NodeRef;
-import org.apache.iotdb.db.queryengine.plan.relational.analyzer.RelationType;
 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.ProjectNode;
+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.Except;
 import org.apache.iotdb.db.relational.sql.tree.Expression;
-import org.apache.iotdb.db.relational.sql.tree.Intersect;
-import org.apache.iotdb.db.relational.sql.tree.Join;
 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;
-import org.apache.iotdb.db.relational.sql.tree.Relation;
-import org.apache.iotdb.db.relational.sql.tree.Row;
-import org.apache.iotdb.db.relational.sql.tree.SetOperation;
 import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression;
 import org.apache.iotdb.db.relational.sql.tree.Table;
-import org.apache.iotdb.db.relational.sql.tree.TableSubquery;
-import org.apache.iotdb.db.relational.sql.tree.Union;
-import org.apache.iotdb.db.relational.sql.tree.Values;
 import org.apache.iotdb.session.Session;
-import org.apache.iotdb.tsfile.read.common.type.RowType;
 
-import java.util.ArrayList;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
 import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.ImmutableMap.toImmutableMap;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static java.lang.Boolean.TRUE;
+
 import static java.util.Objects.requireNonNull;
 import static org.apache.iotdb.db.queryengine.plan.relational.planner.PlanBuilder.newPlanBuilder;
-
-class RelationPlanner
-        extends AstVisitor<RelationPlan, Void>
-{
-    private final Analysis analysis;
-    private final SymbolAllocator symbolAllocator;
-    private final QueryId idAllocator;
-    private final Session session;
-    private final Map<NodeRef<Node>, RelationPlan> recursiveSubqueries;
-
-    RelationPlanner(
-            Analysis analysis,
-            SymbolAllocator symbolAllocator,
-            QueryId idAllocator,
-            Session session,
-            Map<NodeRef<Node>, RelationPlan> recursiveSubqueries) {
-        requireNonNull(analysis, "analysis is null");
-        requireNonNull(symbolAllocator, "symbolAllocator is null");
-        requireNonNull(idAllocator, "idAllocator is null");
-        requireNonNull(session, "session is null");
-        requireNonNull(recursiveSubqueries, "recursiveSubqueries is null");
-
-        this.analysis = analysis;
-        this.symbolAllocator = symbolAllocator;
-        this.idAllocator = idAllocator;
-        this.session = session;
-        this.recursiveSubqueries = recursiveSubqueries;
+import static org.apache.iotdb.db.queryengine.plan.relational.planner.QueryPlanner.coerceIfNecessary;
+
+class RelationPlanner extends AstVisitor<RelationPlan, Void> {
+  private final Analysis analysis;
+  private final SymbolAllocator symbolAllocator;
+  private final QueryId idAllocator;
+  private final Session session;
+  private final Map<NodeRef<Node>, RelationPlan> recursiveSubqueries;
+
+  RelationPlanner(
+      Analysis analysis,
+      SymbolAllocator symbolAllocator,
+      QueryId idAllocator,
+      Session session,
+      Map<NodeRef<Node>, RelationPlan> recursiveSubqueries) {
+    requireNonNull(analysis, "analysis is null");
+    requireNonNull(symbolAllocator, "symbolAllocator is null");
+    requireNonNull(idAllocator, "idAllocator is null");
+    requireNonNull(session, "session is null");
+    requireNonNull(recursiveSubqueries, "recursiveSubqueries is null");
+
+    this.analysis = analysis;
+    this.symbolAllocator = symbolAllocator;
+    this.idAllocator = idAllocator;
+    this.session = session;
+    this.recursiveSubqueries = recursiveSubqueries;
+  }
+
+  @Override
+  protected RelationPlan visitNode(Node node, Void context) {
+    throw new IllegalStateException("Unsupported node type: " + node.getClass().getName());
+  }
+
+  @Override
+  protected RelationPlan visitTable(Table node, Void context) {
+    // is this a recursive reference in expandable named query? If so, there's base relation already
+    // planned.
+    RelationPlan expansion = recursiveSubqueries.get(NodeRef.of(node));
+    if (expansion != null) {
+      // put the pre-planned recursive subquery in the actual outer context to enable resolving
+      // correlation
+      return new RelationPlan(
+          expansion.getRoot(), expansion.getScope(), expansion.getFieldMappings());
     }
 
-    @Override
-    protected RelationPlan visitNode(Node node, Void context) {
-        throw new IllegalStateException("Unsupported node type: " + node.getClass().getName());
-    }
+    Query namedQuery = analysis.getNamedQuery(node);
+    Scope scope = analysis.getScope(node);
 
-    @Override
-    protected RelationPlan visitTable(Table node, Void context) {
-        // is this a recursive reference in expandable named query? If so, there's base relation already planned.
-        RelationPlan expansion = recursiveSubqueries.get(NodeRef.of(node));
-        if (expansion != null) {
-            // put the pre-planned recursive subquery in the actual outer context to enable resolving correlation
-            return new RelationPlan(expansion.getRoot(), expansion.getScope(), expansion.getFieldMappings());
-        }
+    RelationPlan plan;
+    if (namedQuery != null) {
+      throw new RuntimeException("NamedQuery is not supported");
+    } else {
+      TableHandle handle = analysis.getTableHandle(node);
 
-        Query namedQuery = analysis.getNamedQuery(node);
-        Scope scope = analysis.getScope(node);
-
-        RelationPlan plan;
-        if (namedQuery != null) {
-            RelationPlan subPlan;
-            if (analysis.isExpandableQuery(namedQuery)) {
-                subPlan = new QueryPlanner(analysis, symbolAllocator, idAllocator,
-                        session, recursiveSubqueries)
-                        .planExpand(namedQuery);
-            } else {
-                subPlan = process(namedQuery, null);
-            }
-
-            // Add implicit coercions if view query produces types that don't match the declared output types
-            // of the view (e.g., if the underlying tables referenced by the view changed)
-
-            List<Type> types = analysis.getOutputDescriptor(node)
-                    .getAllFields().stream()
-                    .map(Field::getType)
-                    .collect(toImmutableList());
-
-            NodeAndMappings coerced = coerce(subPlan, types, symbolAllocator, idAllocator);
-
-            plan = new RelationPlan(coerced.getNode(), scope, coerced.getFields());
-        } else {
-            TableHandle handle = analysis.getTableHandle(node);
-
-            ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder();
-            ImmutableMap.Builder<Symbol, ColumnHandle> columns = ImmutableMap.builder();
-
-            Collection<Field> fields = analysis.getMaterializedViewStorageTableFields(node);
-            boolean hasStorageTableFields = fields != null;
-            if (!hasStorageTableFields) {
-                fields = scope.getRelationType().getAllFields();
-            }
-            for (Field field : fields) {
-                Symbol symbol = symbolAllocator.newSymbol(field);
-
-                outputSymbolsBuilder.add(symbol);
-                columns.put(symbol, analysis.getColumn(field));
-            }
-
-            List<Symbol> outputSymbols = outputSymbolsBuilder.build();
-            PlanNode root = TableScanNode.newInstance(idAllocator.genPlanNodeId(), handle, outputSymbols,
-                    columns.buildOrThrow(), Optional.empty());
-
-            plan = new RelationPlan(root, scope, outputSymbols);
-
-            if (hasStorageTableFields) {
-                List<Type> types = scope.getRelationType().getAllFields().stream()
-                        .map(Field::getType)
-                        .collect(toImmutableList());
-                // apply required coercion and prune invisible fields from child outputs
-                NodeAndMappings coerced = coerce(plan, types, symbolAllocator, idAllocator);
-                plan = new RelationPlan(coerced.getNode(), scope, coerced.getFields());
-            }
-        }
+      ImmutableList.Builder<Symbol> outputSymbolsBuilder = ImmutableList.builder();
+      ImmutableMap.Builder<Symbol, ColumnHandle> columns = ImmutableMap.builder();
 
-        plan = addRowFilters(node, plan);
-        plan = addColumnMasks(node, plan);
+      // Collection<Field> fields = analysis.getMaterializedViewStorageTableFields(node);
+      Collection<Field> fields = scope.getRelationType().getAllFields();
+      for (Field field : fields) {
+        Symbol symbol = symbolAllocator.newSymbol(field);
 
-        return plan;
-    }
+        outputSymbolsBuilder.add(symbol);
+        columns.put(symbol, analysis.getColumn(field));
+      }
 
-    private RelationPlan addRowFilters(Table node, RelationPlan plan) {
-        return addRowFilters(node, plan, Function.identity());
-    }
+      List<Symbol> outputSymbols = outputSymbolsBuilder.build();
+      PlanNode root =
+          new TableScanNode(
+              idAllocator.genPlanNodeId(), handle, outputSymbols, columns.buildOrThrow());
 
-    public RelationPlan addRowFilters(Table node, RelationPlan plan, Function<Expression, Expression> predicateTransformation) {
-        return addRowFilters(node, plan, predicateTransformation, analysis::getAccessControlScope);
+      plan = new RelationPlan(root, scope, outputSymbols);
     }
 
-    public RelationPlan addRowFilters(Table node, RelationPlan plan,
-                                      Function<Expression, Expression> predicateTransformation,
-                                      Function<Table, Scope> accessControlScope) {
-        List<Expression> filters = analysis.getRowFilters(node);
+    plan = addRowFilters(node, plan);
+    // TODO what's the meaning of addColumnMasks?
+    // plan = addColumnMasks(node, plan);
 
-        if (filters.isEmpty()) {
-            return plan;
-        }
-
-        PlanBuilder planBuilder = newPlanBuilder(plan, analysis, session)
-                .withScope(accessControlScope.apply(node), plan.getFieldMappings()); // The fields in the access control scope has the same layout as those for the table scope
+    return plan;
+  }
 
-        for (Expression filter : filters) {
-            planBuilder = subqueryPlanner.handleSubqueries(planBuilder, filter, analysis.getSubqueries(filter));
+  private RelationPlan addRowFilters(Table node, RelationPlan plan) {
+    return addRowFilters(node, plan, Function.identity());
+  }
 
-            Expression predicate = coerceIfNecessary(analysis, filter, planBuilder.rewrite(filter));
-            predicate = predicateTransformation.apply(predicate);
-            planBuilder = planBuilder.withNewRoot(new FilterNode(
-                    idAllocator.genPlanNodeId(),
-                    planBuilder.getRoot(),
-                    predicate));
-        }
+  public RelationPlan addRowFilters(
+      Table node, RelationPlan plan, Function<Expression, Expression> predicateTransformation) {
+    List<Expression> filters = null;
+    // analysis.getRowFilters(node);
 
-        return new RelationPlan(planBuilder.getRoot(), plan.getScope(), plan.getFieldMappings());
+    if (filters.isEmpty()) {
+      return plan;
     }
 
-    private RelationPlan addColumnMasks(Table table, RelationPlan plan) {
-        Map<String, Expression> columnMasks = analysis.getColumnMasks(table);
-
-        // A Table can represent a WITH query, which can have anonymous fields. On the other hand,
-        // it can't have masks. The loop below expects fields to have proper names, so bail out
-        // if the masks are missing
-        if (columnMasks.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(analysis.getAccessControlScope(table), plan.getFieldMappings());
-
-        Assignments.Builder assignments = Assignments.builder();
-        assignments.putIdentities(planBuilder.getRoot().getOutputSymbols());
-
-        List<Symbol> fieldMappings = new ArrayList<>();
-        for (int i = 0; i < plan.getDescriptor().getAllFieldCount(); i++) {
-            Field field = plan.getDescriptor().getFieldByIndex(i);
+    // 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());
 
-            Expression mask = columnMasks.get(field.getName().orElseThrow());
-            Symbol symbol = plan.getFieldMappings().get(i);
-            Expression projection = symbol.toSymbolReference();
-            if (mask != null) {
-                symbol = symbolAllocator.newSymbol(symbol);
-                projection = coerceIfNecessary(analysis, mask, planBuilder.rewrite(mask));
-            }
+    for (Expression filter : filters) {
+      // planBuilder = subqueryPlanner.handleSubqueries(planBuilder, filter,
+      // analysis.getSubqueries(filter));
 
-            assignments.put(symbol, projection);
-            fieldMappings.add(symbol);
-        }
-
-        planBuilder = planBuilder
-                .withNewRoot(new ProjectNode(
-                        idAllocator.genPlanNodeId(),
-                        planBuilder.getRoot(),
-                        assignments.build()));
-
-        return new RelationPlan(planBuilder.getRoot(), plan.getScope(), fieldMappings);
+      Expression predicate = coerceIfNecessary(analysis, filter, filter);
+      predicate = predicateTransformation.apply(predicate);
+      planBuilder =
+          planBuilder.withNewRoot(
+              new FilterNode(idAllocator.genPlanNodeId(), planBuilder.getRoot(), predicate));
     }
 
-    @Override
-    protected RelationPlan visitAliasedRelation(AliasedRelation node, Void context) {
-        RelationPlan subPlan = process(node.getRelation(), context);
-
-        PlanNode root = subPlan.getRoot();
-        List<Symbol> mappings = subPlan.getFieldMappings();
-
-        if (node.getColumnNames() != null) {
-            ImmutableList.Builder<Symbol> newMappings = ImmutableList.builder();
-
-            // Adjust the mappings to expose only the columns visible in the scope of the aliased relation
-            for (int i = 0; i < subPlan.getDescriptor().getAllFieldCount(); i++) {
-                if (!subPlan.getDescriptor().getFieldByIndex(i).isHidden()) {
-                    newMappings.add(subPlan.getFieldMappings().get(i));
-                }
-            }
-
-            mappings = newMappings.build();
+    return new RelationPlan(planBuilder.getRoot(), plan.getScope(), plan.getFieldMappings());
+  }
+
+  //    private RelationPlan addColumnMasks(Table table, RelationPlan plan) {
+  //        Map<String, Expression> columnMasks = analysis.getColumnMasks(table);
+  //
+  //        // A Table can represent a WITH query, which can have anonymous fields. On the other
+  // hand,
+  //        // it can't have masks. The loop below expects fields to have proper names, so bail out
+  //        // if the masks are missing
+  //        if (columnMasks.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(analysis.getAccessControlScope(table), plan.getFieldMappings());
+  //
+  //        Assignments.Builder assignments = Assignments.builder();
+  //        assignments.putIdentities(planBuilder.getRoot().getOutputSymbols());
+  //
+  //        List<Symbol> fieldMappings = new ArrayList<>();
+  //        for (int i = 0; i < plan.getDescriptor().getAllFieldCount(); i++) {
+  //            Field field = plan.getDescriptor().getFieldByIndex(i);
+  //
+  //            Expression mask = columnMasks.get(field.getName().orElseThrow());
+  //            Symbol symbol = plan.getFieldMappings().get(i);
+  //            Expression projection = symbol.toSymbolReference();
+  //            if (mask != null) {
+  //                symbol = symbolAllocator.newSymbol(symbol);
+  //                projection = coerceIfNecessary(analysis, mask, planBuilder.rewrite(mask));
+  //            }
+  //
+  //            assignments.put(symbol, projection);
+  //            fieldMappings.add(symbol);
+  //        }
+  //
+  //        planBuilder = planBuilder
+  //                .withNewRoot(new ProjectNode(
+  //                        idAllocator.genPlanNodeId(),
+  //                        planBuilder.getRoot(),
+  //                        assignments.build()));
+  //
+  //        return new RelationPlan(planBuilder.getRoot(), plan.getScope(), fieldMappings);
+  //    }
+
+  @Override
+  protected RelationPlan visitAliasedRelation(AliasedRelation node, Void context) {
+    RelationPlan subPlan = process(node.getRelation(), context);
+
+    PlanNode root = subPlan.getRoot();
+    List<Symbol> mappings = subPlan.getFieldMappings();
+
+    if (node.getColumnNames() != null) {
+      ImmutableList.Builder<Symbol> newMappings = ImmutableList.builder();
+
+      // Adjust the mappings to expose only the columns visible in the scope of the aliased relation
+      for (int i = 0; i < subPlan.getDescriptor().getAllFieldCount(); i++) {
+        if (!subPlan.getDescriptor().getFieldByIndex(i).isHidden()) {
+          newMappings.add(subPlan.getFieldMappings().get(i));
         }
+      }
 
-        return new RelationPlan(root, analysis.getScope(node), mappings);
-    }
-
-    @Override
-    protected RelationPlan visitQuery(Query node, Void context) {
-        return new QueryPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
-                .plan(node);
-    }
-
-    @Override
-    protected RelationPlan visitQuerySpecification(QuerySpecification node, Void context) {
-        return new QueryPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
-                .plan(node);
+      mappings = newMappings.build();
     }
 
-    @Override
-    protected RelationPlan visitSubqueryExpression(SubqueryExpression node, Void context) {
-        return process(node.getQuery(), context);
-    }
+    return new RelationPlan(root, analysis.getScope(node), mappings);
+  }
+
+  @Override
+  protected RelationPlan visitQuery(Query node, Void context) {
+    return new QueryPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
+        .plan(node);
+  }
+
+  @Override
+  protected RelationPlan visitQuerySpecification(QuerySpecification node, Void context) {
+    return new QueryPlanner(analysis, symbolAllocator, idAllocator, session, recursiveSubqueries)
+        .plan(node);
+  }
+
+  @Override
+  protected RelationPlan visitSubqueryExpression(SubqueryExpression node, Void context) {
+    return process(node.getQuery(), context);
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SortOrder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SortOrder.java
new file mode 100644
index 00000000000..70d2cf789a2
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SortOrder.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import static java.lang.String.format;
+
+public enum SortOrder {
+  ASC_NULLS_FIRST(true, true),
+  ASC_NULLS_LAST(true, false),
+  DESC_NULLS_FIRST(false, true),
+  DESC_NULLS_LAST(false, false);
+
+  private final boolean ascending;
+  private final boolean nullsFirst;
+
+  SortOrder(boolean ascending, boolean nullsFirst) {
+    this.ascending = ascending;
+    this.nullsFirst = nullsFirst;
+  }
+
+  public boolean isAscending() {
+    return ascending;
+  }
+
+  public boolean isNullsFirst() {
+    return nullsFirst;
+  }
+
+  @Override
+  public String toString() {
+    return format("%s %s", ascending ? "ASC" : "DESC", nullsFirst ? "NULLS FIRST" : "NULLS LAST");
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolAllocator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolAllocator.java
index 2252c5f3a39..be6c360a5c4 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolAllocator.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolAllocator.java
@@ -13,14 +13,15 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.planner;
 
+import org.apache.iotdb.db.queryengine.plan.analyze.TypeProvider;
+import org.apache.iotdb.db.queryengine.plan.relational.analyzer.Field;
+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.SymbolReference;
+import org.apache.iotdb.tsfile.read.common.type.LongType;
+import org.apache.iotdb.tsfile.read.common.type.Type;
+
 import com.google.common.primitives.Ints;
-import io.trino.spi.type.BigintType;
-import io.trino.spi.type.Type;
-import io.trino.sql.analyzer.Field;
-import io.trino.sql.ir.Expression;
-import io.trino.sql.ir.FunctionCall;
-import io.trino.sql.ir.SymbolReference;
-import jakarta.annotation.Nullable;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -29,107 +30,95 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Locale.ENGLISH;
 import static java.util.Objects.requireNonNull;
 
-public class SymbolAllocator
-{
-    private final Map<Symbol, Type> symbols;
-    private int nextId;
-
-    public SymbolAllocator()
-    {
-        symbols = new HashMap<>();
-    }
+public class SymbolAllocator {
+  private final Map<Symbol, Type> symbols;
+  private int nextId;
 
-    public SymbolAllocator(Map<Symbol, Type> initial)
-    {
-        symbols = new HashMap<>(initial);
-    }
+  public SymbolAllocator() {
+    symbols = new HashMap<>();
+  }
 
-    public Symbol newSymbol(Symbol symbolHint)
-    {
-        return newSymbol(symbolHint, null);
-    }
-
-    public Symbol newSymbol(Symbol symbolHint, String suffix)
-    {
-        checkArgument(symbols.containsKey(symbolHint), "symbolHint not in symbols map");
-        return newSymbol(symbolHint.getName(), symbols.get(symbolHint), suffix);
-    }
+  public SymbolAllocator(Map<Symbol, Type> initial) {
+    symbols = new HashMap<>(initial);
+  }
 
-    public Symbol newSymbol(String nameHint, Type type)
-    {
-        return newSymbol(nameHint, type, null);
-    }
+  public Symbol newSymbol(Symbol symbolHint) {
+    return newSymbol(symbolHint, null);
+  }
 
-    public Symbol newHashSymbol()
-    {
-        return newSymbol("$hashValue", BigintType.BIGINT);
-    }
+  public Symbol newSymbol(Symbol symbolHint, String suffix) {
+    checkArgument(symbols.containsKey(symbolHint), "symbolHint not in symbols map");
+    return newSymbol(symbolHint.getName(), symbols.get(symbolHint), suffix);
+  }
 
-    public Symbol newSymbol(String nameHint, Type type, @Nullable String suffix)
-    {
-        requireNonNull(nameHint, "nameHint is null");
-        requireNonNull(type, "type is null");
+  public Symbol newSymbol(String nameHint, Type type) {
+    return newSymbol(nameHint, type, null);
+  }
 
-        // TODO: workaround for the fact that QualifiedName lowercases parts
-        nameHint = nameHint.toLowerCase(ENGLISH);
+  public Symbol newHashSymbol() {
+    return newSymbol("$hashValue", LongType.getInstance());
+  }
 
-        // don't strip the tail if the only _ is the first character
-        int index = nameHint.lastIndexOf("_");
-        if (index > 0) {
-            String tail = nameHint.substring(index + 1);
+  public Symbol newSymbol(String nameHint, Type type, String suffix) {
+    requireNonNull(nameHint, "nameHint is null");
+    requireNonNull(type, "type is null");
 
-            // only strip if tail is numeric or _ is the last character
-            if (Ints.tryParse(tail) != null || index == nameHint.length() - 1) {
-                nameHint = nameHint.substring(0, index);
-            }
-        }
+    // TODO: workaround for the fact that QualifiedName lowercases parts
+    nameHint = nameHint.toLowerCase(ENGLISH);
 
-        String unique = nameHint;
+    // don't strip the tail if the only _ is the first character
+    int index = nameHint.lastIndexOf("_");
+    if (index > 0) {
+      String tail = nameHint.substring(index + 1);
 
-        if (suffix != null) {
-            unique = unique + "$" + suffix;
-        }
+      // only strip if tail is numeric or _ is the last character
+      if (Ints.tryParse(tail) != null || index == nameHint.length() - 1) {
+        nameHint = nameHint.substring(0, index);
+      }
+    }
 
-        Symbol symbol = new Symbol(unique);
-        while (symbols.putIfAbsent(symbol, type) != null) {
-            symbol = new Symbol(unique + "_" + nextId());
-        }
+    String unique = nameHint;
 
-        return symbol;
+    if (suffix != null) {
+      unique = unique + "$" + suffix;
     }
 
-    public Symbol newSymbol(Expression expression, Type type)
-    {
-        return newSymbol(expression, type, null);
+    Symbol symbol = new Symbol(unique);
+    while (symbols.putIfAbsent(symbol, type) != null) {
+      symbol = new Symbol(unique + "_" + nextId());
     }
 
-    public Symbol newSymbol(Expression expression, Type type, String suffix)
-    {
-        String nameHint = "expr";
-        if (expression instanceof FunctionCall functionCall) {
-            // symbol allocation can happen during planning, before function calls are rewritten
-            nameHint = functionCall.getFunction().getName().getFunctionName();
-        }
-        else if (expression instanceof SymbolReference symbolReference) {
-            nameHint = symbolReference.getName();
-        }
-
-        return newSymbol(nameHint, type, suffix);
+    return symbol;
+  }
+
+  public Symbol newSymbol(Expression expression, Type type) {
+    return newSymbol(expression, type, null);
+  }
+
+  public Symbol newSymbol(Expression expression, Type type, String suffix) {
+    String nameHint = "expr";
+    if (expression instanceof FunctionCall) {
+      FunctionCall functionCall = (FunctionCall) expression;
+      // symbol allocation can happen during planning, before function calls are rewritten
+      nameHint = functionCall.getName().toString();
+    } else if (expression instanceof SymbolReference) {
+      SymbolReference symbolReference = (SymbolReference) expression;
+      nameHint = symbolReference.getName();
     }
 
-    public Symbol newSymbol(Field field)
-    {
-        String nameHint = field.getName().orElse("field");
-        return newSymbol(nameHint, field.getType());
-    }
+    return newSymbol(nameHint, type, suffix);
+  }
 
-    public TypeProvider getTypes()
-    {
-        return TypeProvider.viewOf(symbols);
-    }
+  public Symbol newSymbol(Field field) {
+    String nameHint = field.getName().orElse("field");
+    return newSymbol(nameHint, field.getType());
+  }
 
-    private int nextId()
-    {
-        return nextId++;
-    }
+  public TypeProvider getTypes() {
+    return TypeProvider.viewOf(symbols);
+  }
+
+  private int nextId() {
+    return nextId++;
+  }
 }
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 805078c8afd..e56946216fc 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
@@ -2,7 +2,6 @@ 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.ExchangeNode;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.process.SingleChildProcessNode;
 import org.apache.iotdb.db.relational.sql.tree.Expression;
 
@@ -12,30 +11,26 @@ import java.nio.ByteBuffer;
 import java.util.List;
 
 public class FilterNode extends SingleChildProcessNode {
-    private final Expression predicate;
+  private final Expression predicate;
 
-    public FilterNode(PlanNodeId id, PlanNode child, Expression predicate) {
-        super(id, child);
-        this.predicate = predicate;
-    }
+  public FilterNode(PlanNodeId id, PlanNode child, Expression predicate) {
+    super(id, child);
+    this.predicate = predicate;
+  }
 
-    @Override
-    public PlanNode clone() {
-        return null;
-    }
+  @Override
+  public PlanNode clone() {
+    return null;
+  }
 
-    @Override
-    public List<String> getOutputColumnNames() {
-        return null;
-    }
+  @Override
+  public List<String> getOutputColumnNames() {
+    return null;
+  }
 
-    @Override
-    protected void serializeAttributes(ByteBuffer byteBuffer) {
+  @Override
+  protected void serializeAttributes(ByteBuffer byteBuffer) {}
 
-    }
-
-    @Override
-    protected void serializeAttributes(DataOutputStream stream) throws IOException {
-
-    }
+  @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/LimitNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/LimitNode.java
new file mode 100644
index 00000000000..41b376e04be
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/LimitNode.java
@@ -0,0 +1,43 @@
+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.SingleChildProcessNode;
+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;
+import java.util.Optional;
+
+public class LimitNode extends SingleChildProcessNode {
+  private final long count;
+  // what's the meaning?
+  private final Optional<OrderingScheme> tiesResolvingScheme;
+  // private final boolean partial;
+  // private final List<Symbol> preSortedInputs;
+
+  public LimitNode(
+      PlanNodeId id, PlanNode child, long count, Optional<OrderingScheme> tiesResolvingScheme) {
+    super(id, child);
+    this.count = count;
+    this.tiesResolvingScheme = tiesResolvingScheme;
+  }
+
+  @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/OffsetNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OffsetNode.java
new file mode 100644
index 00000000000..c4ea93a27cb
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OffsetNode.java
@@ -0,0 +1,35 @@
+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.SingleChildProcessNode;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class OffsetNode extends SingleChildProcessNode {
+  private final long count;
+
+  public OffsetNode(PlanNodeId id, PlanNode child, long count) {
+    super(id, child);
+    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/node/OutputNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OutputNode.java
index befc2af78c0..cb884b09a56 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OutputNode.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/OutputNode.java
@@ -12,35 +12,32 @@ import java.util.List;
 
 public class OutputNode extends SingleChildProcessNode {
 
-    private final List<String> outputColumnNames;
-
-    private final List<Symbol> symbols;
-
-    public OutputNode(PlanNodeId id, PlanNode child, List<String> outputColumnNames, List<Symbol> symbols) {
-        super(id, child);
-        this.id = id;
-        this.child = child;
-        this.outputColumnNames = outputColumnNames;
-        this.symbols = symbols;
-    }
-
-    @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 {
-
-    }
+  private final List<String> outputColumnNames;
+
+  private final List<Symbol> symbols;
+
+  public OutputNode(
+      PlanNodeId id, PlanNode child, List<String> outputColumnNames, List<Symbol> symbols) {
+    super(id, child);
+    this.id = id;
+    this.child = child;
+    this.outputColumnNames = outputColumnNames;
+    this.symbols = symbols;
+  }
+
+  @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/ProjectNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ProjectNode.java
index 20fbfd1d02f..ff8de50de27 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ProjectNode.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ProjectNode.java
@@ -11,30 +11,26 @@ import java.nio.ByteBuffer;
 import java.util.List;
 
 public class ProjectNode extends SingleChildProcessNode {
-    private final Assignments assignments;
+  private final Assignments assignments;
 
-    public ProjectNode(PlanNodeId id, PlanNode child, Assignments assignments) {
-        super(id, child);
-        this.assignments = assignments;
-    }
+  public ProjectNode(PlanNodeId id, PlanNode child, Assignments assignments) {
+    super(id, child);
+    this.assignments = assignments;
+  }
 
-    @Override
-    public PlanNode clone() {
-        return null;
-    }
+  @Override
+  public PlanNode clone() {
+    return null;
+  }
 
-    @Override
-    public List<String> getOutputColumnNames() {
-        return null;
-    }
+  @Override
+  public List<String> getOutputColumnNames() {
+    return null;
+  }
 
-    @Override
-    protected void serializeAttributes(ByteBuffer byteBuffer) {
+  @Override
+  protected void serializeAttributes(ByteBuffer byteBuffer) {}
 
-    }
-
-    @Override
-    protected void serializeAttributes(DataOutputStream stream) throws IOException {
-
-    }
+  @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/SortNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/SortNode.java
new file mode 100644
index 00000000000..37358d3d696
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/SortNode.java
@@ -0,0 +1,38 @@
+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.SingleChildProcessNode;
+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 SortNode extends SingleChildProcessNode {
+  private final OrderingScheme orderingScheme;
+  private final boolean partial;
+
+  public SortNode(PlanNodeId id, PlanNode planNode, OrderingScheme scheme, boolean partial) {
+    super(id, planNode);
+    this.orderingScheme = scheme;
+    this.partial = partial;
+  }
+
+  @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 c6f6c64f671..860bbbab443 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
@@ -1,6 +1,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.relational.metadata.ColumnHandle;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.Symbol;
@@ -12,42 +13,47 @@ import java.util.List;
 import java.util.Map;
 
 public class TableScanNode extends PlanNode {
-    private final TableHandle table;
-    private final List<Symbol> outputSymbols;
-    private final Map<Symbol, ColumnHandle> assignments; // symbol -> column
-
-    @Override
-    public List<PlanNode> getChildren() {
-        return null;
-    }
-
-    @Override
-    public void addChild(PlanNode child) {
-
-    }
-
-    @Override
-    public PlanNode clone() {
-        return null;
-    }
-
-    @Override
-    public int allowedChildCount() {
-        return 0;
-    }
-
-    @Override
-    public List<String> getOutputColumnNames() {
-        return null;
-    }
-
-    @Override
-    protected void serializeAttributes(ByteBuffer byteBuffer) {
-
-    }
-
-    @Override
-    protected void serializeAttributes(DataOutputStream stream) throws IOException {
-
-    }
+  private final TableHandle table;
+  private final List<Symbol> outputSymbols;
+  private final Map<Symbol, ColumnHandle> assignments; // symbol -> column
+
+  public TableScanNode(
+      PlanNodeId id,
+      TableHandle table,
+      List<Symbol> outputSymbols,
+      Map<Symbol, ColumnHandle> assignments) {
+    super(id);
+    this.table = table;
+    this.outputSymbols = outputSymbols;
+    this.assignments = assignments;
+  }
+
+  @Override
+  public List<PlanNode> getChildren() {
+    return null;
+  }
+
+  @Override
+  public void addChild(PlanNode child) {}
+
+  @Override
+  public PlanNode clone() {
+    return null;
+  }
+
+  @Override
+  public int allowedChildCount() {
+    return 0;
+  }
+
+  @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/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
index 01b972ada52..35b7e24919c 100644
--- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
@@ -122,8 +122,8 @@ public class AnalyzerTest {
   @Test
   public void testSingleTableQuery() throws OperatorNotFoundException {
     String sql =
-        "SELECT tag1 as tmp_tag, tag2, attribute1, s1+1 as add_s1, s2 FROM table1 " +
-                "WHERE time>1 AND tag1=\"A\" and tag3=\"B\" AND s1=1 AND s3=3 ORDER BY time DESC OFFSET 10 LIMIT 5";
+        "SELECT tag1 as tmp_tag, tag2, attribute1, s1+1 as add_s1, s2 FROM table1 "
+            + "WHERE time>1 AND tag1=\"A\" and tag3=\"B\" AND s1=1 AND s3=3 ORDER BY time DESC OFFSET 10 LIMIT 5";
     Metadata metadata = new TestMatadata();
 
     TableHandle tableHandle = Mockito.mock(TableHandle.class);
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMatadata.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMatadata.java
index 1ed6652d1a9..294dc28b354 100644
--- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMatadata.java
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMatadata.java
@@ -12,6 +12,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableMetadata;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableSchema;
 import org.apache.iotdb.tsfile.read.common.type.Type;
+
 import org.mockito.Mockito;
 
 import java.util.Arrays;
@@ -25,55 +26,57 @@ import static org.apache.iotdb.tsfile.read.common.type.LongType.INT64;
 
 public class TestMatadata implements Metadata {
 
-    public static final String DB1 = "db1";
-    public static final String DB2 = "db2";
-    public static final String TABLE1 = "table1";
-    public static final String TABLE2 = "table2";
-
+  public static final String DB1 = "db1";
+  public static final String DB2 = "db2";
+  public static final String TABLE1 = "table1";
+  public static final String TABLE2 = "table2";
 
-    @Override
-    public boolean tableExists(QualifiedObjectName name) {
-        return name.getDatabaseName().equalsIgnoreCase(DB1) && name.getObjectName().equalsIgnoreCase(TABLE1);
-    }
+  @Override
+  public boolean tableExists(QualifiedObjectName name) {
+    return name.getDatabaseName().equalsIgnoreCase(DB1)
+        && name.getObjectName().equalsIgnoreCase(TABLE1);
+  }
 
-    @Override
-    public TableSchema getTableSchema(SessionInfo session, TableHandle tableHandle) {
-        return null;
-    }
+  @Override
+  public TableSchema getTableSchema(SessionInfo session, TableHandle tableHandle) {
+    return null;
+  }
 
-    @Override
-    public TableMetadata getTableMetadata(SessionInfo session, TableHandle tableHandle) {
-        return null;
-    }
+  @Override
+  public TableMetadata getTableMetadata(SessionInfo session, TableHandle tableHandle) {
+    return null;
+  }
 
-    @Override
-    public Optional<TableHandle> getTableHandle(SessionInfo session, QualifiedObjectName name) {
-        return Optional.empty();
-    }
+  @Override
+  public Optional<TableHandle> getTableHandle(SessionInfo session, QualifiedObjectName name) {
+    return Optional.empty();
+  }
 
-    @Override
-    public Map<String, ColumnHandle> getColumnHandles(SessionInfo session, TableHandle tableHandle) {
-        Map<String, ColumnHandle> map = new HashMap<>();
-        TableSchema tableSchema = Mockito.mock(TableSchema.class);
-        Mockito.when(tableSchema.getTableName()).thenReturn("table1");
-        ColumnSchema column1 =
-                ColumnSchema.builder().setName("time").setType(INT64).setHidden(false).build();
-        ColumnHandle column1Handle = Mockito.mock(ColumnHandle.class);
-        map.put("time", column1Handle);
-        ColumnSchema column2 =
-                ColumnSchema.builder().setName("s1").setType(INT32).setHidden(false).build();
-        ColumnHandle column2Handle = Mockito.mock(ColumnHandle.class);
-        map.put("s1", column2Handle);
-        ColumnSchema column3 =
-                ColumnSchema.builder().setName("s2").setType(INT64).setHidden(false).build();
-        ColumnHandle column3Handle = Mockito.mock(ColumnHandle.class);
-        map.put("s2", column3Handle);
-        List<ColumnSchema> columnSchemaList = Arrays.asList(column1, column2, column3);
-        return columnSchemaList;
-    }
+  @Override
+  public Map<String, ColumnHandle> getColumnHandles(SessionInfo session, TableHandle tableHandle) {
+    Map<String, ColumnHandle> map = new HashMap<>();
+    TableSchema tableSchema = Mockito.mock(TableSchema.class);
+    Mockito.when(tableSchema.getTableName()).thenReturn("table1");
+    ColumnSchema column1 =
+        ColumnSchema.builder().setName("time").setType(INT64).setHidden(false).build();
+    ColumnHandle column1Handle = Mockito.mock(ColumnHandle.class);
+    map.put("time", column1Handle);
+    ColumnSchema column2 =
+        ColumnSchema.builder().setName("s1").setType(INT32).setHidden(false).build();
+    ColumnHandle column2Handle = Mockito.mock(ColumnHandle.class);
+    map.put("s1", column2Handle);
+    ColumnSchema column3 =
+        ColumnSchema.builder().setName("s2").setType(INT64).setHidden(false).build();
+    ColumnHandle column3Handle = Mockito.mock(ColumnHandle.class);
+    map.put("s2", column3Handle);
+    List<ColumnSchema> columnSchemaList = Arrays.asList(column1, column2, column3);
+    return columnSchemaList;
+  }
 
-    @Override
-    public ResolvedFunction resolveOperator(OperatorType operatorType, List<? extends Type> argumentTypes) throws OperatorNotFoundException {
-        return null;
-    }
+  @Override
+  public ResolvedFunction resolveOperator(
+      OperatorType operatorType, List<? extends Type> argumentTypes)
+      throws OperatorNotFoundException {
+    return null;
+  }
 }