You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2024/02/27 13:27:30 UTC

(iotdb) 02/02: partial

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

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

commit 29153818e070259ade89fdc62c1cd03220a39996
Author: JackieTien97 <ja...@gmail.com>
AuthorDate: Tue Feb 27 21:27:20 2024 +0800

    partial
---
 .../iotdb/db/queryengine/common/SessionInfo.java   |   29 +
 .../relational/analyzer/AggregationAnalyzer.java   |  459 +++++++
 .../plan/relational/analyzer/Analysis.java         |  179 ++-
 ...essionAnalyzer.java => CorrelationSupport.java} |    5 +-
 .../relational/analyzer/ExpressionAnalyzer.java    |   68 +-
 .../relational/analyzer/ExpressionTreeUtils.java   |   15 +
 .../analyzer/ScopeReferenceExtractor.java          |   60 +
 .../relational/analyzer/StatementAnalyzer.java     | 1371 +++++++++++++++++---
 .../ColumnHandle.java}                             |   22 +-
 .../plan/relational/metadata/ColumnMetadata.java   |  213 +++
 .../plan/relational/metadata/ColumnSchema.java     |  121 ++
 .../plan/relational/metadata/Metadata.java         |   58 +
 .../plan/relational/metadata/MetadataUtil.java     |   94 +-
 .../TableHandle.java}                              |    4 +-
 .../plan/relational/metadata/TableMetadata.java    |   93 ++
 .../TableSchema.java}                              |   24 +-
 .../plan/relational/planner/Symbol.java            |   77 ++
 .../SymbolResolver.java}                           |    6 +-
 .../AccessControl.java}                            |    4 +-
 .../Identity.java}                                 |   14 +-
 .../iotdb/tsfile/read/common/type/BinaryType.java  |   14 +-
 .../iotdb/tsfile/read/common/type/BooleanType.java |   14 +-
 .../iotdb/tsfile/read/common/type/DoubleType.java  |   14 +-
 .../iotdb/tsfile/read/common/type/FloatType.java   |   14 +-
 .../iotdb/tsfile/read/common/type/IntType.java     |   14 +-
 .../iotdb/tsfile/read/common/type/LongType.java    |   14 +-
 .../apache/iotdb/tsfile/read/common/type/Type.java |    7 +
 27 files changed, 2731 insertions(+), 276 deletions(-)

diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/SessionInfo.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/SessionInfo.java
index 43c933585a9..a4a28dd2f38 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/SessionInfo.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/SessionInfo.java
@@ -20,23 +20,30 @@
 package org.apache.iotdb.db.queryengine.common;
 
 import org.apache.iotdb.commons.conf.IoTDBConstant.ClientVersion;
+import org.apache.iotdb.db.queryengine.plan.relational.security.Identity;
 import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
 
+import javax.annotation.Nullable;
+
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Optional;
 
 public class SessionInfo {
   private final long sessionId;
   private final String userName;
   private final String zoneId;
 
+  @Nullable private final String databaseName;
+
   private ClientVersion version = ClientVersion.V_1_0;
 
   public SessionInfo(long sessionId, String userName, String zoneId) {
     this.sessionId = sessionId;
     this.userName = userName;
     this.zoneId = zoneId;
+    this.databaseName = null;
   }
 
   public SessionInfo(long sessionId, String userName, String zoneId, ClientVersion version) {
@@ -44,6 +51,20 @@ public class SessionInfo {
     this.userName = userName;
     this.zoneId = zoneId;
     this.version = version;
+    this.databaseName = null;
+  }
+
+  public SessionInfo(
+      long sessionId,
+      String userName,
+      String zoneId,
+      @Nullable String databaseName,
+      ClientVersion version) {
+    this.sessionId = sessionId;
+    this.userName = userName;
+    this.zoneId = zoneId;
+    this.databaseName = databaseName;
+    this.version = version;
   }
 
   public long getSessionId() {
@@ -62,6 +83,14 @@ public class SessionInfo {
     return version;
   }
 
+  public Identity getIdentity() {
+    return new Identity(userName);
+  }
+
+  public Optional<String> getDatabaseName() {
+    return Optional.ofNullable(databaseName);
+  }
+
   public static SessionInfo deserializeFrom(ByteBuffer buffer) {
     long sessionId = ReadWriteIOUtils.readLong(buffer);
     String userName = ReadWriteIOUtils.readString(buffer);
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
new file mode 100644
index 00000000000..b1a642d95e5
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
@@ -0,0 +1,459 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+
+import org.apache.iotdb.db.exception.sql.SemanticException;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware;
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticBinaryExpression;
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticUnaryExpression;
+import org.apache.iotdb.db.relational.sql.tree.AstVisitor;
+import org.apache.iotdb.db.relational.sql.tree.BetweenPredicate;
+import org.apache.iotdb.db.relational.sql.tree.Cast;
+import org.apache.iotdb.db.relational.sql.tree.CoalesceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.CurrentTime;
+import org.apache.iotdb.db.relational.sql.tree.DereferenceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ExistsPredicate;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.FieldReference;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
+import org.apache.iotdb.db.relational.sql.tree.Identifier;
+import org.apache.iotdb.db.relational.sql.tree.IfExpression;
+import org.apache.iotdb.db.relational.sql.tree.InListExpression;
+import org.apache.iotdb.db.relational.sql.tree.InPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNotNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.LikePredicate;
+import org.apache.iotdb.db.relational.sql.tree.Literal;
+import org.apache.iotdb.db.relational.sql.tree.LogicalExpression;
+import org.apache.iotdb.db.relational.sql.tree.Node;
+import org.apache.iotdb.db.relational.sql.tree.NotExpression;
+import org.apache.iotdb.db.relational.sql.tree.NullIfExpression;
+import org.apache.iotdb.db.relational.sql.tree.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.QuantifiedComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.SearchedCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression;
+import org.apache.iotdb.db.relational.sql.tree.Trim;
+import org.apache.iotdb.db.relational.sql.tree.WhenClause;
+
+import com.google.common.collect.ImmutableList;
+
+import javax.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.extractAggregateFunctions;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.isAggregationFunction;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.getReferencesToScope;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.hasReferencesToScope;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.isFieldFromScope;
+import static org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware.scopeAwareKey;
+
+/** Checks whether an expression is constant with respect to the group */
+class AggregationAnalyzer {
+
+  // fields and expressions in the group by clause
+  private final Set<FieldId> groupingFields;
+  private final Set<ScopeAware<Expression>> expressions;
+  private final Map<NodeRef<Expression>, ResolvedField> columnReferences;
+
+  private final Analysis analysis;
+  private final Scope sourceScope;
+  private final Optional<Scope> orderByScope;
+
+  public static void verifySourceAggregations(
+      List<Expression> groupByExpressions,
+      Scope sourceScope,
+      List<Expression> expressions,
+      Analysis analysis) {
+    AggregationAnalyzer analyzer =
+        new AggregationAnalyzer(groupByExpressions, sourceScope, Optional.empty(), analysis);
+    for (Expression expression : expressions) {
+      analyzer.analyze(expression);
+    }
+  }
+
+  public static void verifyOrderByAggregations(
+      List<Expression> groupByExpressions,
+      Scope sourceScope,
+      Scope orderByScope,
+      List<Expression> expressions,
+      Analysis analysis) {
+    AggregationAnalyzer analyzer =
+        new AggregationAnalyzer(
+            groupByExpressions, sourceScope, Optional.of(orderByScope), analysis);
+    for (Expression expression : expressions) {
+      analyzer.analyze(expression);
+    }
+  }
+
+  private AggregationAnalyzer(
+      List<Expression> groupByExpressions,
+      Scope sourceScope,
+      Optional<Scope> orderByScope,
+      Analysis analysis) {
+    requireNonNull(groupByExpressions, "groupByExpressions is null");
+    requireNonNull(sourceScope, "sourceScope is null");
+    requireNonNull(orderByScope, "orderByScope is null");
+    requireNonNull(analysis, "analysis is null");
+
+    this.sourceScope = sourceScope;
+    this.orderByScope = orderByScope;
+    this.analysis = analysis;
+    this.expressions =
+        groupByExpressions.stream()
+            .map(expression -> scopeAwareKey(expression, analysis, sourceScope))
+            .collect(toImmutableSet());
+
+    // No defensive copy here for performance reasons.
+    // Copying this map may lead to quadratic time complexity
+    this.columnReferences = analysis.getColumnReferenceFields();
+
+    this.groupingFields =
+        groupByExpressions.stream()
+            .map(NodeRef::of)
+            .filter(columnReferences::containsKey)
+            .map(columnReferences::get)
+            .map(ResolvedField::getFieldId)
+            .collect(toImmutableSet());
+
+    this.groupingFields.forEach(
+        fieldId -> {
+          checkState(
+              isFieldFromScope(fieldId, sourceScope),
+              "Grouping field %s should originate from %s",
+              fieldId,
+              sourceScope.getRelationType());
+        });
+  }
+
+  private void analyze(Expression expression) {
+    Visitor visitor = new Visitor();
+    if (!visitor.process(expression, null)) {
+      throw new SemanticException(
+          String.format(
+              "'%s' must be an aggregate expression or appear in GROUP BY clause", expression));
+    }
+  }
+
+  /** visitor returns true if all expressions are constant with respect to the group. */
+  private class Visitor extends AstVisitor<Boolean, Void> {
+    @Override
+    protected Boolean visitExpression(Expression node, Void context) {
+      throw new UnsupportedOperationException(
+          "aggregation analysis not yet implemented for: " + node.getClass().getName());
+    }
+
+    @Override
+    protected Boolean visitSubqueryExpression(SubqueryExpression node, Void context) {
+      /*
+       * Column reference can resolve to (a) some subquery's scope, (b) a projection (ORDER BY scope),
+       * (c) source scope or (d) outer query scope (effectively a constant).
+       * From AggregationAnalyzer's perspective, only case (c) needs verification.
+       */
+      getReferencesToScope(node, analysis, sourceScope)
+          .filter(expression -> !isGroupingKey(expression))
+          .findFirst()
+          .ifPresent(
+              expression -> {
+                throw new SemanticException(
+                    String.format(
+                        "Subquery uses '%s' which must appear in GROUP BY clause", expression));
+              });
+
+      return true;
+    }
+
+    @Override
+    protected Boolean visitExists(ExistsPredicate node, Void context) {
+      checkState(node.getSubquery() instanceof SubqueryExpression);
+      return process(node.getSubquery(), context);
+    }
+
+    @Override
+    protected Boolean visitCast(Cast node, Void context) {
+      return process(node.getExpression(), context);
+    }
+
+    @Override
+    protected Boolean visitCoalesceExpression(CoalesceExpression node, Void context) {
+      return node.getOperands().stream().allMatch(expression -> process(expression, context));
+    }
+
+    @Override
+    protected Boolean visitNullIfExpression(NullIfExpression node, Void context) {
+      return process(node.getFirst(), context) && process(node.getSecond(), context);
+    }
+
+    @Override
+    protected Boolean visitBetweenPredicate(BetweenPredicate node, Void context) {
+      return process(node.getMin(), context)
+          && process(node.getValue(), context)
+          && process(node.getMax(), context);
+    }
+
+    @Override
+    protected Boolean visitCurrentTime(CurrentTime node, Void context) {
+      return true;
+    }
+
+    @Override
+    protected Boolean visitArithmeticBinary(ArithmeticBinaryExpression node, Void context) {
+      return process(node.getLeft(), context) && process(node.getRight(), context);
+    }
+
+    @Override
+    protected Boolean visitComparisonExpression(ComparisonExpression node, Void context) {
+      return process(node.getLeft(), context) && process(node.getRight(), context);
+    }
+
+    @Override
+    protected Boolean visitLiteral(Literal node, Void context) {
+      return true;
+    }
+
+    @Override
+    protected Boolean visitIsNotNullPredicate(IsNotNullPredicate node, Void context) {
+      return process(node.getValue(), context);
+    }
+
+    @Override
+    protected Boolean visitIsNullPredicate(IsNullPredicate node, Void context) {
+      return process(node.getValue(), context);
+    }
+
+    @Override
+    protected Boolean visitLikePredicate(LikePredicate node, Void context) {
+      return process(node.getValue(), context) && process(node.getPattern(), context);
+    }
+
+    @Override
+    protected Boolean visitInListExpression(InListExpression node, Void context) {
+      return node.getValues().stream().allMatch(expression -> process(expression, context));
+    }
+
+    @Override
+    protected Boolean visitInPredicate(InPredicate node, Void context) {
+      return process(node.getValue(), context) && process(node.getValueList(), context);
+    }
+
+    @Override
+    protected Boolean visitQuantifiedComparisonExpression(
+        QuantifiedComparisonExpression node, Void context) {
+      return process(node.getValue(), context) && process(node.getSubquery(), context);
+    }
+
+    @Override
+    protected Boolean visitTrim(Trim node, Void context) {
+      return process(node.getTrimSource(), context)
+          && (!node.getTrimCharacter().isPresent()
+              || process(node.getTrimCharacter().get(), context));
+    }
+
+    @Override
+    protected Boolean visitFunctionCall(FunctionCall node, Void context) {
+      if (isAggregationFunction(node.getName().toString())) {
+        List<FunctionCall> aggregateFunctions = extractAggregateFunctions(node.getArguments());
+
+        if (!aggregateFunctions.isEmpty()) {
+          throw new SemanticException(
+              String.format(
+                  "Cannot nest aggregations inside aggregation '%s': %s",
+                  node.getName(), aggregateFunctions));
+        }
+
+        return true;
+      }
+
+      return node.getArguments().stream().allMatch(expression -> process(expression, context));
+    }
+
+    @Override
+    protected Boolean visitIdentifier(Identifier node, Void context) {
+      //      if (analysis.getLambdaArgumentReferences().containsKey(NodeRef.of(node))) {
+      //        return true;
+      //      }
+
+      if (!hasReferencesToScope(node, analysis, sourceScope)) {
+        // reference to outer scope is group-invariant
+        return true;
+      }
+
+      return isGroupingKey(node);
+    }
+
+    @Override
+    protected Boolean visitDereferenceExpression(DereferenceExpression node, Void context) {
+
+      if (!hasReferencesToScope(node, analysis, sourceScope)) {
+        // reference to outer scope is group-invariant
+        return true;
+      }
+
+      if (columnReferences.containsKey(NodeRef.<Expression>of(node))) {
+        return isGroupingKey(node);
+      }
+
+      // Allow SELECT col1.f1 FROM table1 GROUP BY col1
+      return process(node.getBase(), context);
+    }
+
+    private boolean isGroupingKey(Expression node) {
+      FieldId fieldId =
+          requireNonNull(columnReferences.get(NodeRef.of(node)), () -> "No field for " + node)
+              .getFieldId();
+
+      if (orderByScope.isPresent() && isFieldFromScope(fieldId, orderByScope.get())) {
+        return true;
+      }
+
+      return groupingFields.contains(fieldId);
+    }
+
+    @Override
+    protected Boolean visitFieldReference(FieldReference node, Void context) {
+      if (orderByScope.isPresent()) {
+        return true;
+      }
+
+      FieldId fieldId =
+          requireNonNull(columnReferences.get(NodeRef.of(node)), () -> "No field for " + node)
+              .getFieldId();
+      boolean inGroup = groupingFields.contains(fieldId);
+      if (!inGroup) {
+        Field field = sourceScope.getRelationType().getFieldByIndex(node.getFieldIndex());
+
+        String column;
+        if (!field.getName().isPresent()) {
+          column = Integer.toString(node.getFieldIndex() + 1);
+        } else if (field.getRelationAlias().isPresent()) {
+          column = format("'%s.%s'", field.getRelationAlias().get(), field.getName().get());
+        } else {
+          column = "'" + field.getName().get() + "'";
+        }
+
+        throw new SemanticException(String.format("Column %s not in GROUP BY clause", column));
+      }
+      return inGroup;
+    }
+
+    @Override
+    protected Boolean visitArithmeticUnary(ArithmeticUnaryExpression node, Void context) {
+      return process(node.getValue(), context);
+    }
+
+    @Override
+    protected Boolean visitNotExpression(NotExpression node, Void context) {
+      return process(node.getValue(), context);
+    }
+
+    @Override
+    protected Boolean visitLogicalExpression(LogicalExpression node, Void context) {
+      return node.getTerms().stream().allMatch(item -> process(item, context));
+    }
+
+    @Override
+    protected Boolean visitIfExpression(IfExpression node, Void context) {
+      ImmutableList.Builder<Expression> expressions =
+          ImmutableList.<Expression>builder().add(node.getCondition()).add(node.getTrueValue());
+
+      if (node.getFalseValue().isPresent()) {
+        expressions.add(node.getFalseValue().get());
+      }
+
+      return expressions.build().stream().allMatch(expression -> process(expression, context));
+    }
+
+    @Override
+    protected Boolean visitSimpleCaseExpression(SimpleCaseExpression node, Void context) {
+      if (!process(node.getOperand(), context)) {
+        return false;
+      }
+
+      for (WhenClause whenClause : node.getWhenClauses()) {
+        if (!process(whenClause.getOperand(), context)
+            || !process(whenClause.getResult(), context)) {
+          return false;
+        }
+      }
+
+      return !node.getDefaultValue().isPresent() || process(node.getDefaultValue().get(), context);
+    }
+
+    @Override
+    protected Boolean visitSearchedCaseExpression(SearchedCaseExpression node, Void context) {
+      for (WhenClause whenClause : node.getWhenClauses()) {
+        if (!process(whenClause.getOperand(), context)
+            || !process(whenClause.getResult(), context)) {
+          return false;
+        }
+      }
+
+      return !node.getDefaultValue().isPresent() || process(node.getDefaultValue().get(), context);
+    }
+
+    @Override
+    protected Boolean visitParameter(Parameter node, Void context) {
+      //      if (analysis.isDescribe()) {
+      //        return true;
+      //      }
+      Map<NodeRef<Parameter>, Expression> parameters = analysis.getParameters();
+      checkArgument(
+          node.getId() < parameters.size(),
+          "Invalid parameter number %s, max values is %s",
+          node.getId(),
+          parameters.size() - 1);
+      return process(parameters.get(NodeRef.of(node)), context);
+    }
+
+    @Override
+    public Boolean process(Node node, @Nullable Void context) {
+      if (node instanceof Expression
+          && expressions.contains(scopeAwareKey(node, analysis, sourceScope))
+          && (!orderByScope.isPresent() || !hasOrderByReferencesToOutputColumns(node))) {
+        return true;
+      }
+
+      return super.process(node, context);
+    }
+
+    private boolean hasOrderByReferencesToOutputColumns(Node node) {
+      return hasReferencesToScope(node, analysis, orderByScope.get());
+    }
+
+    private void verifyNoOrderByReferencesToOutputColumns(Node node, String errorString) {
+      getReferencesToScope(node, analysis, orderByScope.get())
+          .findFirst()
+          .ifPresent(
+              expression -> {
+                throw new SemanticException(errorString);
+              });
+    }
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analysis.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analysis.java
index ad2e99dc599..0a2d4ff03df 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analysis.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analysis.java
@@ -19,7 +19,11 @@
 
 package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
 
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnHandle;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.QualifiedObjectName;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+import org.apache.iotdb.db.queryengine.plan.relational.security.Identity;
 import org.apache.iotdb.db.relational.sql.tree.AllColumns;
 import org.apache.iotdb.db.relational.sql.tree.ExistsPredicate;
 import org.apache.iotdb.db.relational.sql.tree.Expression;
@@ -30,6 +34,7 @@ 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.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
 import org.apache.iotdb.db.relational.sql.tree.QuantifiedComparisonExpression;
 import org.apache.iotdb.db.relational.sql.tree.Query;
 import org.apache.iotdb.db.relational.sql.tree.QuerySpecification;
@@ -88,6 +93,17 @@ public class Analysis {
   // map inner recursive reference in the expandable query to the recursion base scope
   private final Map<NodeRef<Node>, Scope> expandableBaseScopes = new LinkedHashMap<>();
 
+  // Synthetic scope when a query does not have a FROM clause
+  // We need to track this separately because there's no node we can attach it to.
+  private final Map<NodeRef<QuerySpecification>, Scope> implicitFromScopes = new LinkedHashMap<>();
+  private final Map<NodeRef<Node>, Scope> scopes = new LinkedHashMap<>();
+
+  private final Map<NodeRef<Expression>, ResolvedField> columnReferences = new LinkedHashMap<>();
+
+  // a map of users to the columns per table that they access
+  private final Map<AccessControlInfo, Map<QualifiedObjectName, Set<String>>>
+      tableColumnReferences = new LinkedHashMap<>();
+
   private final Map<NodeRef<Offset>, Long> offset = new LinkedHashMap<>();
   private final Map<NodeRef<Node>, OptionalLong> limit = new LinkedHashMap<>();
   private final Map<NodeRef<AllColumns>, List<Field>> selectAllResultFields = new LinkedHashMap<>();
@@ -96,6 +112,8 @@ public class Analysis {
   private final Map<NodeRef<Join>, JoinUsingAnalysis> joinUsing = new LinkedHashMap<>();
   private final Map<NodeRef<Node>, SubqueryAnalysis> subQueries = new LinkedHashMap<>();
 
+  private final Map<NodeRef<Table>, TableEntry> tables = new LinkedHashMap<>();
+
   private final Map<NodeRef<Expression>, Type> types = new LinkedHashMap<>();
 
   private final Map<NodeRef<Expression>, Type> coercions = new LinkedHashMap<>();
@@ -103,9 +121,7 @@ public class Analysis {
 
   private final Map<NodeRef<Relation>, List<Type>> relationCoercions = new LinkedHashMap<>();
 
-  private final Map<NodeRef<Node>, Scope> scopes = new LinkedHashMap<>();
-
-  private final Map<NodeRef<Expression>, ResolvedField> columnReferences = new LinkedHashMap<>();
+  private final Map<Field, ColumnHandle> columns = new LinkedHashMap<>();
 
   private final Map<NodeRef<QuerySpecification>, List<FunctionCall>> aggregates =
       new LinkedHashMap<>();
@@ -122,6 +138,12 @@ public class Analysis {
 
   private final Multimap<Field, SourceColumn> originColumnDetails = ArrayListMultimap.create();
 
+  private final Multimap<NodeRef<Expression>, Field> fieldLineage = ArrayListMultimap.create();
+
+  private final Map<NodeRef<Relation>, QualifiedName> relationNames = new LinkedHashMap<>();
+
+  private final Set<NodeRef<Relation>> aliasedRelations = new LinkedHashSet<>();
+
   public Analysis(@Nullable Statement root, Map<NodeRef<Parameter>, Expression> parameters) {
     this.root = root;
     this.parameters = ImmutableMap.copyOf(requireNonNull(parameters, "parameters is null"));
@@ -186,6 +208,14 @@ public class Analysis {
                     format("Analysis does not contain information for node: %s", node)));
   }
 
+  public void setImplicitFromScope(QuerySpecification node, Scope scope) {
+    implicitFromScopes.put(NodeRef.of(node), scope);
+  }
+
+  public Scope getImplicitFromScope(QuerySpecification node) {
+    return implicitFromScopes.get(NodeRef.of(node));
+  }
+
   public Optional<Scope> tryGetScope(Node node) {
     NodeRef<Node> key = NodeRef.of(node);
     if (scopes.containsKey(key)) {
@@ -257,6 +287,14 @@ public class Analysis {
     relationCoercions.put(NodeRef.of(relation), ImmutableList.copyOf(types));
   }
 
+  public void setColumn(Field field, ColumnHandle handle) {
+    columns.put(field, handle);
+  }
+
+  public ColumnHandle getColumn(Field field) {
+    return columns.get(field);
+  }
+
   public Map<NodeRef<Expression>, Type> getCoercions() {
     return unmodifiableMap(coercions);
   }
@@ -278,6 +316,14 @@ public class Analysis {
     this.typeOnlyCoercions.addAll(typeOnlyCoercions);
   }
 
+  public Set<NodeRef<Expression>> getTypeOnlyCoercions() {
+    return unmodifiableSet(typeOnlyCoercions);
+  }
+
+  public boolean isTypeOnlyCoercion(Expression expression) {
+    return typeOnlyCoercions.contains(NodeRef.of(expression));
+  }
+
   public void setGroupingSets(QuerySpecification node, GroupingSetAnalysis groupingSets) {
     this.groupingSets.put(NodeRef.of(node), groupingSets);
   }
@@ -393,6 +439,26 @@ public class Analysis {
     return subQueries.computeIfAbsent(NodeRef.of(node), key -> new SubqueryAnalysis());
   }
 
+  public TableHandle getTableHandle(Table table) {
+    return tables
+        .get(NodeRef.of(table))
+        .getHandle()
+        .orElseThrow(
+            () -> new IllegalArgumentException(format("%s is not a table reference", table)));
+  }
+
+  public Collection<TableHandle> getTables() {
+    return tables.values().stream()
+        .map(TableEntry::getHandle)
+        .filter(Optional::isPresent)
+        .map(Optional::get)
+        .collect(toImmutableList());
+  }
+
+  public void registerTable(Table table, Optional<TableHandle> handle, QualifiedObjectName name) {
+    tables.put(NodeRef.of(table), new TableEntry(handle, name));
+  }
+
   public ResolvedField getResolvedField(Expression expression) {
     checkArgument(
         isColumnReference(expression), "Expression is not a column reference: %s", expression);
@@ -404,6 +470,31 @@ public class Analysis {
     return columnReferences.containsKey(NodeRef.of(expression));
   }
 
+  public void addTableColumnReferences(
+      AccessControl accessControl,
+      Identity identity,
+      Multimap<QualifiedObjectName, String> tableColumnMap) {
+    AccessControlInfo accessControlInfo = new AccessControlInfo(accessControl, identity);
+    Map<QualifiedObjectName, Set<String>> references =
+        tableColumnReferences.computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>());
+    tableColumnMap
+        .asMap()
+        .forEach(
+            (key, value) -> references.computeIfAbsent(key, k -> new HashSet<>()).addAll(value));
+  }
+
+  public void addEmptyColumnReferencesForTable(
+      AccessControl accessControl, Identity identity, QualifiedObjectName table) {
+    AccessControlInfo accessControlInfo = new AccessControlInfo(accessControl, identity);
+    tableColumnReferences
+        .computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>())
+        .computeIfAbsent(table, k -> new HashSet<>());
+  }
+
+  public Map<AccessControlInfo, Map<QualifiedObjectName, Set<String>>> getTableColumnReferences() {
+    return tableColumnReferences;
+  }
+
   public RelationType getOutputDescriptor() {
     return getOutputDescriptor(root);
   }
@@ -420,6 +511,88 @@ public class Analysis {
     return ImmutableSet.copyOf(originColumnDetails.get(field));
   }
 
+  public void addExpressionFields(Expression expression, Collection<Field> fields) {
+    fieldLineage.putAll(NodeRef.of(expression), fields);
+  }
+
+  public Set<SourceColumn> getExpressionSourceColumns(Expression expression) {
+    return fieldLineage.get(NodeRef.of(expression)).stream()
+        .flatMap(field -> getSourceColumns(field).stream())
+        .collect(toImmutableSet());
+  }
+
+  public void setRelationName(Relation relation, QualifiedName name) {
+    relationNames.put(NodeRef.of(relation), name);
+  }
+
+  public QualifiedName getRelationName(Relation relation) {
+    return relationNames.get(NodeRef.of(relation));
+  }
+
+  public void addAliased(Relation relation) {
+    aliasedRelations.add(NodeRef.of(relation));
+  }
+
+  public boolean isAliased(Relation relation) {
+    return aliasedRelations.contains(NodeRef.of(relation));
+  }
+
+  public static final class AccessControlInfo {
+    private final AccessControl accessControl;
+    private final Identity identity;
+
+    public AccessControlInfo(AccessControl accessControl, Identity identity) {
+      this.accessControl = requireNonNull(accessControl, "accessControl is null");
+      this.identity = requireNonNull(identity, "identity is null");
+    }
+
+    public AccessControl getAccessControl() {
+      return accessControl;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+
+      AccessControlInfo that = (AccessControlInfo) o;
+      return Objects.equals(accessControl, that.accessControl)
+          && Objects.equals(identity, that.identity);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(accessControl, identity);
+    }
+
+    @Override
+    public String toString() {
+      return format("AccessControl: %s, Identity: %s", accessControl.getClass(), identity);
+    }
+  }
+
+  private static class TableEntry {
+    private final Optional<TableHandle> handle;
+    private final QualifiedObjectName name;
+
+    public TableEntry(Optional<TableHandle> handle, QualifiedObjectName name) {
+      this.handle = requireNonNull(handle, "handle is null");
+      this.name = requireNonNull(name, "name is null");
+    }
+
+    public Optional<TableHandle> getHandle() {
+      return handle;
+    }
+
+    public QualifiedObjectName getName() {
+      return name;
+    }
+  }
+
   public static class SourceColumn {
     private final QualifiedObjectName tableName;
     private final String columnName;
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/CorrelationSupport.java
similarity index 93%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/CorrelationSupport.java
index 61d95d2e832..dbf72adcd18 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/CorrelationSupport.java
@@ -19,4 +19,7 @@
 
 package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
 
-public class ExpressionAnalyzer {}
+public enum CorrelationSupport {
+  ALLOWED,
+  DISALLOWED
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
index 61d95d2e832..bd906b816f1 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
@@ -19,4 +19,70 @@
 
 package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
 
-public class ExpressionAnalyzer {}
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.queryengine.execution.warnings.WarningCollector;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.Identifier;
+
+import java.util.Optional;
+
+import static java.util.Objects.requireNonNull;
+
+public class ExpressionAnalyzer {
+
+  public static ExpressionAnalysis analyzeExpression(
+      SessionInfo session,
+      AccessControl accessControl,
+      Scope scope,
+      Analysis analysis,
+      Expression expression,
+      WarningCollector warningCollector,
+      CorrelationSupport correlationSupport) {
+    //    ExpressionAnalyzer analyzer =
+    //        new ExpressionAnalyzer(accessControl, analysis, session,
+    //            TypeProvider.empty(), warningCollector);
+    //    analyzer.analyze(expression, scope, correlationSupport);
+    //
+    //    updateAnalysis(analysis, analyzer, session, accessControl);
+    //    analysis.addExpressionFields(expression, analyzer.getSourceFields());
+    //
+    //    return new ExpressionAnalysis(
+    //        analyzer.getExpressionTypes(),
+    //        analyzer.getExpressionCoercions(),
+    //        analyzer.getSubqueryInPredicates(),
+    //        analyzer.getSubqueries(),
+    //        analyzer.getExistsSubqueries(),
+    //        analyzer.getColumnReferences(),
+    //        analyzer.getTypeOnlyCoercions(),
+    //        analyzer.getQuantifiedComparisons(),
+    //        analyzer.getWindowFunctions());
+    return null;
+  }
+
+  public static class LabelPrefixedReference {
+    private final String label;
+    private final Optional<Identifier> column;
+
+    public LabelPrefixedReference(String label, Identifier column) {
+      this(label, Optional.of(requireNonNull(column, "column is null")));
+    }
+
+    public LabelPrefixedReference(String label) {
+      this(label, Optional.empty());
+    }
+
+    private LabelPrefixedReference(String label, Optional<Identifier> column) {
+      this.label = requireNonNull(label, "label is null");
+      this.column = requireNonNull(column, "column is null");
+    }
+
+    public String getLabel() {
+      return label;
+    }
+
+    public Optional<Identifier> getColumn() {
+      return column;
+    }
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionTreeUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionTreeUtils.java
index 1c564af206d..8bd81cc63d8 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionTreeUtils.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionTreeUtils.java
@@ -19,9 +19,11 @@
 
 package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
 
+import org.apache.iotdb.commons.udf.builtin.BuiltinAggregationFunction;
 import org.apache.iotdb.db.relational.sql.tree.DefaultExpressionTraversalVisitor;
 import org.apache.iotdb.db.relational.sql.tree.DereferenceExpression;
 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.Identifier;
 import org.apache.iotdb.db.relational.sql.tree.Node;
 import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
@@ -39,6 +41,10 @@ import static java.util.Objects.requireNonNull;
 public final class ExpressionTreeUtils {
   private ExpressionTreeUtils() {}
 
+  static List<FunctionCall> extractAggregateFunctions(Iterable<? extends Node> nodes) {
+    return extractExpressions(nodes, FunctionCall.class, ExpressionTreeUtils::isAggregation);
+  }
+
   public static <T extends Expression> List<T> extractExpressions(
       Iterable<? extends Node> nodes, Class<T> clazz) {
     return extractExpressions(nodes, clazz, alwaysTrue());
@@ -58,6 +64,10 @@ public final class ExpressionTreeUtils {
         .collect(toImmutableList());
   }
 
+  private static boolean isAggregation(FunctionCall functionCall) {
+    return isAggregationFunction(functionCall.getName().toString());
+  }
+
   private static List<Node> linearizeNodes(Node node) {
     ImmutableList.Builder<Node> nodes = ImmutableList.builder();
     new DefaultExpressionTraversalVisitor<Void>() {
@@ -80,4 +90,9 @@ public final class ExpressionTreeUtils {
     }
     return name;
   }
+
+  static boolean isAggregationFunction(String functionName) {
+    // TODO consider UDAF
+    return BuiltinAggregationFunction.getNativeFunctionNames().contains(functionName.toLowerCase());
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ScopeReferenceExtractor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ScopeReferenceExtractor.java
new file mode 100644
index 00000000000..1e4952762e0
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ScopeReferenceExtractor.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.Node;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.iotdb.db.relational.sql.util.AstUtil.preOrder;
+
+/** Extract expressions that are references to a given scope. */
+final class ScopeReferenceExtractor {
+  private ScopeReferenceExtractor() {}
+
+  public static boolean hasReferencesToScope(Node node, Analysis analysis, Scope scope) {
+    return getReferencesToScope(node, analysis, scope).findAny().isPresent();
+  }
+
+  public static Stream<Expression> getReferencesToScope(Node node, Analysis analysis, Scope scope) {
+    Map<NodeRef<Expression>, ResolvedField> columnReferences = analysis.getColumnReferenceFields();
+
+    return preOrder(node)
+        .filter(Expression.class::isInstance)
+        .map(Expression.class::cast)
+        .filter(expression -> columnReferences.containsKey(NodeRef.of(expression)))
+        .filter(expression -> isReferenceToScope(expression, scope, columnReferences));
+  }
+
+  private static boolean isReferenceToScope(
+      Expression node, Scope scope, Map<NodeRef<Expression>, ResolvedField> columnReferences) {
+    ResolvedField field = columnReferences.get(NodeRef.of(node));
+    requireNonNull(field, () -> "No Field for " + node);
+    return isFieldFromScope(field.getFieldId(), scope);
+  }
+
+  public static boolean isFieldFromScope(FieldId field, Scope scope) {
+    return Objects.equals(field.getRelationId(), scope.getRelationId());
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
index f24c0ea0388..26a4d9b8c8c 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
@@ -20,10 +20,19 @@
 package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
 
 import org.apache.iotdb.db.exception.sql.SemanticException;
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
 import org.apache.iotdb.db.queryengine.execution.warnings.IoTDBWarning;
 import org.apache.iotdb.db.queryengine.execution.warnings.WarningCollector;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnHandle;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnSchema;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.QualifiedObjectName;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableHandle;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.TableSchema;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
 import org.apache.iotdb.db.relational.sql.tree.AddColumn;
+import org.apache.iotdb.db.relational.sql.tree.AliasedRelation;
 import org.apache.iotdb.db.relational.sql.tree.AllColumns;
 import org.apache.iotdb.db.relational.sql.tree.AllRows;
 import org.apache.iotdb.db.relational.sql.tree.AstVisitor;
@@ -43,15 +52,23 @@ import org.apache.iotdb.db.relational.sql.tree.Explain;
 import org.apache.iotdb.db.relational.sql.tree.ExplainAnalyze;
 import org.apache.iotdb.db.relational.sql.tree.Expression;
 import org.apache.iotdb.db.relational.sql.tree.FieldReference;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
+import org.apache.iotdb.db.relational.sql.tree.GroupBy;
+import org.apache.iotdb.db.relational.sql.tree.GroupingElement;
+import org.apache.iotdb.db.relational.sql.tree.GroupingSets;
 import org.apache.iotdb.db.relational.sql.tree.Identifier;
 import org.apache.iotdb.db.relational.sql.tree.Insert;
 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.JoinCriteria;
+import org.apache.iotdb.db.relational.sql.tree.JoinOn;
+import org.apache.iotdb.db.relational.sql.tree.JoinUsing;
 import org.apache.iotdb.db.relational.sql.tree.Limit;
 import org.apache.iotdb.db.relational.sql.tree.LongLiteral;
+import org.apache.iotdb.db.relational.sql.tree.NaturalJoin;
 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.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.OrderBy;
 import org.apache.iotdb.db.relational.sql.tree.Property;
 import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
 import org.apache.iotdb.db.relational.sql.tree.Query;
@@ -61,14 +78,17 @@ import org.apache.iotdb.db.relational.sql.tree.RenameColumn;
 import org.apache.iotdb.db.relational.sql.tree.RenameTable;
 import org.apache.iotdb.db.relational.sql.tree.Select;
 import org.apache.iotdb.db.relational.sql.tree.SelectItem;
+import org.apache.iotdb.db.relational.sql.tree.SetOperation;
 import org.apache.iotdb.db.relational.sql.tree.SetProperties;
 import org.apache.iotdb.db.relational.sql.tree.ShowDB;
 import org.apache.iotdb.db.relational.sql.tree.ShowFunctions;
 import org.apache.iotdb.db.relational.sql.tree.ShowIndex;
 import org.apache.iotdb.db.relational.sql.tree.ShowTables;
+import org.apache.iotdb.db.relational.sql.tree.SimpleGroupBy;
 import org.apache.iotdb.db.relational.sql.tree.SingleColumn;
 import org.apache.iotdb.db.relational.sql.tree.SortItem;
 import org.apache.iotdb.db.relational.sql.tree.Statement;
+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;
@@ -78,14 +98,21 @@ import org.apache.iotdb.db.relational.sql.tree.With;
 import org.apache.iotdb.db.relational.sql.tree.WithQuery;
 import org.apache.iotdb.tsfile.read.common.type.Type;
 
-import com.google.common.base.VerifyException;
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Streams;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.OptionalLong;
@@ -94,31 +121,59 @@ import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.collect.Iterables.getLast;
 import static java.lang.Math.toIntExact;
 import static java.util.Collections.emptyList;
 import static java.util.Locale.ENGLISH;
 import static java.util.Objects.requireNonNull;
 import static org.apache.iotdb.db.queryengine.execution.warnings.StandardWarningCode.REDUNDANT_ORDER_BY;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.AggregationAnalyzer.verifyOrderByAggregations;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.AggregationAnalyzer.verifySourceAggregations;
 import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.CanonicalizationAware.canonicalizationAwareKey;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.asQualifiedName;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.extractAggregateFunctions;
+import static org.apache.iotdb.db.queryengine.plan.relational.analyzer.Scope.BasisType.TABLE;
+import static org.apache.iotdb.db.queryengine.plan.relational.metadata.MetadataUtil.createQualifiedObjectName;
 import static org.apache.iotdb.db.queryengine.plan.relational.utils.NodeUtils.getSortItemsFromOrderBy;
 import static org.apache.iotdb.db.relational.sql.tree.Join.Type.FULL;
+import static org.apache.iotdb.db.relational.sql.tree.Join.Type.INNER;
 import static org.apache.iotdb.db.relational.sql.tree.Join.Type.LEFT;
 import static org.apache.iotdb.db.relational.sql.tree.Join.Type.RIGHT;
 import static org.apache.iotdb.db.relational.sql.util.AstUtil.preOrder;
+import static org.apache.iotdb.tsfile.read.common.type.BooleanType.BOOLEAN;
 
 public class StatementAnalyzer {
 
   private final Analysis analysis;
 
+  private final AccessControl accessControl;
+
   private final WarningCollector warningCollector;
 
-  public StatementAnalyzer(Analysis analysis, WarningCollector warningCollector) {
+  private final SessionInfo sessionContext;
+
+  private final Metadata metadata;
+
+  private final CorrelationSupport correlationSupport;
+
+  public StatementAnalyzer(
+      Analysis analysis,
+      AccessControl accessControl,
+      WarningCollector warningCollector,
+      SessionInfo sessionContext,
+      Metadata metadata,
+      CorrelationSupport correlationSupport) {
     this.analysis = analysis;
+    this.accessControl = accessControl;
     this.warningCollector = warningCollector;
+    this.sessionContext = sessionContext;
+    this.metadata = metadata;
+    this.correlationSupport = correlationSupport;
   }
 
   public Scope analyze(Node node) {
@@ -551,7 +606,7 @@ public class StatementAnalyzer {
           stepType.getVisibleFields().stream().map(Field::getType).collect(toImmutableList());
 
       for (int i = 0; i < anchorFieldTypes.size(); i++) {
-        if (!typeCoercion.canCoerce(stepFieldTypes.get(i), anchorFieldTypes.get(i))) {
+        if (stepFieldTypes.get(i) != anchorFieldTypes.get(i)) {
           // TODO for more precise error location, pass the mismatching select expression instead of
           // `step`
           throw new SemanticException(
@@ -574,9 +629,17 @@ public class StatementAnalyzer {
     @Override
     protected Scope visitTableSubquery(TableSubquery node, Optional<Scope> scope) {
       StatementAnalyzer analyzer =
-          statementAnalyzerFactory.createStatementAnalyzer(
-              analysis, session, warningCollector, CorrelationSupport.ALLOWED);
-      Scope queryScope = analyzer.analyze(node.getQuery(), scope.orElseThrow());
+          new StatementAnalyzer(
+              analysis,
+              accessControl,
+              warningCollector,
+              sessionContext,
+              metadata,
+              CorrelationSupport.ALLOWED);
+      Scope queryScope =
+          analyzer.analyze(
+              node.getQuery(),
+              scope.orElseThrow(() -> new NoSuchElementException("No value present")));
       return createAndAssignScope(node, scope, queryScope.getRelationType());
     }
 
@@ -587,13 +650,11 @@ public class StatementAnalyzer {
 
       Scope sourceScope = analyzeFrom(node, scope);
 
-      analyzeWindowDefinitions(node, sourceScope);
-      resolveFunctionCallAndMeasureWindows(node);
-
       node.getWhere().ifPresent(where -> analyzeWhere(node, sourceScope, where));
 
       List<Expression> outputExpressions = analyzeSelect(node, sourceScope);
-      GroupingSetAnalysis groupByAnalysis = analyzeGroupBy(node, sourceScope, outputExpressions);
+      Analysis.GroupingSetAnalysis groupByAnalysis =
+          analyzeGroupBy(node, sourceScope, outputExpressions);
       analyzeHaving(node, sourceScope);
 
       Scope outputScope = computeAndAssignOutputScope(node, scope, sourceScope);
@@ -607,12 +668,12 @@ public class StatementAnalyzer {
         orderByExpressions = analyzeOrderBy(node, orderBy.getSortItems(), orderByScope.get());
 
         if ((sourceScope.getOuterQueryParent().isPresent() || !isTopLevel)
-            && node.getLimit().isEmpty()
-            && node.getOffset().isEmpty()) {
+            && !node.getLimit().isPresent()
+            && !node.getOffset().isPresent()) {
           // not the root scope and ORDER BY is ineffective
           analysis.markRedundantOrderBy(orderBy);
           warningCollector.add(
-              new TrinoWarning(REDUNDANT_ORDER_BY, "ORDER BY in subquery may have no effect"));
+              new IoTDBWarning(REDUNDANT_ORDER_BY, "ORDER BY in subquery may have no effect"));
         }
       }
       analysis.setOrderByExpressions(node, orderByExpressions);
@@ -623,63 +684,630 @@ public class StatementAnalyzer {
 
       if (node.getLimit().isPresent()) {
         boolean requiresOrderBy = analyzeLimit(node.getLimit().get(), outputScope);
-        if (requiresOrderBy && node.getOrderBy().isEmpty()) {
-          throw semanticException(
-              MISSING_ORDER_BY,
-              node.getLimit().get(),
-              "FETCH FIRST WITH TIES clause requires ORDER BY");
+        if (requiresOrderBy && !node.getOrderBy().isPresent()) {
+          throw new SemanticException("FETCH FIRST WITH TIES clause requires ORDER BY");
         }
       }
 
       List<Expression> sourceExpressions = new ArrayList<>();
       analysis.getSelectExpressions(node).stream()
-          .map(SelectExpression::getExpression)
+          .map(Analysis.SelectExpression::getExpression)
           .forEach(sourceExpressions::add);
       node.getHaving().ifPresent(sourceExpressions::add);
-      for (WindowDefinition windowDefinition : node.getWindows()) {
-        WindowSpecification window = windowDefinition.getWindow();
-        sourceExpressions.addAll(window.getPartitionBy());
-        getSortItemsFromOrderBy(window.getOrderBy()).stream()
-            .map(SortItem::getSortKey)
-            .forEach(sourceExpressions::add);
-        if (window.getFrame().isPresent()) {
-          WindowFrame frame = window.getFrame().get();
-          frame.getStart().getValue().ifPresent(sourceExpressions::add);
-          frame.getEnd().flatMap(FrameBound::getValue).ifPresent(sourceExpressions::add);
-          frame.getMeasures().stream()
-              .map(MeasureDefinition::getExpression)
-              .forEach(sourceExpressions::add);
-          frame.getVariableDefinitions().stream()
-              .map(VariableDefinition::getExpression)
-              .forEach(sourceExpressions::add);
-        }
-      }
 
-      analyzeGroupingOperations(node, sourceExpressions, orderByExpressions);
       analyzeAggregations(
           node, sourceScope, orderByScope, groupByAnalysis, sourceExpressions, orderByExpressions);
-      analyzeWindowFunctionsAndMeasures(node, outputExpressions, orderByExpressions);
 
       if (analysis.isAggregation(node) && node.getOrderBy().isPresent()) {
         ImmutableList.Builder<Expression> aggregates =
             ImmutableList.<Expression>builder()
                 .addAll(groupByAnalysis.getOriginalExpressions())
-                .addAll(
-                    extractAggregateFunctions(
-                        orderByExpressions, session, functionResolver, accessControl))
-                .addAll(extractExpressions(orderByExpressions, GroupingOperation.class));
+                .addAll(extractAggregateFunctions(orderByExpressions));
 
         analysis.setOrderByAggregates(node.getOrderBy().get(), aggregates.build());
       }
 
       if (node.getOrderBy().isPresent() && node.getSelect().isDistinct()) {
         verifySelectDistinct(
-            node, orderByExpressions, outputExpressions, sourceScope, orderByScope.orElseThrow());
+            node,
+            orderByExpressions,
+            outputExpressions,
+            sourceScope,
+            orderByScope.orElseThrow(() -> new NoSuchElementException("No value present")));
       }
 
       return outputScope;
     }
 
+    private Scope analyzeFrom(QuerySpecification node, Optional<Scope> scope) {
+      if (node.getFrom().isPresent()) {
+        return process(node.getFrom().get(), scope);
+      }
+
+      Scope result = createScope(scope);
+      analysis.setImplicitFromScope(node, result);
+      return result;
+    }
+
+    private void analyzeWhere(Node node, Scope scope, Expression predicate) {
+      verifyNoAggregateWindowOrGroupingFunctions(predicate, "WHERE clause");
+
+      ExpressionAnalysis expressionAnalysis = analyzeExpression(predicate, scope);
+      analysis.recordSubqueries(node, expressionAnalysis);
+
+      Type predicateType = expressionAnalysis.getType(predicate);
+      if (!predicateType.equals(BOOLEAN)) {
+        //        if (!predicateType.equals(UNKNOWN)) {
+        throw new SemanticException(
+            String.format(
+                "WHERE clause must evaluate to a boolean: actual type %s", predicateType));
+        //        }
+        // coerce null to boolean
+        //        analysis.addCoercion(predicate, BOOLEAN, false);
+      }
+
+      analysis.setWhere(node, predicate);
+    }
+
+    private List<Expression> analyzeSelect(QuerySpecification node, Scope scope) {
+      ImmutableList.Builder<Expression> outputExpressionBuilder = ImmutableList.builder();
+      ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder =
+          ImmutableList.builder();
+
+      for (SelectItem item : node.getSelect().getSelectItems()) {
+        if (item instanceof AllColumns) {
+          analyzeSelectAllColumns(
+              (AllColumns) item, node, scope, outputExpressionBuilder, selectExpressionBuilder);
+        } else if (item instanceof SingleColumn) {
+          analyzeSelectSingleColumn(
+              (SingleColumn) item, node, scope, outputExpressionBuilder, selectExpressionBuilder);
+        } else {
+          throw new IllegalArgumentException(
+              "Unsupported SelectItem type: " + item.getClass().getName());
+        }
+      }
+      analysis.setSelectExpressions(node, selectExpressionBuilder.build());
+
+      return outputExpressionBuilder.build();
+    }
+
+    private void analyzeSelectAllColumns(
+        AllColumns allColumns,
+        QuerySpecification node,
+        Scope scope,
+        ImmutableList.Builder<Expression> outputExpressionBuilder,
+        ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder) {
+      // expand * and expression.*
+      if (allColumns.getTarget().isPresent()) {
+        // analyze AllColumns with target expression (expression.*)
+        Expression expression = allColumns.getTarget().get();
+
+        QualifiedName prefix = asQualifiedName(expression);
+        if (prefix != null) {
+          // analyze prefix as an 'asterisked identifier chain'
+          Scope.AsteriskedIdentifierChainBasis identifierChainBasis =
+              scope
+                  .resolveAsteriskedIdentifierChainBasis(prefix, allColumns)
+                  .orElseThrow(
+                      () ->
+                          new SemanticException(
+                              String.format("Unable to resolve reference %s", prefix)));
+          if (identifierChainBasis.getBasisType() == TABLE) {
+            RelationType relationType =
+                identifierChainBasis
+                    .getRelationType()
+                    .orElseThrow(() -> new NoSuchElementException("No value present"));
+            List<Field> requestedFields =
+                relationType.resolveVisibleFieldsWithRelationPrefix(Optional.of(prefix));
+            List<Field> fields = filterInaccessibleFields(requestedFields);
+            if (fields.isEmpty()) {
+              if (!requestedFields.isEmpty()) {
+                throw new SemanticException("Relation not found or not allowed");
+              }
+              throw new SemanticException("SELECT * not allowed from relation that has no columns");
+            }
+            boolean local =
+                scope.isLocalScope(
+                    identifierChainBasis
+                        .getScope()
+                        .orElseThrow(() -> new NoSuchElementException("No value present")));
+            analyzeAllColumnsFromTable(
+                fields,
+                allColumns,
+                node,
+                local ? scope : identifierChainBasis.getScope().get(),
+                outputExpressionBuilder,
+                selectExpressionBuilder,
+                relationType,
+                local);
+            return;
+          }
+        }
+        // identifierChainBasis.get().getBasisType == FIELD or target expression isn't a
+        // QualifiedName
+        throw new SemanticException(
+            "identifierChainBasis.get().getBasisType == FIELD or target expression isn't a QualifiedName");
+        //        analyzeAllFieldsFromRowTypeExpression(expression, allColumns, node, scope,
+        // outputExpressionBuilder,
+        //            selectExpressionBuilder);
+      } else {
+        // analyze AllColumns without target expression ('*')
+        if (!allColumns.getAliases().isEmpty()) {
+          throw new SemanticException("Column aliases not supported");
+        }
+
+        List<Field> requestedFields = (List<Field>) scope.getRelationType().getVisibleFields();
+        List<Field> fields = filterInaccessibleFields(requestedFields);
+        if (fields.isEmpty()) {
+          if (!node.getFrom().isPresent()) {
+            throw new SemanticException("SELECT * not allowed in queries without FROM clause");
+          }
+          if (!requestedFields.isEmpty()) {
+            throw new SemanticException("Relation not found or not allowed");
+          }
+          throw new SemanticException("SELECT * not allowed from relation that has no columns");
+        }
+
+        analyzeAllColumnsFromTable(
+            fields,
+            allColumns,
+            node,
+            scope,
+            outputExpressionBuilder,
+            selectExpressionBuilder,
+            scope.getRelationType(),
+            true);
+      }
+    }
+
+    private List<Field> filterInaccessibleFields(List<Field> fields) {
+
+      ImmutableSet.Builder<Field> accessibleFields = ImmutableSet.builder();
+
+      // collect fields by table
+      ListMultimap<QualifiedObjectName, Field> tableFieldsMap = ArrayListMultimap.create();
+      fields.forEach(
+          field -> {
+            Optional<QualifiedObjectName> originTable = field.getOriginTable();
+            if (originTable.isPresent()) {
+              tableFieldsMap.put(originTable.get(), field);
+            } else {
+              // keep anonymous fields accessible
+              accessibleFields.add(field);
+            }
+          });
+
+      // TODO Auth control
+      //      tableFieldsMap.asMap().forEach((table, tableFields) -> {
+      //        Set<String> accessibleColumns = accessControl.filterColumns(
+      //                session.toSecurityContext(),
+      //                table.getCatalogName(),
+      //                ImmutableMap.of(
+      //                    table.asSchemaTableName(),
+      //                    tableFields.stream()
+      //                        .map(field -> field.getOriginColumnName().get())
+      //                        .collect(toImmutableSet())))
+      //            .getOrDefault(table.asSchemaTableName(), ImmutableSet.of());
+      //        accessibleFields.addAll(tableFields.stream()
+      //            .filter(field -> accessibleColumns.contains(field.getOriginColumnName().get()))
+      //            .collect(toImmutableList()));
+      //      });
+
+      return fields.stream().filter(accessibleFields.build()::contains).collect(toImmutableList());
+    }
+
+    private void analyzeAllColumnsFromTable(
+        List<Field> fields,
+        AllColumns allColumns,
+        QuerySpecification node,
+        Scope scope,
+        ImmutableList.Builder<Expression> outputExpressionBuilder,
+        ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder,
+        RelationType relationType,
+        boolean local) {
+      if (!allColumns.getAliases().isEmpty()) {
+        validateColumnAliasesCount(allColumns.getAliases(), fields.size());
+      }
+
+      ImmutableList.Builder<Field> itemOutputFieldBuilder = ImmutableList.builder();
+
+      for (int i = 0; i < fields.size(); i++) {
+        Field field = fields.get(i);
+        Expression fieldExpression;
+        if (local) {
+          fieldExpression = new FieldReference(relationType.indexOf(field));
+        } else {
+          if (!field.getName().isPresent()) {
+            throw new SemanticException(
+                "SELECT * from outer scope table not supported with anonymous columns");
+          }
+          checkState(field.getRelationAlias().isPresent(), "missing relation alias");
+          fieldExpression =
+              new DereferenceExpression(
+                  DereferenceExpression.from(field.getRelationAlias().get()),
+                  new Identifier(field.getName().get()));
+        }
+        analyzeExpression(fieldExpression, scope);
+        outputExpressionBuilder.add(fieldExpression);
+        selectExpressionBuilder.add(
+            new Analysis.SelectExpression(fieldExpression, Optional.empty()));
+
+        Optional<String> alias = field.getName();
+        if (!allColumns.getAliases().isEmpty()) {
+          alias = Optional.of(allColumns.getAliases().get(i).getValue());
+        }
+
+        Field newField =
+            new Field(
+                field.getRelationAlias(),
+                alias,
+                field.getType(),
+                false,
+                field.getOriginTable(),
+                field.getOriginColumnName(),
+                !allColumns.getAliases().isEmpty() || field.isAliased());
+        itemOutputFieldBuilder.add(newField);
+        analysis.addSourceColumns(newField, analysis.getSourceColumns(field));
+
+        Type type = field.getType();
+        if (node.getSelect().isDistinct() && !type.isComparable()) {
+          throw new SemanticException(
+              String.format("DISTINCT can only be applied to comparable types (actual: %s)", type));
+        }
+      }
+      analysis.setSelectAllResultFields(allColumns, itemOutputFieldBuilder.build());
+    }
+
+    //    private void analyzeAllFieldsFromRowTypeExpression(
+    //        Expression expression,
+    //        AllColumns allColumns,
+    //        QuerySpecification node,
+    //        Scope scope,
+    //        ImmutableList.Builder<Expression> outputExpressionBuilder,
+    //        ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder) {
+    //      ImmutableList.Builder<Field> itemOutputFieldBuilder = ImmutableList.builder();
+    //
+    //      ExpressionAnalysis expressionAnalysis = analyzeExpression(expression, scope);
+    //      Type type = expressionAnalysis.getType(expression);
+    //      if (!(type instanceof RowType)) {
+    //        throw semanticException(TYPE_MISMATCH, node.getSelect(), "expected expression of type
+    // Row");
+    //      }
+    //      int referencedFieldsCount = ((RowType) type).getFields().size();
+    //      if (!allColumns.getAliases().isEmpty()) {
+    //        validateColumnAliasesCount(allColumns.getAliases(), referencedFieldsCount);
+    //      }
+    //      analysis.recordSubqueries(node, expressionAnalysis);
+    //
+    //      ImmutableList.Builder<Expression> unfoldedExpressionsBuilder = ImmutableList.builder();
+    //      for (int i = 0; i < referencedFieldsCount; i++) {
+    //        Expression outputExpression = new SubscriptExpression(expression, new LongLiteral("" +
+    // (i + 1)));
+    //        outputExpressionBuilder.add(outputExpression);
+    //        analyzeExpression(outputExpression, scope);
+    //        unfoldedExpressionsBuilder.add(outputExpression);
+    //
+    //        Type outputExpressionType = type.getTypeParameters().get(i);
+    //        if (node.getSelect().isDistinct() && !outputExpressionType.isComparable()) {
+    //          throw semanticException(TYPE_MISMATCH, node.getSelect(),
+    //              "DISTINCT can only be applied to comparable types (actual: %s)",
+    // type.getTypeParameters().get(i));
+    //        }
+    //
+    //        Optional<String> name = ((RowType) type).getFields().get(i).getName();
+    //        if (!allColumns.getAliases().isEmpty()) {
+    //          name = Optional.of(allColumns.getAliases().get(i).getValue());
+    //        }
+    //        itemOutputFieldBuilder.add(Field.newUnqualified(name, outputExpressionType));
+    //      }
+    //      selectExpressionBuilder.add(new SelectExpression(expression,
+    // Optional.of(unfoldedExpressionsBuilder.build())));
+    //      analysis.setSelectAllResultFields(allColumns, itemOutputFieldBuilder.build());
+    //    }
+
+    private void analyzeSelectSingleColumn(
+        SingleColumn singleColumn,
+        QuerySpecification node,
+        Scope scope,
+        ImmutableList.Builder<Expression> outputExpressionBuilder,
+        ImmutableList.Builder<Analysis.SelectExpression> selectExpressionBuilder) {
+      Expression expression = singleColumn.getExpression();
+      ExpressionAnalysis expressionAnalysis = analyzeExpression(expression, scope);
+      analysis.recordSubqueries(node, expressionAnalysis);
+      outputExpressionBuilder.add(expression);
+      selectExpressionBuilder.add(new Analysis.SelectExpression(expression, Optional.empty()));
+
+      Type type = expressionAnalysis.getType(expression);
+      if (node.getSelect().isDistinct() && !type.isComparable()) {
+        throw new SemanticException(
+            String.format(
+                "DISTINCT can only be applied to comparable types (actual: %s): %s",
+                type, expression));
+      }
+    }
+
+    private Analysis.GroupingSetAnalysis analyzeGroupBy(
+        QuerySpecification node, Scope scope, List<Expression> outputExpressions) {
+      if (node.getGroupBy().isPresent()) {
+        ImmutableList.Builder<List<Set<FieldId>>> cubes = ImmutableList.builder();
+        ImmutableList.Builder<List<Set<FieldId>>> rollups = ImmutableList.builder();
+        ImmutableList.Builder<List<Set<FieldId>>> sets = ImmutableList.builder();
+        ImmutableList.Builder<Expression> complexExpressions = ImmutableList.builder();
+        ImmutableList.Builder<Expression> groupingExpressions = ImmutableList.builder();
+
+        checkGroupingSetsCount(node.getGroupBy().get());
+        for (GroupingElement groupingElement : node.getGroupBy().get().getGroupingElements()) {
+          if (groupingElement instanceof SimpleGroupBy) {
+            for (Expression column : groupingElement.getExpressions()) {
+              // simple GROUP BY expressions allow ordinals or arbitrary expressions
+              if (column instanceof LongLiteral) {
+                long ordinal = ((LongLiteral) column).getParsedValue();
+                if (ordinal < 1 || ordinal > outputExpressions.size()) {
+                  throw new SemanticException(
+                      String.format("GROUP BY position %s is not in select list", ordinal));
+                }
+
+                column = outputExpressions.get(toIntExact(ordinal - 1));
+                verifyNoAggregateWindowOrGroupingFunctions(column, "GROUP BY clause");
+              } else {
+                verifyNoAggregateWindowOrGroupingFunctions(column, "GROUP BY clause");
+                analyzeExpression(column, scope);
+              }
+
+              ResolvedField field = analysis.getColumnReferenceFields().get(NodeRef.of(column));
+              if (field != null) {
+                sets.add(ImmutableList.of(ImmutableSet.of(field.getFieldId())));
+              } else {
+                analysis.recordSubqueries(node, analyzeExpression(column, scope));
+                complexExpressions.add(column);
+              }
+
+              groupingExpressions.add(column);
+            }
+          } else if (groupingElement instanceof GroupingSets) {
+            GroupingSets element = (GroupingSets) groupingElement;
+            for (Expression column : groupingElement.getExpressions()) {
+              analyzeExpression(column, scope);
+              if (!analysis.getColumnReferences().contains(NodeRef.of(column))) {
+                throw new SemanticException(
+                    String.format("GROUP BY expression must be a column reference: %s", column));
+              }
+
+              groupingExpressions.add(column);
+            }
+
+            List<Set<FieldId>> groupingSets =
+                element.getSets().stream()
+                    .map(
+                        set ->
+                            set.stream()
+                                .map(NodeRef::of)
+                                .map(analysis.getColumnReferenceFields()::get)
+                                .map(ResolvedField::getFieldId)
+                                .collect(toImmutableSet()))
+                    .collect(toImmutableList());
+
+            switch (element.getType()) {
+              case CUBE:
+                cubes.add(groupingSets);
+                break;
+              case ROLLUP:
+                rollups.add(groupingSets);
+                break;
+              case EXPLICIT:
+                sets.add(groupingSets);
+                break;
+            }
+          }
+        }
+
+        List<Expression> expressions = groupingExpressions.build();
+        for (Expression expression : expressions) {
+          Type type = analysis.getType(expression);
+          if (!type.isComparable()) {
+            throw new SemanticException(
+                String.format(
+                    "%s is not comparable, and therefore cannot be used in GROUP BY", type));
+          }
+        }
+
+        Analysis.GroupingSetAnalysis groupingSets =
+            new Analysis.GroupingSetAnalysis(
+                expressions,
+                cubes.build(),
+                rollups.build(),
+                sets.build(),
+                complexExpressions.build());
+        analysis.setGroupingSets(node, groupingSets);
+
+        return groupingSets;
+      }
+
+      Analysis.GroupingSetAnalysis result =
+          new Analysis.GroupingSetAnalysis(
+              ImmutableList.of(),
+              ImmutableList.of(),
+              ImmutableList.of(),
+              ImmutableList.of(),
+              ImmutableList.of());
+
+      if (hasAggregates(node) || node.getHaving().isPresent()) {
+        analysis.setGroupingSets(node, result);
+      }
+
+      return result;
+    }
+
+    private boolean hasAggregates(QuerySpecification node) {
+      List<Node> toExtract =
+          ImmutableList.<Node>builder()
+              .addAll(node.getSelect().getSelectItems())
+              .addAll(getSortItemsFromOrderBy(node.getOrderBy()))
+              .build();
+
+      List<FunctionCall> aggregates = extractAggregateFunctions(toExtract);
+
+      return !aggregates.isEmpty();
+    }
+
+    private void checkGroupingSetsCount(GroupBy node) {
+      // If groupBy is distinct then crossProduct will be overestimated if there are duplicate
+      // grouping sets.
+      int crossProduct = 1;
+      for (GroupingElement element : node.getGroupingElements()) {
+        try {
+          int product = 0;
+          if (element instanceof SimpleGroupBy) {
+            product = 1;
+          } else if (element instanceof GroupingSets) {
+            GroupingSets groupingSets = (GroupingSets) element;
+            switch (groupingSets.getType()) {
+              case CUBE:
+                int exponent = ((GroupingSets) element).getSets().size();
+                if (exponent > 30) {
+                  throw new ArithmeticException();
+                }
+                product = 1 << exponent;
+                break;
+              case ROLLUP:
+                product = groupingSets.getSets().size() + 1;
+                break;
+              case EXPLICIT:
+                product = groupingSets.getSets().size();
+                break;
+            }
+          } else {
+            throw new UnsupportedOperationException(
+                "Unsupported grouping element type: " + element.getClass().getName());
+          }
+          crossProduct = Math.multiplyExact(crossProduct, product);
+        } catch (ArithmeticException e) {
+          throw new SemanticException(
+              String.format("GROUP BY has more than %s grouping sets", Integer.MAX_VALUE));
+        }
+        //        if (crossProduct > getMaxGroupingSets(session)) {
+        //          throw semanticException(TOO_MANY_GROUPING_SETS, node,
+        //              "GROUP BY has %s grouping sets but can contain at most %s", crossProduct,
+        // getMaxGroupingSets(session));
+        //        }
+      }
+    }
+
+    private void analyzeHaving(QuerySpecification node, Scope scope) {
+      if (node.getHaving().isPresent()) {
+        Expression predicate = node.getHaving().get();
+
+        ExpressionAnalysis expressionAnalysis = analyzeExpression(predicate, scope);
+        analysis.recordSubqueries(node, expressionAnalysis);
+
+        Type predicateType = expressionAnalysis.getType(predicate);
+        if (!predicateType.equals(BOOLEAN)) {
+          throw new SemanticException(
+              String.format(
+                  "HAVING clause must evaluate to a boolean: actual type %s", predicateType));
+        }
+
+        analysis.setHaving(node, predicate);
+      }
+    }
+
+    private Scope computeAndAssignOutputScope(
+        QuerySpecification node, Optional<Scope> scope, Scope sourceScope) {
+      ImmutableList.Builder<Field> outputFields = ImmutableList.builder();
+
+      for (SelectItem item : node.getSelect().getSelectItems()) {
+        if (item instanceof AllColumns) {
+          AllColumns allColumns = (AllColumns) item;
+          List<Field> fields = analysis.getSelectAllResultFields(allColumns);
+          checkNotNull(fields, "output fields is null for select item %s", item);
+          for (int i = 0; i < fields.size(); i++) {
+            Field field = fields.get(i);
+
+            Optional<String> name;
+            if (!allColumns.getAliases().isEmpty()) {
+              name = Optional.of(allColumns.getAliases().get(i).getCanonicalValue());
+            } else {
+              name = field.getName();
+            }
+
+            Field newField =
+                Field.newUnqualified(
+                    name,
+                    field.getType(),
+                    field.getOriginTable(),
+                    field.getOriginColumnName(),
+                    false);
+            analysis.addSourceColumns(newField, analysis.getSourceColumns(field));
+            outputFields.add(newField);
+          }
+        } else if (item instanceof SingleColumn) {
+          SingleColumn column = (SingleColumn) item;
+          Expression expression = column.getExpression();
+          Optional<Identifier> field = column.getAlias();
+
+          Optional<QualifiedObjectName> originTable = Optional.empty();
+          Optional<String> originColumn = Optional.empty();
+          QualifiedName name = null;
+
+          if (expression instanceof Identifier) {
+            name = QualifiedName.of(((Identifier) expression).getValue());
+          } else if (expression instanceof DereferenceExpression) {
+            name = DereferenceExpression.getQualifiedName((DereferenceExpression) expression);
+          }
+
+          if (name != null) {
+            List<Field> matchingFields = sourceScope.getRelationType().resolveFields(name);
+            if (!matchingFields.isEmpty()) {
+              originTable = matchingFields.get(0).getOriginTable();
+              originColumn = matchingFields.get(0).getOriginColumnName();
+            }
+          }
+
+          if (!field.isPresent() && (name != null)) {
+            field = Optional.of(getLast(name.getOriginalParts()));
+          }
+
+          Field newField =
+              Field.newUnqualified(
+                  field.map(Identifier::getValue),
+                  analysis.getType(expression),
+                  originTable,
+                  originColumn,
+                  column.getAlias().isPresent()); // TODO don't use analysis as a side-channel. Use
+          // outputExpressions to look up the type
+          if (originTable.isPresent()) {
+            analysis.addSourceColumns(
+                newField,
+                ImmutableSet.of(
+                    new Analysis.SourceColumn(
+                        originTable.get(),
+                        originColumn.orElseThrow(
+                            () -> new NoSuchElementException("No value present")))));
+          } else {
+            analysis.addSourceColumns(newField, analysis.getExpressionSourceColumns(expression));
+          }
+          outputFields.add(newField);
+        } else {
+          throw new IllegalArgumentException(
+              "Unsupported SelectItem type: " + item.getClass().getName());
+        }
+      }
+
+      return createAndAssignScope(node, scope, outputFields.build());
+    }
+
+    private Scope computeAndAssignOrderByScope(OrderBy node, Scope sourceScope, Scope outputScope) {
+      // ORDER BY should "see" both output and FROM fields during initial analysis and
+      // non-aggregation query planning
+      Scope orderByScope =
+          Scope.builder()
+              .withParent(sourceScope)
+              .withRelationType(outputScope.getRelationId(), outputScope.getRelationType())
+              .build();
+      analysis.setScope(node, orderByScope);
+      return orderByScope;
+    }
+
     @Override
     protected Scope visitSubqueryExpression(SubqueryExpression node, Optional<Scope> context) {
       return process(node.getQuery(), context);
@@ -701,29 +1329,22 @@ public class StatementAnalyzer {
         int outputFieldSize = outputFieldTypes.length;
         int descFieldSize = relationType.getVisibleFields().size();
         if (outputFieldSize != descFieldSize) {
-          throw semanticException(
-              TYPE_MISMATCH,
-              node,
-              "%s query has different number of fields: %d, %d",
-              setOperationName,
-              outputFieldSize,
-              descFieldSize);
+          throw new SemanticException(
+              String.format(
+                  "%s query has different number of fields: %d, %d",
+                  setOperationName, outputFieldSize, descFieldSize));
         }
         for (int i = 0; i < descFieldSize; i++) {
           Type descFieldType = relationType.getFieldByIndex(i).getType();
-          Optional<Type> commonSuperType =
-              typeCoercion.getCommonSuperType(outputFieldTypes[i], descFieldType);
-          if (commonSuperType.isEmpty()) {
-            throw semanticException(
-                TYPE_MISMATCH,
-                node,
-                "column %d in %s query has incompatible types: %s, %s",
-                i + 1,
-                setOperationName,
-                outputFieldTypes[i].getDisplayName(),
-                descFieldType.getDisplayName());
+          if (descFieldType != outputFieldTypes[i]) {
+            throw new SemanticException(
+                String.format(
+                    "column %d in %s query has incompatible types: %s, %s",
+                    i + 1,
+                    setOperationName,
+                    outputFieldTypes[i].getDisplayName(),
+                    descFieldType.getDisplayName()));
           }
-          outputFieldTypes[i] = commonSuperType.get();
         }
       }
 
@@ -732,13 +1353,10 @@ public class StatementAnalyzer {
           || node instanceof Union && node.isDistinct()) {
         for (Type type : outputFieldTypes) {
           if (!type.isComparable()) {
-            throw semanticException(
-                TYPE_MISMATCH,
-                node,
-                "Type %s is not comparable and therefore cannot be used in %s%s",
-                type,
-                setOperationName,
-                node instanceof Union ? " DISTINCT" : "");
+            throw new SemanticException(
+                String.format(
+                    "Type %s is not comparable and therefore cannot be used in %s%s",
+                    type, setOperationName, node instanceof Union ? " DISTINCT" : ""));
           }
         }
       }
@@ -782,64 +1400,328 @@ public class StatementAnalyzer {
     }
 
     @Override
-    protected Scope visitJoin(Join node, Optional<Scope> scope) {
-      JoinCriteria criteria = node.getCriteria().orElse(null);
-      if (criteria instanceof NaturalJoin) {
-        throw semanticException(NOT_SUPPORTED, node, "Natural join not supported");
+    protected Scope visitTable(Table table, Optional<Scope> scope) {
+      if (!table.getName().getPrefix().isPresent()) {
+        // is this a reference to a WITH query?
+        Optional<WithQuery> withQuery =
+            createScope(scope).getNamedQuery(table.getName().getSuffix());
+        if (withQuery.isPresent()) {
+          analysis.setRelationName(table, table.getName());
+          return createScopeForCommonTableExpression(table, scope, withQuery.get());
+        }
+        // is this a recursive reference in expandable WITH query? If so, there's base scope
+        // recorded.
+        Optional<Scope> expandableBaseScope = analysis.getExpandableBaseScope(table);
+        if (expandableBaseScope.isPresent()) {
+          Scope baseScope = expandableBaseScope.get();
+          // adjust local and outer parent scopes accordingly to the local context of the recursive
+          // reference
+          Scope resultScope =
+              scopeBuilder(scope)
+                  .withRelationType(baseScope.getRelationId(), baseScope.getRelationType())
+                  .build();
+          analysis.setScope(table, resultScope);
+          analysis.setRelationName(table, table.getName());
+          return resultScope;
+        }
       }
 
-      Scope left = process(node.getLeft(), scope);
-      Scope right =
-          process(node.getRight(), isLateralRelation(node.getRight()) ? Optional.of(left) : scope);
-
-      if (isLateralRelation(node.getRight())) {
-        if (node.getType() == RIGHT || node.getType() == FULL) {
-          Stream<Expression> leftScopeReferences =
-              getReferencesToScope(node.getRight(), analysis, left);
-          leftScopeReferences
-              .findFirst()
-              .ifPresent(
-                  reference -> {
-                    throw semanticException(
-                        INVALID_COLUMN_REFERENCE,
-                        reference,
-                        "LATERAL reference not allowed in %s JOIN",
-                        node.getType().name());
-                  });
-        }
-        if (isUnnestRelation(node.getRight())) {
-          if (criteria != null) {
-            if (!(criteria instanceof JoinOn)
-                || !((JoinOn) criteria).getExpression().equals(TRUE_LITERAL)) {
-              throw semanticException(
-                  NOT_SUPPORTED,
-                  criteria instanceof JoinOn ? ((JoinOn) criteria).getExpression() : node,
-                  "%s JOIN involving UNNEST is only supported with condition ON TRUE",
-                  node.getType().name());
-            }
-          }
-        } else if (isJsonTable(node.getRight())) {
-          if (criteria != null) {
-            if (!(criteria instanceof JoinOn)
-                || !((JoinOn) criteria).getExpression().equals(TRUE_LITERAL)) {
-              throw semanticException(
-                  NOT_SUPPORTED,
-                  criteria instanceof JoinOn ? ((JoinOn) criteria).getExpression() : node,
-                  "%s JOIN involving JSON_TABLE is only supported with condition ON TRUE",
-                  node.getType().name());
-            }
+      QualifiedObjectName name = createQualifiedObjectName(sessionContext, table, table.getName());
+      analysis.setRelationName(
+          table, QualifiedName.of(name.getDatabaseName(), name.getObjectName()));
+
+      Optional<TableHandle> tableHandle = metadata.getTableHandle(sessionContext, name);
+      // This can only be a table
+      if (!tableHandle.isPresent()) {
+        throw new SemanticException(String.format("Table '%s' does not exist", name));
+      }
+      analysis.addEmptyColumnReferencesForTable(accessControl, sessionContext.getIdentity(), name);
+
+      TableSchema tableSchema = metadata.getTableSchema(sessionContext, tableHandle.get());
+      Map<String, ColumnHandle> columnHandles =
+          metadata.getColumnHandles(sessionContext, tableHandle.get());
+
+      ImmutableList.Builder<Field> fields = ImmutableList.builder();
+      fields.addAll(analyzeTableOutputFields(table, name, tableSchema, columnHandles));
+
+      //      boolean addRowIdColumn = updateKind.isPresent();
+      //
+      //      if (addRowIdColumn) {
+      //        // Add the row id field
+      //        ColumnHandle rowIdColumnHandle = metadata.getMergeRowIdColumnHandle(session,
+      // tableHandle.get());
+      //        Type type = metadata.getColumnMetadata(session, tableHandle.get(),
+      // rowIdColumnHandle).getType();
+      //        Field field = Field.newUnqualified(Optional.empty(), type);
+      //        fields.add(field);
+      //        analysis.setColumn(field, rowIdColumnHandle);
+      //      }
+
+      List<Field> outputFields = fields.build();
+
+      Scope accessControlScope =
+          Scope.builder()
+              .withRelationType(RelationId.anonymous(), new RelationType(outputFields))
+              .build();
+      //      analyzeFiltersAndMasks(table, name, new RelationType(outputFields),
+      // accessControlScope);
+      analysis.registerTable(table, tableHandle, name);
+
+      Scope tableScope = createAndAssignScope(table, scope, outputFields);
+
+      //      if (addRowIdColumn) {
+      //        FieldReference reference = new FieldReference(outputFields.size() - 1);
+      //        analyzeExpression(reference, tableScope);
+      //        analysis.setRowIdField(table, reference);
+      //      }
+
+      return tableScope;
+    }
+
+    private Scope createScopeForCommonTableExpression(
+        Table table, Optional<Scope> scope, WithQuery withQuery) {
+      Query query = withQuery.getQuery();
+      analysis.registerNamedQuery(table, query);
+
+      // re-alias the fields with the name assigned to the query in the WITH declaration
+      RelationType queryDescriptor = analysis.getOutputDescriptor(query);
+
+      List<Field> fields;
+      Optional<List<Identifier>> columnNames = withQuery.getColumnNames();
+      if (columnNames.isPresent()) {
+        // if columns are explicitly aliased -> WITH cte(alias1, alias2 ...)
+        checkState(
+            columnNames.get().size() == queryDescriptor.getVisibleFieldCount(),
+            "mismatched aliases");
+        ImmutableList.Builder<Field> fieldBuilder = ImmutableList.builder();
+        Iterator<Identifier> aliases = columnNames.get().iterator();
+        for (int i = 0; i < queryDescriptor.getAllFieldCount(); i++) {
+          Field inputField = queryDescriptor.getFieldByIndex(i);
+          if (!inputField.isHidden()) {
+            Field field =
+                Field.newQualified(
+                    QualifiedName.of(table.getName().getSuffix()),
+                    Optional.of(aliases.next().getValue()),
+                    inputField.getType(),
+                    false,
+                    inputField.getOriginTable(),
+                    inputField.getOriginColumnName(),
+                    inputField.isAliased());
+            fieldBuilder.add(field);
+            analysis.addSourceColumns(field, analysis.getSourceColumns(inputField));
           }
-        } else if (node.getType() == FULL) {
-          if (!(criteria instanceof JoinOn)
-              || !((JoinOn) criteria).getExpression().equals(TRUE_LITERAL)) {
-            throw semanticException(
-                NOT_SUPPORTED,
-                criteria instanceof JoinOn ? ((JoinOn) criteria).getExpression() : node,
-                "FULL JOIN involving LATERAL relation is only supported with condition ON TRUE");
+        }
+        fields = fieldBuilder.build();
+      } else {
+        ImmutableList.Builder<Field> fieldBuilder = ImmutableList.builder();
+        for (int i = 0; i < queryDescriptor.getAllFieldCount(); i++) {
+          Field inputField = queryDescriptor.getFieldByIndex(i);
+          if (!inputField.isHidden()) {
+            Field field =
+                Field.newQualified(
+                    QualifiedName.of(table.getName().getSuffix()),
+                    inputField.getName(),
+                    inputField.getType(),
+                    false,
+                    inputField.getOriginTable(),
+                    inputField.getOriginColumnName(),
+                    inputField.isAliased());
+            fieldBuilder.add(field);
+            analysis.addSourceColumns(field, analysis.getSourceColumns(inputField));
           }
         }
+        fields = fieldBuilder.build();
+      }
+
+      return createAndAssignScope(table, scope, fields);
+    }
+
+    private List<Field> analyzeTableOutputFields(
+        Table table,
+        QualifiedObjectName tableName,
+        TableSchema tableSchema,
+        Map<String, ColumnHandle> columnHandles) {
+      // TODO: discover columns lazily based on where they are needed (to support connectors that
+      // can't enumerate all tables)
+      ImmutableList.Builder<Field> fields = ImmutableList.builder();
+      for (ColumnSchema column : tableSchema.getColumns()) {
+        Field field =
+            Field.newQualified(
+                table.getName(),
+                Optional.of(column.getName()),
+                column.getType(),
+                column.isHidden(),
+                Optional.of(tableName),
+                Optional.of(column.getName()),
+                false);
+        fields.add(field);
+        ColumnHandle columnHandle = columnHandles.get(column.getName());
+        checkArgument(columnHandle != null, "Unknown field %s", field);
+        analysis.setColumn(field, columnHandle);
+        analysis.addSourceColumns(
+            field, ImmutableSet.of(new Analysis.SourceColumn(tableName, column.getName())));
+      }
+      return fields.build();
+    }
+
+    //    private void analyzeFiltersAndMasks(Table table, QualifiedObjectName name, RelationType
+    // relationType,
+    //                                        Scope accessControlScope) {
+    //      for (int index = 0; index < relationType.getAllFieldCount(); index++) {
+    //        Field field = relationType.getFieldByIndex(index);
+    //        if (field.getName().isPresent()) {
+    //          Optional<ViewExpression> mask =
+    //              accessControl.getColumnMask(session.toSecurityContext(), name,
+    // field.getName().get(), field.getType());
+    //
+    //          if (mask.isPresent() && checkCanSelectFromColumn(name,
+    // field.getName().orElseThrow())) {
+    //            analyzeColumnMask(session.getIdentity().getUser(), table, name, field,
+    // accessControlScope, mask.get());
+    //          }
+    //        }
+    //      }
+    //
+    //      accessControl.getRowFilters(session.toSecurityContext(), name)
+    //          .forEach(
+    //              filter -> analyzeRowFilter(session.getIdentity().getUser(), table, name,
+    // accessControlScope, filter));
+    //    }
+
+    //    @Override
+    //    protected Scope visitValues(Values node, Optional<Scope> scope) {
+    //      checkState(!node.getRows().isEmpty());
+    //
+    //      List<Type> rowTypes = node.getRows().stream()
+    //          .map(row -> analyzeExpression(row, createScope(scope)).getType(row))
+    //          .map(type -> {
+    //            if (type instanceof RowType) {
+    //              return type;
+    //            }
+    //            return RowType.anonymousRow(type);
+    //          })
+    //          .collect(toImmutableList());
+    //
+    //      int fieldCount = rowTypes.get(0).getTypeParameters().size();
+    //      Type commonSuperType = rowTypes.get(0);
+    //      for (Type rowType : rowTypes) {
+    //        // check field count consistency for rows
+    //        if (rowType.getTypeParameters().size() != fieldCount) {
+    //          throw semanticException(TYPE_MISMATCH,
+    //              node,
+    //              "Values rows have mismatched sizes: %s vs %s",
+    //              fieldCount,
+    //              rowType.getTypeParameters().size());
+    //        }
+    //
+    //        // determine common super type of the rows
+    //        commonSuperType = typeCoercion.getCommonSuperType(rowType, commonSuperType)
+    //            .orElseThrow(() -> semanticException(TYPE_MISMATCH,
+    //                node,
+    //                "Values rows have mismatched types: %s vs %s",
+    //                rowTypes.get(0),
+    //                rowType));
+    //      }
+    //
+    //      // add coercions
+    //      for (Expression row : node.getRows()) {
+    //        Type actualType = analysis.getType(row);
+    //        if (row instanceof Row) {
+    //          // coerce Row by fields to preserve Row structure and enable optimizations based on
+    // this structure, e.g. pruning, predicate extraction
+    //          // TODO coerce the whole Row and add an Optimizer rule that converts CAST(ROW(...)
+    // AS ...) into ROW(CAST(...), CAST(...), ...).
+    //          //  The rule would also handle Row-type expressions that were specified as
+    // CAST(ROW). It should support multiple casts over a ROW.
+    //          for (int i = 0; i < actualType.getTypeParameters().size(); i++) {
+    //            Expression item = ((Row) row).getItems().get(i);
+    //            Type actualItemType = actualType.getTypeParameters().get(i);
+    //            Type expectedItemType = commonSuperType.getTypeParameters().get(i);
+    //            if (!actualItemType.equals(expectedItemType)) {
+    //              analysis.addCoercion(item, expectedItemType,
+    //                  typeCoercion.isTypeOnlyCoercion(actualItemType, expectedItemType));
+    //            }
+    //          }
+    //        } else if (actualType instanceof RowType) {
+    //          // coerce row-type expression as a whole
+    //          if (!actualType.equals(commonSuperType)) {
+    //            analysis.addCoercion(row, commonSuperType,
+    // typeCoercion.isTypeOnlyCoercion(actualType, commonSuperType));
+    //          }
+    //        } else {
+    //          // coerce field. it will be wrapped in Row by Planner
+    //          Type superType = getOnlyElement(commonSuperType.getTypeParameters());
+    //          if (!actualType.equals(superType)) {
+    //            analysis.addCoercion(row, superType, typeCoercion.isTypeOnlyCoercion(actualType,
+    // superType));
+    //          }
+    //        }
+    //      }
+    //
+    //      List<Field> fields = commonSuperType.getTypeParameters().stream()
+    //          .map(valueType -> Field.newUnqualified(Optional.empty(), valueType))
+    //          .collect(toImmutableList());
+    //
+    //      return createAndAssignScope(node, scope, fields);
+    //    }
+
+    @Override
+    protected Scope visitAliasedRelation(AliasedRelation relation, Optional<Scope> scope) {
+      analysis.setRelationName(relation, QualifiedName.of(ImmutableList.of(relation.getAlias())));
+      analysis.addAliased(relation.getRelation());
+      Scope relationScope = process(relation.getRelation(), scope);
+      RelationType relationType = relationScope.getRelationType();
+
+      // todo this check should be inside of TupleDescriptor.withAlias, but the exception needs the
+      // node object
+      if (relation.getColumnNames() != null) {
+        int totalColumns = relationType.getVisibleFieldCount();
+        if (totalColumns != relation.getColumnNames().size()) {
+          throw new SemanticException(
+              String.format(
+                  "Column alias list has %s entries but '%s' has %s columns available",
+                  relation.getColumnNames().size(), relation.getAlias(), totalColumns));
+        }
+      }
+
+      List<String> aliases = null;
+      Collection<Field> inputFields = relationType.getAllFields();
+      if (relation.getColumnNames() != null) {
+        aliases =
+            relation.getColumnNames().stream()
+                .map(Identifier::getValue)
+                .collect(Collectors.toList());
+        // hidden fields are not exposed when there are column aliases
+        inputFields = relationType.getVisibleFields();
       }
 
+      RelationType descriptor = relationType.withAlias(relation.getAlias().getValue(), aliases);
+
+      checkArgument(
+          inputFields.size() == descriptor.getAllFieldCount(),
+          "Expected %s fields, got %s",
+          descriptor.getAllFieldCount(),
+          inputFields.size());
+
+      Streams.forEachPair(
+          descriptor.getAllFields().stream(),
+          inputFields.stream(),
+          (newField, field) ->
+              analysis.addSourceColumns(newField, analysis.getSourceColumns(field)));
+
+      return createAndAssignScope(relation, scope, descriptor);
+    }
+
+    @Override
+    protected Scope visitJoin(Join node, Optional<Scope> scope) {
+      JoinCriteria criteria = node.getCriteria().orElse(null);
+      if (criteria instanceof NaturalJoin) {
+        throw new SemanticException("Natural join not supported");
+      }
+
+      Scope left = process(node.getLeft(), scope);
+      Scope right = process(node.getRight(), scope);
+
       if (criteria instanceof JoinUsing) {
         return analyzeJoinUsing(node, ((JoinUsing) criteria).getColumns(), scope, left, right);
       }
@@ -853,8 +1735,7 @@ public class StatementAnalyzer {
       }
       if (criteria instanceof JoinOn) {
         Expression expression = ((JoinOn) criteria).getExpression();
-        verifyNoAggregateWindowOrGroupingFunctions(
-            session, functionResolver, accessControl, expression, "JOIN clause");
+        verifyNoAggregateWindowOrGroupingFunctions(expression, "JOIN clause");
 
         // Need to register coercions in case when join criteria requires coercion (e.g. join on
         // char(1) = char(2))
@@ -868,15 +1749,18 @@ public class StatementAnalyzer {
                     : CorrelationSupport.DISALLOWED);
         Type clauseType = expressionAnalysis.getType(expression);
         if (!clauseType.equals(BOOLEAN)) {
-          if (!clauseType.equals(UNKNOWN)) {
-            throw semanticException(
-                TYPE_MISMATCH,
-                expression,
-                "JOIN ON clause must evaluate to a boolean: actual type %s",
-                clauseType);
-          }
+          //          if (!clauseType.equals(UNKNOWN)) {
+          //            throw semanticException(
+          //                TYPE_MISMATCH,
+          //                expression,
+          //                "JOIN ON clause must evaluate to a boolean: actual type %s",
+          //                clauseType);
+          //          }
+          throw new SemanticException(
+              String.format(
+                  "JOIN ON clause must evaluate to a boolean: actual type %s", clauseType));
           // coerce expression to boolean
-          analysis.addCoercion(expression, BOOLEAN, false);
+          //          analysis.addCoercion(expression, BOOLEAN, false);
         }
 
         analysis.recordSubqueries(node, expressionAnalysis);
@@ -889,6 +1773,100 @@ public class StatementAnalyzer {
       return output;
     }
 
+    private Scope analyzeJoinUsing(
+        Join node, List<Identifier> columns, Optional<Scope> scope, Scope left, Scope right) {
+      List<Field> joinFields = new ArrayList<>();
+
+      List<Integer> leftJoinFields = new ArrayList<>();
+      List<Integer> rightJoinFields = new ArrayList<>();
+
+      Set<Identifier> seen = new HashSet<>();
+      for (Identifier column : columns) {
+        if (!seen.add(column)) {
+          throw new SemanticException(
+              String.format(
+                  "Column '%s' appears multiple times in USING clause", column.getValue()));
+        }
+
+        ResolvedField leftField =
+            left.tryResolveField(column)
+                .orElseThrow(
+                    () ->
+                        new SemanticException(
+                            String.format(
+                                "Column '%s' is missing from left side of join",
+                                column.getValue())));
+        ResolvedField rightField =
+            right
+                .tryResolveField(column)
+                .orElseThrow(
+                    () ->
+                        new SemanticException(
+                            String.format(
+                                "Column '%s' is missing from right side of join",
+                                column.getValue())));
+
+        // ensure a comparison operator exists for the given types (applying coercions if necessary)
+        //        try {
+        //          metadata.resolveOperator(OperatorType.EQUAL, ImmutableList.of(
+        //              leftField.getType(), rightField.getType()));
+        //        } catch (OperatorNotFoundException e) {
+        //          throw semanticException(TYPE_MISMATCH, column, e, "%s", e.getMessage());
+        //        }
+        if (leftField.getType() != rightField.getType()) {
+          throw new SemanticException(
+              String.format(
+                  "Column Types of left and right side are different: left is %s, right is %s",
+                  leftField.getType(), rightField.getType()));
+        }
+
+        analysis.addTypes(ImmutableMap.of(NodeRef.of(column), leftField.getType()));
+
+        joinFields.add(Field.newUnqualified(column.getValue(), leftField.getType()));
+
+        leftJoinFields.add(leftField.getRelationFieldIndex());
+        rightJoinFields.add(rightField.getRelationFieldIndex());
+
+        recordColumnAccess(leftField.getField());
+        recordColumnAccess(rightField.getField());
+      }
+
+      ImmutableList.Builder<Field> outputs = ImmutableList.builder();
+      outputs.addAll(joinFields);
+
+      ImmutableList.Builder<Integer> leftFields = ImmutableList.builder();
+      for (int i = 0; i < left.getRelationType().getAllFieldCount(); i++) {
+        if (!leftJoinFields.contains(i)) {
+          outputs.add(left.getRelationType().getFieldByIndex(i));
+          leftFields.add(i);
+        }
+      }
+
+      ImmutableList.Builder<Integer> rightFields = ImmutableList.builder();
+      for (int i = 0; i < right.getRelationType().getAllFieldCount(); i++) {
+        if (!rightJoinFields.contains(i)) {
+          outputs.add(right.getRelationType().getFieldByIndex(i));
+          rightFields.add(i);
+        }
+      }
+
+      analysis.setJoinUsing(
+          node,
+          new Analysis.JoinUsingAnalysis(
+              leftJoinFields, rightJoinFields, leftFields.build(), rightFields.build()));
+
+      return createAndAssignScope(node, scope, new RelationType(outputs.build()));
+    }
+
+    private void recordColumnAccess(Field field) {
+      if (field.getOriginTable().isPresent() && field.getOriginColumnName().isPresent()) {
+        analysis.addTableColumnReferences(
+            accessControl,
+            sessionContext.getIdentity(),
+            ImmutableMultimap.of(field.getOriginTable().get(), field.getOriginColumnName().get()));
+      }
+    }
+
     private List<Expression> analyzeOrderBy(
         Node node, List<SortItem> sortItems, Scope orderByScope) {
       ImmutableList.Builder<Expression> orderByFieldsBuilder = ImmutableList.builder();
@@ -910,9 +1888,7 @@ public class StatementAnalyzer {
 
         ExpressionAnalysis expressionAnalysis =
             ExpressionAnalyzer.analyzeExpression(
-                session,
-                plannerContext,
-                statementAnalyzerFactory,
+                sessionContext,
                 accessControl,
                 orderByScope,
                 analysis,
@@ -940,12 +1916,15 @@ public class StatementAnalyzer {
       if (node.getRowCount() instanceof LongLiteral) {
         rowCount = ((LongLiteral) node.getRowCount()).getParsedValue();
       } else {
-        checkState(
-            node.getRowCount() instanceof Parameter,
+        //        checkState(
+        //            node.getRowCount() instanceof Parameter,
+        //            "unexpected OFFSET rowCount: " +
+        // node.getRowCount().getClass().getSimpleName());
+        throw new SemanticException(
             "unexpected OFFSET rowCount: " + node.getRowCount().getClass().getSimpleName());
-        OptionalLong providedValue =
-            analyzeParameterAsRowCount((Parameter) node.getRowCount(), scope, "OFFSET");
-        rowCount = providedValue.orElse(0);
+        //        OptionalLong providedValue =
+        //            analyzeParameterAsRowCount((Parameter) node.getRowCount(), scope, "OFFSET");
+        //        rowCount = providedValue.orElse(0);
       }
       if (rowCount < 0) {
         throw new SemanticException(
@@ -1006,10 +1985,14 @@ public class StatementAnalyzer {
       } else if (node.getRowCount() instanceof LongLiteral) {
         rowCount = OptionalLong.of(((LongLiteral) node.getRowCount()).getParsedValue());
       } else {
-        checkState(
-            node.getRowCount() instanceof Parameter,
+        //        checkState(
+        //            node.getRowCount() instanceof Parameter,
+        //            "unexpected LIMIT rowCount: " +
+        // node.getRowCount().getClass().getSimpleName());
+        throw new SemanticException(
             "unexpected LIMIT rowCount: " + node.getRowCount().getClass().getSimpleName());
-        rowCount = analyzeParameterAsRowCount((Parameter) node.getRowCount(), scope, "LIMIT");
+        //        rowCount = analyzeParameterAsRowCount((Parameter) node.getRowCount(), scope,
+        // "LIMIT");
       }
       rowCount.ifPresent(
           count -> {
@@ -1025,37 +2008,72 @@ public class StatementAnalyzer {
       return false;
     }
 
-    private OptionalLong analyzeParameterAsRowCount(
-        Parameter parameter, Scope scope, String context) {
-      // validate parameter index
-      analyzeExpression(parameter, scope);
-      Expression providedValue = analysis.getParameters().get(NodeRef.of(parameter));
-      Object value;
-      try {
-        value =
-            evaluateConstantExpression(
-                providedValue,
-                BIGINT,
-                plannerContext,
-                session,
-                accessControl,
-                analysis.getParameters());
-      } catch (VerifyException e) {
-        throw new SemanticException(
-            String.format("Non constant parameter value for %s: %s", context, providedValue));
-      }
-      if (value == null) {
-        throw new SemanticException(
-            String.format("Parameter value provided for %s is NULL: %s", context, providedValue));
+    //    private OptionalLong analyzeParameterAsRowCount(
+    //        Parameter parameter, Scope scope, String context) {
+    //      // validate parameter index
+    //      analyzeExpression(parameter, scope);
+    //      Expression providedValue = analysis.getParameters().get(NodeRef.of(parameter));
+    //      Object value;
+    //      try {
+    //        value =
+    //            evaluateConstantExpression(
+    //                providedValue,
+    //                BIGINT,
+    //                plannerContext,
+    //                session,
+    //                accessControl,
+    //                analysis.getParameters());
+    //      } catch (VerifyException e) {
+    //        throw new SemanticException(
+    //            String.format("Non constant parameter value for %s: %s", context, providedValue));
+    //      }
+    //      if (value == null) {
+    //        throw new SemanticException(
+    //            String.format("Parameter value provided for %s is NULL: %s", context,
+    // providedValue));
+    //      }
+    //      return OptionalLong.of((long) value);
+    //    }
+
+    private void analyzeAggregations(
+        QuerySpecification node,
+        Scope sourceScope,
+        Optional<Scope> orderByScope,
+        Analysis.GroupingSetAnalysis groupByAnalysis,
+        List<Expression> outputExpressions,
+        List<Expression> orderByExpressions) {
+      checkState(
+          orderByExpressions.isEmpty() || orderByScope.isPresent(),
+          "non-empty orderByExpressions list without orderByScope provided");
+
+      List<FunctionCall> aggregates =
+          extractAggregateFunctions(Iterables.concat(outputExpressions, orderByExpressions));
+      analysis.setAggregates(node, aggregates);
+
+      if (analysis.isAggregation(node)) {
+        // ensure SELECT, ORDER BY and HAVING are constant with respect to group
+        // e.g, these are all valid expressions:
+        //     SELECT f(a) GROUP BY a
+        //     SELECT f(a + 1) GROUP BY a + 1
+        //     SELECT a + sum(b) GROUP BY a
+        List<Expression> distinctGroupingColumns =
+            ImmutableSet.copyOf(groupByAnalysis.getOriginalExpressions()).asList();
+
+        verifySourceAggregations(distinctGroupingColumns, sourceScope, outputExpressions, analysis);
+        if (!orderByExpressions.isEmpty()) {
+          verifyOrderByAggregations(
+              distinctGroupingColumns,
+              sourceScope,
+              orderByScope.orElseThrow(() -> new NoSuchElementException("No value present")),
+              orderByExpressions,
+              analysis);
+        }
       }
-      return OptionalLong.of((long) value);
     }
 
     private ExpressionAnalysis analyzeExpression(Expression expression, Scope scope) {
       return ExpressionAnalyzer.analyzeExpression(
-          session,
-          plannerContext,
-          statementAnalyzerFactory,
+          sessionContext,
           accessControl,
           scope,
           analysis,
@@ -1067,9 +2085,7 @@ public class StatementAnalyzer {
     private ExpressionAnalysis analyzeExpression(
         Expression expression, Scope scope, CorrelationSupport correlationSupport) {
       return ExpressionAnalyzer.analyzeExpression(
-          session,
-          plannerContext,
-          statementAnalyzerFactory,
+          sessionContext,
           accessControl,
           scope,
           analysis,
@@ -1383,4 +2399,15 @@ public class StatementAnalyzer {
 
     return false;
   }
+
+  static void verifyNoAggregateWindowOrGroupingFunctions(Expression predicate, String clause) {
+    List<FunctionCall> aggregates = extractAggregateFunctions(ImmutableList.of(predicate));
+
+    if (!aggregates.isEmpty()) {
+      throw new SemanticException(
+          String.format(
+              "%s cannot contain aggregations, window functions or grouping operations: %s",
+              clause, aggregates));
+    }
+  }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnHandle.java
similarity index 57%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnHandle.java
index 61d95d2e832..b035782cfa8 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnHandle.java
@@ -17,6 +17,24 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
 
-public class ExpressionAnalyzer {}
+/**
+ * Represents a handle to a column within a {@link TableHandle} returned from the connector to the
+ * engine. It will be used by the engine whenever given column will be accessed.
+ *
+ * <p>ColumnHandle should follow:
+ *
+ * <ul>
+ *   <li>a table cannot have two equal column handles
+ *   <li>column handle is unique within a table scope. Two different tables can have two equal
+ *       column handles
+ * </ul>
+ */
+public interface ColumnHandle {
+  @Override
+  int hashCode();
+
+  @Override
+  boolean equals(Object other);
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnMetadata.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnMetadata.java
new file mode 100644
index 00000000000..6039f6d1cf7
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnMetadata.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
+
+import org.apache.iotdb.tsfile.read.common.type.Type;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Locale.ENGLISH;
+import static java.util.Objects.requireNonNull;
+
+public class ColumnMetadata {
+  private final String name;
+  private final Type type;
+  private final boolean nullable;
+  private final String comment;
+  private final String extraInfo;
+  private final boolean hidden;
+  private final Map<String, Object> properties;
+
+  public ColumnMetadata(String name, Type type) {
+    this(name, type, true, null, null, false, emptyMap());
+  }
+
+  private ColumnMetadata(
+      String name,
+      Type type,
+      boolean nullable,
+      String comment,
+      String extraInfo,
+      boolean hidden,
+      Map<String, Object> properties) {
+    requireNonNull(type, "type is null");
+    requireNonNull(properties, "properties is null");
+
+    this.name = name.toLowerCase(ENGLISH);
+    this.type = type;
+    this.comment = comment;
+    this.extraInfo = extraInfo;
+    this.hidden = hidden;
+    this.properties =
+        properties.isEmpty() ? emptyMap() : unmodifiableMap(new LinkedHashMap<>(properties));
+    this.nullable = nullable;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  public boolean isNullable() {
+    return nullable;
+  }
+
+  public Optional<String> getComment() {
+    return Optional.ofNullable(comment);
+  }
+
+  public Optional<String> getExtraInfo() {
+    return Optional.ofNullable(extraInfo);
+  }
+
+  public boolean isHidden() {
+    return hidden;
+  }
+
+  public Map<String, Object> getProperties() {
+    return properties;
+  }
+
+  public ColumnSchema getColumnSchema() {
+    return ColumnSchema.builder(this).build();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("ColumnMetadata{");
+    sb.append("name='").append(name).append('\'');
+    sb.append(", type=").append(type);
+    sb.append(", ").append(nullable ? "nullable" : "nonnull");
+    if (comment != null) {
+      sb.append(", comment='").append(comment).append('\'');
+    }
+    if (extraInfo != null) {
+      sb.append(", extraInfo='").append(extraInfo).append('\'');
+    }
+    if (hidden) {
+      sb.append(", hidden");
+    }
+    if (!properties.isEmpty()) {
+      sb.append(", properties=").append(properties);
+    }
+    sb.append('}');
+    return sb.toString();
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(name, type, nullable, comment, extraInfo, hidden);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    ColumnMetadata other = (ColumnMetadata) obj;
+    return Objects.equals(this.name, other.name)
+        && Objects.equals(this.type, other.type)
+        && Objects.equals(this.nullable, other.nullable)
+        && Objects.equals(this.comment, other.comment)
+        && Objects.equals(this.extraInfo, other.extraInfo)
+        && Objects.equals(this.hidden, other.hidden);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builderFrom(ColumnMetadata columnMetadata) {
+    return new Builder(columnMetadata);
+  }
+
+  public static class Builder {
+    private String name;
+    private Type type;
+    private boolean nullable = true;
+    private Optional<String> comment = Optional.empty();
+    private Optional<String> extraInfo = Optional.empty();
+    private boolean hidden;
+    private Map<String, Object> properties = emptyMap();
+
+    private Builder() {}
+
+    private Builder(ColumnMetadata columnMetadata) {
+      this.name = columnMetadata.getName();
+      this.type = columnMetadata.getType();
+      this.nullable = columnMetadata.isNullable();
+      this.comment = columnMetadata.getComment();
+      this.extraInfo = columnMetadata.getExtraInfo();
+      this.hidden = columnMetadata.isHidden();
+      this.properties = columnMetadata.getProperties();
+    }
+
+    public Builder setName(String name) {
+      this.name = requireNonNull(name, "name is null");
+      return this;
+    }
+
+    public Builder setType(Type type) {
+      this.type = requireNonNull(type, "type is null");
+      return this;
+    }
+
+    public Builder setNullable(boolean nullable) {
+      this.nullable = nullable;
+      return this;
+    }
+
+    public Builder setComment(Optional<String> comment) {
+      this.comment = requireNonNull(comment, "comment is null");
+      return this;
+    }
+
+    public Builder setExtraInfo(Optional<String> extraInfo) {
+      this.extraInfo = requireNonNull(extraInfo, "extraInfo is null");
+      return this;
+    }
+
+    public Builder setHidden(boolean hidden) {
+      this.hidden = hidden;
+      return this;
+    }
+
+    public Builder setProperties(Map<String, Object> properties) {
+      this.properties = requireNonNull(properties, "properties is null");
+      return this;
+    }
+
+    public ColumnMetadata build() {
+      return new ColumnMetadata(
+          name, type, nullable, comment.orElse(null), extraInfo.orElse(null), hidden, properties);
+    }
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnSchema.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnSchema.java
new file mode 100644
index 00000000000..a94ae08651a
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ColumnSchema.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
+
+import org.apache.iotdb.tsfile.read.common.type.Type;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+import static java.util.Locale.ENGLISH;
+import static java.util.Objects.requireNonNull;
+
+public class ColumnSchema {
+  private final String name;
+  private final Type type;
+  private final boolean hidden;
+
+  private ColumnSchema(String name, Type type, boolean hidden) {
+    requireNonNull(type, "type is null");
+
+    this.name = name.toLowerCase(ENGLISH);
+    this.type = type;
+    this.hidden = hidden;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  public boolean isHidden() {
+    return hidden;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ColumnSchema that = (ColumnSchema) o;
+    return hidden == that.hidden && name.equals(that.name) && type.equals(that.type);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(name, type, hidden);
+  }
+
+  @Override
+  public String toString() {
+    return new StringJoiner(", ", ColumnSchema.class.getSimpleName() + "[", "]")
+        .add("name='" + name + "'")
+        .add("type=" + type)
+        .add("hidden=" + hidden)
+        .toString();
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(ColumnMetadata columnMetadata) {
+    return new Builder(columnMetadata);
+  }
+
+  public static class Builder {
+    private String name;
+    private Type type;
+    private boolean hidden;
+
+    private Builder() {}
+
+    private Builder(ColumnMetadata columnMetadata) {
+      this.name = columnMetadata.getName();
+      this.type = columnMetadata.getType();
+      this.hidden = columnMetadata.isHidden();
+    }
+
+    public Builder setName(String name) {
+      this.name = requireNonNull(name, "name is null");
+      return this;
+    }
+
+    public Builder setType(Type type) {
+      this.type = requireNonNull(type, "type is null");
+      return this;
+    }
+
+    public Builder setHidden(boolean hidden) {
+      this.hidden = hidden;
+      return this;
+    }
+
+    public ColumnSchema build() {
+      return new ColumnSchema(name, type, hidden);
+    }
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/Metadata.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/Metadata.java
new file mode 100644
index 00000000000..f4880b58b6e
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/Metadata.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
+
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
+
+import java.util.Map;
+import java.util.Optional;
+
+public interface Metadata {
+
+  boolean tableExists(QualifiedObjectName name);
+
+  /**
+   * Return table schema definition for the specified table handle. Table schema definition is a set
+   * of information required by semantic analyzer to analyze the query.
+   *
+   * @throws RuntimeException if table handle is no longer valid
+   * @see #getTableMetadata(SessionInfo, TableHandle)
+   */
+  TableSchema getTableSchema(SessionInfo session, TableHandle tableHandle);
+
+  /**
+   * Return the metadata for the specified table handle.
+   *
+   * @throws RuntimeException if table handle is no longer valid
+   * @see #getTableSchema(SessionInfo, TableHandle) a different method which is less expensive.
+   */
+  TableMetadata getTableMetadata(SessionInfo session, TableHandle tableHandle);
+
+  /** Returns a table handle for the specified table name with a specified version */
+  Optional<TableHandle> getTableHandle(SessionInfo session, QualifiedObjectName name);
+
+  /**
+   * Gets all of the columns on the specified table, or an empty map if the columns cannot be
+   * enumerated.
+   *
+   * @throws RuntimeException if table handle is no longer valid
+   */
+  Map<String, ColumnHandle> getColumnHandles(SessionInfo session, TableHandle tableHandle);
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/MetadataUtil.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/MetadataUtil.java
index c134054fff1..3fea978e205 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/MetadataUtil.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/MetadataUtil.java
@@ -19,8 +19,11 @@
 
 package org.apache.iotdb.db.queryengine.plan.relational.metadata;
 
+import org.apache.iotdb.db.exception.sql.SemanticException;
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
 import org.apache.iotdb.db.relational.sql.tree.Node;
 import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
+import org.apache.iotdb.tsfile.read.common.type.Type;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -64,109 +67,51 @@ public class MetadataUtil {
   }
 
   public static QualifiedObjectName createQualifiedObjectName(
-      Session session, Node node, QualifiedName name) {
+      SessionInfo session, Node node, QualifiedName name) {
     requireNonNull(session, "session is null");
     requireNonNull(name, "name is null");
     if (name.getParts().size() > 3) {
-      throw new TrinoException(SYNTAX_ERROR, format("Too many dots in table name: %s", name));
+      throw new SemanticException(String.format("Too many dots in table name: %s", name));
     }
 
     List<String> parts = Lists.reverse(name.getParts());
     String objectName = parts.get(0);
-    String schemaName =
-        (parts.size() > 1)
-            ? parts.get(1)
-            : session
-                .getSchema()
-                .orElseThrow(
-                    () ->
-                        semanticException(
-                            MISSING_SCHEMA_NAME,
-                            node,
-                            "Schema must be specified when session schema is not set"));
-    String catalogName =
+    String databaseName =
         (parts.size() > 2)
             ? parts.get(2)
             : session
-                .getCatalog()
+                .getDatabaseName()
                 .orElseThrow(
                     () ->
-                        semanticException(
-                            MISSING_CATALOG_NAME,
-                            node,
+                        new SemanticException(
                             "Catalog must be specified when session catalog is not set"));
 
-    return new QualifiedObjectName(catalogName, schemaName, objectName);
+    return new QualifiedObjectName(databaseName, objectName);
   }
 
-  public static boolean tableExists(Metadata metadata, Session session, String table) {
-    if (session.getCatalog().isEmpty() || session.getSchema().isEmpty()) {
+  public static boolean tableExists(Metadata metadata, SessionInfo session, String table) {
+    if (!session.getDatabaseName().isPresent()) {
       return false;
     }
-    QualifiedObjectName name =
-        new QualifiedObjectName(session.getCatalog().get(), session.getSchema().get(), table);
-    return metadata.getTableHandle(session, name).isPresent();
-  }
-
-  public static void checkRoleExists(
-      Session session,
-      Node node,
-      Metadata metadata,
-      TrinoPrincipal principal,
-      Optional<String> catalog) {
-    if (principal.getType() == ROLE) {
-      checkRoleExists(session, node, metadata, principal.getName(), catalog);
-    }
-  }
-
-  public static void checkRoleExists(
-      Session session, Node node, Metadata metadata, String role, Optional<String> catalog) {
-    if (!metadata.roleExists(session, role, catalog)) {
-      throw semanticException(
-          ROLE_NOT_FOUND,
-          node,
-          "Role '%s' does not exist%s",
-          role,
-          catalog.map(c -> format(" in catalog '%s'", c)).orElse(""));
-    }
-  }
-
-  public static Optional<String> processRoleCommandCatalog(
-      Metadata metadata, Session session, Node node, Optional<String> catalog) {
-    boolean legacyCatalogRoles = isLegacyCatalogRoles(session);
-    // old role commands use only supported catalog roles and used session catalog as the default
-    if (catalog.isEmpty() && legacyCatalogRoles) {
-      catalog = session.getCatalog();
-      if (catalog.isEmpty()) {
-        throw semanticException(MISSING_CATALOG_NAME, node, "Session catalog must be set");
-      }
-    }
-    catalog.ifPresent(
-        catalogName -> getRequiredCatalogHandle(metadata, session, node, catalogName));
-
-    if (catalog.isPresent() && !metadata.isCatalogManagedSecurity(session, catalog.get())) {
-      throw semanticException(
-          NOT_SUPPORTED, node, "Catalog '%s' does not support role management", catalog.get());
-    }
-
-    return catalog;
+    QualifiedObjectName name = new QualifiedObjectName(session.getDatabaseName().get(), table);
+    return metadata.tableExists(name);
   }
 
   public static class TableMetadataBuilder {
-    public static TableMetadataBuilder tableMetadataBuilder(SchemaTableName tableName) {
+    public static TableMetadataBuilder tableMetadataBuilder(String tableName) {
       return new TableMetadataBuilder(tableName);
     }
 
-    private final SchemaTableName tableName;
+    private final String tableName;
     private final ImmutableList.Builder<ColumnMetadata> columns = ImmutableList.builder();
     private final ImmutableMap.Builder<String, Object> properties = ImmutableMap.builder();
     private final Optional<String> comment;
 
-    private TableMetadataBuilder(SchemaTableName tableName) {
+    private TableMetadataBuilder(String tableName) {
       this(tableName, Optional.empty());
     }
 
-    private TableMetadataBuilder(SchemaTableName tableName, Optional<String> comment) {
+    private TableMetadataBuilder(String tableName, Optional<String> comment) {
       this.tableName = tableName;
       this.comment = comment;
     }
@@ -187,9 +132,8 @@ public class MetadataUtil {
       return this;
     }
 
-    public ConnectorTableMetadata build() {
-      return new ConnectorTableMetadata(
-          tableName, columns.build(), properties.buildOrThrow(), comment);
+    public TableMetadata build() {
+      return new TableMetadata(tableName, columns.build(), properties.buildOrThrow(), comment);
     }
   }
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableHandle.java
similarity index 89%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableHandle.java
index 61d95d2e832..0f83a73b0d2 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableHandle.java
@@ -17,6 +17,6 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
 
-public class ExpressionAnalyzer {}
+public class TableHandle {}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadata.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadata.java
new file mode 100644
index 00000000000..a3acb473d2c
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadata.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Objects.requireNonNull;
+
+public class TableMetadata {
+  private final String table;
+  private final Optional<String> comment;
+  private final List<ColumnMetadata> columns;
+  private final Map<String, Object> properties;
+
+  public TableMetadata(String table, List<ColumnMetadata> columns) {
+    this(table, columns, emptyMap());
+  }
+
+  public TableMetadata(String table, List<ColumnMetadata> columns, Map<String, Object> properties) {
+    this(table, columns, properties, Optional.empty());
+  }
+
+  public TableMetadata(
+      String table,
+      List<ColumnMetadata> columns,
+      Map<String, Object> properties,
+      Optional<String> comment) {
+    requireNonNull(table, "table is null");
+    requireNonNull(columns, "columns is null");
+    requireNonNull(comment, "comment is null");
+
+    this.table = table;
+    this.columns = new ArrayList<>(columns);
+    this.properties = Collections.unmodifiableMap(new LinkedHashMap<>(properties));
+    this.comment = comment;
+  }
+
+  public String getTable() {
+    return table;
+  }
+
+  public List<ColumnMetadata> getColumns() {
+    return columns;
+  }
+
+  public Map<String, Object> getProperties() {
+    return properties;
+  }
+
+  public Optional<String> getComment() {
+    return comment;
+  }
+
+  public TableSchema getTableSchema() {
+    return new TableSchema(
+        table, columns.stream().map(ColumnMetadata::getColumnSchema).collect(Collectors.toList()));
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("ConnectorTableMetadata{");
+    sb.append("table=").append(table);
+    sb.append(", columns=").append(columns);
+    sb.append(", properties=").append(properties);
+    comment.ifPresent(value -> sb.append(", comment='").append(value).append("'"));
+    sb.append('}');
+    return sb.toString();
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableSchema.java
similarity index 63%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableSchema.java
index 61d95d2e832..e3ed509931c 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableSchema.java
@@ -17,6 +17,26 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.metadata;
 
-public class ExpressionAnalyzer {}
+import java.util.List;
+
+public class TableSchema {
+
+  private final String tableName;
+
+  private final List<ColumnSchema> columns;
+
+  public TableSchema(String tableName, List<ColumnSchema> columns) {
+    this.tableName = tableName;
+    this.columns = columns;
+  }
+
+  public String getTableName() {
+    return tableName;
+  }
+
+  public List<ColumnSchema> getColumns() {
+    return columns;
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Symbol.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Symbol.java
new file mode 100644
index 00000000000..847197ba6b1
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/Symbol.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.queryengine.plan.relational.planner;
+
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.SymbolReference;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public class Symbol implements Comparable<Symbol> {
+  private final String name;
+
+  public static Symbol from(Expression expression) {
+    checkArgument(expression instanceof SymbolReference, "Unexpected expression: %s", expression);
+    return new Symbol(((SymbolReference) expression).getName());
+  }
+
+  public Symbol(String name) {
+    requireNonNull(name, "name is null");
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public SymbolReference toSymbolReference() {
+    return new SymbolReference(name);
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    Symbol symbol = (Symbol) o;
+
+    return name.equals(symbol.name);
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+
+  @Override
+  public int compareTo(Symbol o) {
+    return name.compareTo(o.name);
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolResolver.java
similarity index 85%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolResolver.java
index 61d95d2e832..e8df34b2752 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/SymbolResolver.java
@@ -17,6 +17,8 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.planner;
 
-public class ExpressionAnalyzer {}
+public interface SymbolResolver {
+  Object getValue(Symbol symbol);
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControl.java
similarity index 89%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControl.java
index 61d95d2e832..f61b67652d5 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControl.java
@@ -17,6 +17,6 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.security;
 
-public class ExpressionAnalyzer {}
+public interface AccessControl {}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/Identity.java
similarity index 78%
copy from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/Identity.java
index 61d95d2e832..fad9562a916 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/Identity.java
@@ -17,6 +17,16 @@
  * under the License.
  */
 
-package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
+package org.apache.iotdb.db.queryengine.plan.relational.security;
 
-public class ExpressionAnalyzer {}
+public class Identity {
+  private final String user;
+
+  public Identity(String user) {
+    this.user = user;
+  }
+
+  public String getUser() {
+    return user;
+  }
+}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BinaryType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BinaryType.java
index d5780068d23..98e988c38bc 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BinaryType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BinaryType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
 import org.apache.iotdb.tsfile.utils.Binary;
 
 public class BinaryType implements Type {
-  private static final BinaryType INSTANCE = new BinaryType();
+  private static final BinaryType TEXT = new BinaryType();
 
   private BinaryType() {}
 
@@ -49,12 +49,22 @@ public class BinaryType implements Type {
     return TypeEnum.BINARY;
   }
 
+  @Override
+  public String getDisplayName() {
+    return "TEXT";
+  }
+
+  @Override
+  public boolean isComparable() {
+    return true;
+  }
+
   @Override
   public boolean isOrderable() {
     return true;
   }
 
   public static BinaryType getInstance() {
-    return INSTANCE;
+    return TEXT;
   }
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BooleanType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BooleanType.java
index 3c278f4e6ee..fdb65f8d030 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BooleanType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/BooleanType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
 
 public class BooleanType implements Type {
 
-  private static final BooleanType INSTANCE = new BooleanType();
+  public static final BooleanType BOOLEAN = new BooleanType();
 
   private BooleanType() {}
 
@@ -49,12 +49,22 @@ public class BooleanType implements Type {
     return TypeEnum.BOOLEAN;
   }
 
+  @Override
+  public String getDisplayName() {
+    return "BOOLEAN";
+  }
+
+  @Override
+  public boolean isComparable() {
+    return true;
+  }
+
   @Override
   public boolean isOrderable() {
     return true;
   }
 
   public static BooleanType getInstance() {
-    return INSTANCE;
+    return BOOLEAN;
   }
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/DoubleType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/DoubleType.java
index 8d074679052..0d64ffef1b7 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/DoubleType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/DoubleType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.DoubleColumnBuilder;
 
 public class DoubleType implements Type {
 
-  private static final DoubleType INSTANCE = new DoubleType();
+  public static final DoubleType DOUBLE = new DoubleType();
 
   private DoubleType() {}
 
@@ -79,12 +79,22 @@ public class DoubleType implements Type {
     return TypeEnum.DOUBLE;
   }
 
+  @Override
+  public String getDisplayName() {
+    return "DOUBLE";
+  }
+
+  @Override
+  public boolean isComparable() {
+    return true;
+  }
+
   @Override
   public boolean isOrderable() {
     return true;
   }
 
   public static DoubleType getInstance() {
-    return INSTANCE;
+    return DOUBLE;
   }
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/FloatType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/FloatType.java
index f1f619feb46..96d258f533b 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/FloatType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/FloatType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.FloatColumnBuilder;
 
 public class FloatType implements Type {
 
-  private static final FloatType INSTANCE = new FloatType();
+  private static final FloatType FLOAT = new FloatType();
 
   private FloatType() {}
 
@@ -79,12 +79,22 @@ public class FloatType implements Type {
     return TypeEnum.FLOAT;
   }
 
+  @Override
+  public String getDisplayName() {
+    return "FLOAT";
+  }
+
+  @Override
+  public boolean isComparable() {
+    return true;
+  }
+
   @Override
   public boolean isOrderable() {
     return true;
   }
 
   public static FloatType getInstance() {
-    return INSTANCE;
+    return FLOAT;
   }
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/IntType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/IntType.java
index 6c3e4dcc980..e0108f925d5 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/IntType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/IntType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.IntColumnBuilder;
 
 public class IntType implements Type {
 
-  private static final IntType INSTANCE = new IntType();
+  private static final IntType INT32 = new IntType();
 
   private IntType() {}
 
@@ -79,12 +79,22 @@ public class IntType implements Type {
     return TypeEnum.INT32;
   }
 
+  @Override
+  public String getDisplayName() {
+    return "INT32";
+  }
+
+  @Override
+  public boolean isComparable() {
+    return true;
+  }
+
   @Override
   public boolean isOrderable() {
     return true;
   }
 
   public static IntType getInstance() {
-    return INSTANCE;
+    return INT32;
   }
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/LongType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/LongType.java
index 39d451b1013..383bc37619f 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/LongType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/LongType.java
@@ -25,7 +25,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.LongColumnBuilder;
 
 public class LongType implements Type {
 
-  private static final LongType INSTANCE = new LongType();
+  private static final LongType INT64 = new LongType();
 
   private LongType() {}
 
@@ -79,12 +79,22 @@ public class LongType implements Type {
     return TypeEnum.INT64;
   }
 
+  @Override
+  public String getDisplayName() {
+    return "INT64";
+  }
+
+  @Override
+  public boolean isComparable() {
+    return true;
+  }
+
   @Override
   public boolean isOrderable() {
     return true;
   }
 
   public static LongType getInstance() {
-    return INSTANCE;
+    return INT64;
   }
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/Type.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/Type.java
index a8383bb4158..1babec273aa 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/Type.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/Type.java
@@ -54,6 +54,7 @@ public interface Type {
   default Binary getBinary(Column c, int position) {
     throw new UnsupportedOperationException(getClass().getName());
   }
+
   /** Gets a Object at {@code position}. */
   default Object getObject(Column c, int position) {
     return c.getObject(position);
@@ -102,6 +103,12 @@ public interface Type {
 
   TypeEnum getTypeEnum();
 
+  /** Returns the name of this type that should be displayed to end-users. */
+  String getDisplayName();
+
+  /** True if the type supports equalTo and hash. */
+  boolean isComparable();
+
   /** True if the type supports compareTo. */
   boolean isOrderable();
 }