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/29 02:25:43 UTC

(iotdb) branch ty/TableModelGrammar updated (875f5037eda -> 4676d100dbf)

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

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


    from 875f5037eda fix compile
     new 2c12cdf6c8f StatementAnalyzer
     new 4676d100dbf finish analyze

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/iotdb/rpc/TSStatusCode.java    |    2 +
 .../iotdb/db/queryengine/common/SessionInfo.java   |    7 +-
 .../db/queryengine/plan/analyze/TypeProvider.java  |   92 +-
 .../plan/relational/analyzer/Analyzer.java         |   59 +-
 .../relational/analyzer/ExpressionAnalysis.java    |   34 +-
 .../relational/analyzer/ExpressionAnalyzer.java    | 1547 +++++++++++++++++++-
 .../relational/analyzer/StatementAnalyzer.java     |   19 +-
 .../analyzer/StatementAnalyzerFactory.java         |   60 +
 .../plan/relational/function/BoundSignature.java   |   97 ++
 .../NodeRef.java => function/FunctionId.java}      |   43 +-
 .../FunctionKind.java}                             |   10 +-
 .../function/LongVariableConstraint.java}          |   40 +-
 .../plan/relational/function/OperatorType.java     |   67 +
 .../plan/relational/function/Signature.java        |  220 +++
 .../function/TypeVariableConstraint.java           |  200 +++
 .../plan/relational/metadata/Metadata.java         |    6 +
 .../metadata/OperatorNotFoundException.java        |  119 ++
 .../plan/relational/metadata/ResolvedFunction.java |  107 ++
 .../relational/type/NamedTypeSignature.java}       |   48 +-
 .../plan/relational/type/ParameterKind.java        |   10 +-
 .../plan/relational/type/RowFieldName.java}        |   35 +-
 .../plan/relational/type/StandardTypes.java        |   55 +
 .../plan/relational/type/TypeSignature.java        |  207 +++
 .../relational/type/TypeSignatureParameter.java    |  175 +++
 .../plan/relational/analyzer/AnalyzerTest.java     |   79 +
 .../relational/sql/tree/StackableAstVisitor.java   |   65 +
 .../iotdb/tsfile/read/common/type/RowType.java     |   21 +
 .../iotdb/tsfile/read/common/type/TypeEnum.java    |    4 +-
 .../iotdb/tsfile/read/common/type/TypeFactory.java |   22 +
 .../type/{BooleanType.java => UnknownType.java}    |   31 +-
 30 files changed, 3323 insertions(+), 158 deletions(-)
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzerFactory.java
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/BoundSignature.java
 copy iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/{analyzer/NodeRef.java => function/FunctionId.java} (58%)
 copy iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/{analyzer/CorrelationSupport.java => function/FunctionKind.java} (85%)
 copy iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/{execution/warnings/WarningCode.java => plan/relational/function/LongVariableConstraint.java} (61%)
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/OperatorType.java
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/Signature.java
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/TypeVariableConstraint.java
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/OperatorNotFoundException.java
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ResolvedFunction.java
 copy iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/{execution/warnings/IoTDBWarning.java => plan/relational/type/NamedTypeSignature.java} (50%)
 copy iotdb-client/cli/src/main/java/org/apache/iotdb/cli/type/ExitType.java => iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/ParameterKind.java (86%)
 copy iotdb-core/{relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/SymbolReference.java => datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/RowFieldName.java} (68%)
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/StandardTypes.java
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignature.java
 create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignatureParameter.java
 create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
 create mode 100644 iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/StackableAstVisitor.java
 copy iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/{BooleanType.java => UnknownType.java} (63%)


(iotdb) 01/02: StatementAnalyzer

Posted by ja...@apache.org.
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 2c12cdf6c8f19aca13dffb3d14baa345c805d2c0
Author: JackieTien97 <ja...@gmail.com>
AuthorDate: Wed Feb 28 20:59:37 2024 +0800

    StatementAnalyzer
---
 .../java/org/apache/iotdb/rpc/TSStatusCode.java    |    2 +
 .../db/queryengine/plan/analyze/TypeProvider.java  |   92 +-
 .../relational/analyzer/ExpressionAnalysis.java    |   34 +-
 .../relational/analyzer/ExpressionAnalyzer.java    | 1531 +++++++++++++++++++-
 .../relational/analyzer/StatementAnalyzer.java     |    3 +
 .../plan/relational/function/BoundSignature.java   |   97 ++
 .../plan/relational/function/FunctionId.java       |   68 +
 .../plan/relational/function/FunctionKind.java}    |   21 +-
 .../function/LongVariableConstraint.java           |   68 +
 .../plan/relational/function/OperatorType.java     |   67 +
 .../plan/relational/function/Signature.java        |  220 +++
 .../function/TypeVariableConstraint.java           |  200 +++
 .../plan/relational/metadata/Metadata.java         |    6 +
 .../metadata/OperatorNotFoundException.java        |  119 ++
 .../plan/relational/metadata/ResolvedFunction.java |  107 ++
 .../plan/relational/type/NamedTypeSignature.java   |   79 +
 .../plan/relational/type/ParameterKind.java}       |   21 +-
 .../plan/relational/type/RowFieldName.java         |   63 +
 .../plan/relational/type/StandardTypes.java        |   55 +
 .../plan/relational/type/TypeSignature.java        |  207 +++
 .../relational/type/TypeSignatureParameter.java    |  175 +++
 .../relational/sql/tree/StackableAstVisitor.java   |   65 +
 .../iotdb/tsfile/read/common/type/RowType.java     |   21 +
 .../iotdb/tsfile/read/common/type/TypeEnum.java    |    4 +-
 .../iotdb/tsfile/read/common/type/TypeFactory.java |   22 +
 .../iotdb/tsfile/read/common/type/UnknownType.java |   87 ++
 26 files changed, 3356 insertions(+), 78 deletions(-)

diff --git a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
index b8407bb86de..1056e470a09 100644
--- a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
+++ b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
@@ -113,6 +113,8 @@ public enum TSStatusCode {
   NO_SUCH_QUERY(714),
   QUERY_WAS_KILLED(715),
 
+  OPERATOR_NOT_FOUND(716),
+
   // Authentication
   INIT_AUTH_ERROR(800),
   WRONG_LOGIN_PASSWORD(801),
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TypeProvider.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TypeProvider.java
index 0c3bcee6466..d12f71f5303 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TypeProvider.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TypeProvider.java
@@ -19,16 +19,25 @@
 
 package org.apache.iotdb.db.queryengine.plan.analyze;
 
+import org.apache.iotdb.db.queryengine.plan.relational.planner.Symbol;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.common.type.Type;
+import org.apache.iotdb.tsfile.read.common.type.TypeEnum;
+import org.apache.iotdb.tsfile.read.common.type.TypeFactory;
 import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
 
+import com.google.common.collect.ImmutableMap;
+
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
 import static org.apache.iotdb.db.queryengine.plan.expression.leaf.TimestampOperand.TIMESTAMP_EXPRESSION_STRING;
 
 public class TypeProvider {
@@ -39,6 +48,7 @@ public class TypeProvider {
 
   public TypeProvider() {
     this.typeMap = new HashMap<>();
+    types = null;
   }
 
   public TypeProvider(Map<String, TSDataType> typeMap, TemplatedInfo templatedInfo) {
@@ -46,6 +56,7 @@ public class TypeProvider {
     this.templatedInfo = templatedInfo;
     // The type of TimeStampOperand is INT64
     this.typeMap.putIfAbsent(TIMESTAMP_EXPRESSION_STRING, TSDataType.INT64);
+    types = null;
   }
 
   public TSDataType getType(String symbol) {
@@ -71,6 +82,50 @@ public class TypeProvider {
     return this.templatedInfo;
   }
 
+  // ----------------------used for relational model----------------------------
+
+  private final Map<Symbol, Type> types;
+
+  public static TypeProvider viewOf(Map<Symbol, Type> types) {
+    return new TypeProvider(types);
+  }
+
+  public static TypeProvider copyOf(Map<Symbol, Type> types) {
+    return new TypeProvider(ImmutableMap.copyOf(types));
+  }
+
+  public static TypeProvider empty() {
+    return new TypeProvider(ImmutableMap.of());
+  }
+
+  private TypeProvider(Map<Symbol, Type> types) {
+    this.types = types;
+
+    this.typeMap = null;
+  }
+
+  private TypeProvider(
+      Map<String, TSDataType> typeMap, TemplatedInfo templatedInfo, Map<Symbol, Type> types) {
+    this.typeMap = typeMap;
+    this.templatedInfo = templatedInfo;
+    this.types = types;
+  }
+
+  public Type get(Symbol symbol) {
+    requireNonNull(symbol, "symbol is null");
+
+    Type type = types.get(symbol);
+    checkArgument(type != null, "no type found for symbol '%s'", symbol);
+
+    return type;
+  }
+
+  public Map<Symbol, Type> allTypes() {
+    // types may be a HashMap, so creating an ImmutableMap here would add extra cost when allTypes
+    // gets called frequently
+    return Collections.unmodifiableMap(types);
+  }
+
   public void serialize(ByteBuffer byteBuffer) {
     ReadWriteIOUtils.write(typeMap.size(), byteBuffer);
     for (Map.Entry<String, TSDataType> entry : typeMap.entrySet()) {
@@ -84,6 +139,16 @@ public class TypeProvider {
       ReadWriteIOUtils.write((byte) 1, byteBuffer);
       templatedInfo.serialize(byteBuffer);
     }
+
+    if (types == null) {
+      ReadWriteIOUtils.write((byte) 0, byteBuffer);
+    } else {
+      ReadWriteIOUtils.write(types.size(), byteBuffer);
+      for (Map.Entry<Symbol, Type> entry : types.entrySet()) {
+        ReadWriteIOUtils.write(entry.getKey().getName(), byteBuffer);
+        ReadWriteIOUtils.write(entry.getValue().getTypeEnum().ordinal(), byteBuffer);
+      }
+    }
   }
 
   public void serialize(DataOutputStream stream) throws IOException {
@@ -99,11 +164,21 @@ public class TypeProvider {
       ReadWriteIOUtils.write((byte) 1, stream);
       templatedInfo.serialize(stream);
     }
+
+    if (types == null) {
+      ReadWriteIOUtils.write((byte) 0, stream);
+    } else {
+      ReadWriteIOUtils.write(types.size(), stream);
+      for (Map.Entry<Symbol, Type> entry : types.entrySet()) {
+        ReadWriteIOUtils.write(entry.getKey().getName(), stream);
+        ReadWriteIOUtils.write(entry.getValue().getTypeEnum().ordinal(), stream);
+      }
+    }
   }
 
   public static TypeProvider deserialize(ByteBuffer byteBuffer) {
     int mapSize = ReadWriteIOUtils.readInt(byteBuffer);
-    Map<String, TSDataType> typeMap = new HashMap<>();
+    Map<String, TSDataType> typeMap = new HashMap<>(mapSize);
     while (mapSize > 0) {
       typeMap.put(
           ReadWriteIOUtils.readString(byteBuffer),
@@ -117,7 +192,20 @@ public class TypeProvider {
       templatedInfo = TemplatedInfo.deserialize(byteBuffer);
     }
 
-    return new TypeProvider(typeMap, templatedInfo);
+    Map<Symbol, Type> types = null;
+    byte hasTypes = ReadWriteIOUtils.readByte(byteBuffer);
+    if (hasTypes == 1) {
+      mapSize = ReadWriteIOUtils.readInt(byteBuffer);
+      types = new HashMap<>(mapSize);
+      while (mapSize > 0) {
+        types.put(
+            new Symbol(ReadWriteIOUtils.readString(byteBuffer)),
+            TypeFactory.getType(TypeEnum.values()[ReadWriteIOUtils.readInt(byteBuffer)]));
+        mapSize--;
+      }
+    }
+
+    return new TypeProvider(typeMap, templatedInfo, types);
   }
 
   @Override
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalysis.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalysis.java
index 51315c5c889..a576308bce5 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalysis.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalysis.java
@@ -21,7 +21,6 @@ package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
 
 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.FunctionCall;
 import org.apache.iotdb.db.relational.sql.tree.InPredicate;
 import org.apache.iotdb.db.relational.sql.tree.QuantifiedComparisonExpression;
 import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression;
@@ -37,31 +36,28 @@ import static java.util.Objects.requireNonNull;
 
 public class ExpressionAnalysis {
   private final Map<NodeRef<Expression>, Type> expressionTypes;
-  private final Map<NodeRef<Expression>, Type> expressionCoercions;
-  private final Set<NodeRef<Expression>> typeOnlyCoercions;
+  //  private final Map<NodeRef<Expression>, Type> expressionCoercions;
+  //  private final Set<NodeRef<Expression>> typeOnlyCoercions;
   private final Map<NodeRef<Expression>, ResolvedField> columnReferences;
   private final Set<NodeRef<InPredicate>> subqueryInPredicates;
   private final Set<NodeRef<SubqueryExpression>> subqueries;
   private final Set<NodeRef<ExistsPredicate>> existsSubqueries;
   private final Set<NodeRef<QuantifiedComparisonExpression>> quantifiedComparisons;
-  private final Set<NodeRef<FunctionCall>> windowFunctions;
 
   public ExpressionAnalysis(
       Map<NodeRef<Expression>, Type> expressionTypes,
-      Map<NodeRef<Expression>, Type> expressionCoercions,
       Set<NodeRef<InPredicate>> subqueryInPredicates,
       Set<NodeRef<SubqueryExpression>> subqueries,
       Set<NodeRef<ExistsPredicate>> existsSubqueries,
       Map<NodeRef<Expression>, ResolvedField> columnReferences,
-      Set<NodeRef<Expression>> typeOnlyCoercions,
-      Set<NodeRef<QuantifiedComparisonExpression>> quantifiedComparisons,
-      Set<NodeRef<FunctionCall>> windowFunctions) {
+      Set<NodeRef<QuantifiedComparisonExpression>> quantifiedComparisons) {
     this.expressionTypes =
         ImmutableMap.copyOf(requireNonNull(expressionTypes, "expressionTypes is null"));
-    this.expressionCoercions =
-        ImmutableMap.copyOf(requireNonNull(expressionCoercions, "expressionCoercions is null"));
-    this.typeOnlyCoercions =
-        ImmutableSet.copyOf(requireNonNull(typeOnlyCoercions, "typeOnlyCoercions is null"));
+    //    this.expressionCoercions =
+    //        ImmutableMap.copyOf(requireNonNull(expressionCoercions, "expressionCoercions is
+    // null"));
+    //    this.typeOnlyCoercions =
+    //        ImmutableSet.copyOf(requireNonNull(typeOnlyCoercions, "typeOnlyCoercions is null"));
     this.columnReferences =
         ImmutableMap.copyOf(requireNonNull(columnReferences, "columnReferences is null"));
     this.subqueryInPredicates =
@@ -71,8 +67,6 @@ public class ExpressionAnalysis {
         ImmutableSet.copyOf(requireNonNull(existsSubqueries, "existsSubqueries is null"));
     this.quantifiedComparisons =
         ImmutableSet.copyOf(requireNonNull(quantifiedComparisons, "quantifiedComparisons is null"));
-    this.windowFunctions =
-        ImmutableSet.copyOf(requireNonNull(windowFunctions, "windowFunctions is null"));
   }
 
   public Type getType(Expression expression) {
@@ -83,14 +77,6 @@ public class ExpressionAnalysis {
     return expressionTypes;
   }
 
-  public Type getCoercion(Expression expression) {
-    return expressionCoercions.get(NodeRef.of(expression));
-  }
-
-  public boolean isTypeOnlyCoercion(Expression expression) {
-    return typeOnlyCoercions.contains(NodeRef.of(expression));
-  }
-
   public boolean isColumnReference(Expression node) {
     return columnReferences.containsKey(NodeRef.of(node));
   }
@@ -110,8 +96,4 @@ public class ExpressionAnalysis {
   public Set<NodeRef<QuantifiedComparisonExpression>> getQuantifiedComparisons() {
     return quantifiedComparisons;
   }
-
-  public Set<NodeRef<FunctionCall>> getWindowFunctions() {
-    return windowFunctions;
-  }
 }
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 bd906b816f1..9454fa1d1ed 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,19 +19,1406 @@
 
 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.WarningCollector;
+import org.apache.iotdb.db.queryengine.plan.analyze.TypeProvider;
+import org.apache.iotdb.db.queryengine.plan.relational.function.BoundSignature;
+import org.apache.iotdb.db.queryengine.plan.relational.function.OperatorType;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.OperatorNotFoundException;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.QualifiedObjectName;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.Symbol;
 import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+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.BetweenPredicate;
+import org.apache.iotdb.db.relational.sql.tree.BinaryLiteral;
+import org.apache.iotdb.db.relational.sql.tree.BooleanLiteral;
+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.CurrentDatabase;
+import org.apache.iotdb.db.relational.sql.tree.CurrentTime;
+import org.apache.iotdb.db.relational.sql.tree.CurrentUser;
+import org.apache.iotdb.db.relational.sql.tree.DecimalLiteral;
+import org.apache.iotdb.db.relational.sql.tree.DereferenceExpression;
+import org.apache.iotdb.db.relational.sql.tree.DoubleLiteral;
+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.GenericLiteral;
 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.LogicalExpression;
+import org.apache.iotdb.db.relational.sql.tree.LongLiteral;
+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.NullLiteral;
+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.Row;
+import org.apache.iotdb.db.relational.sql.tree.SearchedCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.StackableAstVisitor;
+import org.apache.iotdb.db.relational.sql.tree.StringLiteral;
+import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression;
+import org.apache.iotdb.db.relational.sql.tree.SymbolReference;
+import org.apache.iotdb.db.relational.sql.tree.Trim;
+import org.apache.iotdb.db.relational.sql.tree.WhenClause;
+import org.apache.iotdb.tsfile.read.common.type.BinaryType;
+import org.apache.iotdb.tsfile.read.common.type.RowType;
+import org.apache.iotdb.tsfile.read.common.type.Type;
+import org.apache.iotdb.tsfile.read.common.type.UnknownType;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+
+import javax.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Collections.unmodifiableSet;
 import static java.util.Objects.requireNonNull;
+import static org.apache.iotdb.db.relational.sql.tree.DereferenceExpression.isQualifiedAllFieldsReference;
+import static org.apache.iotdb.tsfile.read.common.type.BinaryType.TEXT;
+import static org.apache.iotdb.tsfile.read.common.type.BooleanType.BOOLEAN;
+import static org.apache.iotdb.tsfile.read.common.type.DoubleType.DOUBLE;
+import static org.apache.iotdb.tsfile.read.common.type.FloatType.FLOAT;
+import static org.apache.iotdb.tsfile.read.common.type.IntType.INT32;
+import static org.apache.iotdb.tsfile.read.common.type.LongType.INT64;
+import static org.apache.iotdb.tsfile.read.common.type.UnknownType.UNKNOWN;
 
 public class ExpressionAnalyzer {
 
+  private final Metadata metadata;
+  private final AccessControl accessControl;
+
+  private final BiFunction<Node, CorrelationSupport, StatementAnalyzer> statementAnalyzerFactory;
+
+  private final TypeProvider symbolTypes;
+
+  private final Set<NodeRef<SubqueryExpression>> subqueries = new LinkedHashSet<>();
+  private final Set<NodeRef<ExistsPredicate>> existsSubqueries = new LinkedHashSet<>();
+
+  private final Set<NodeRef<InPredicate>> subqueryInPredicates = new LinkedHashSet<>();
+
+  private final Map<NodeRef<Expression>, ResolvedField> columnReferences = new LinkedHashMap<>();
+  private final Map<NodeRef<Expression>, Type> expressionTypes = new LinkedHashMap<>();
+  private final Set<NodeRef<QuantifiedComparisonExpression>> quantifiedComparisons =
+      new LinkedHashSet<>();
+
+  private final Multimap<QualifiedObjectName, String> tableColumnReferences = HashMultimap.create();
+
+  // Track referenced fields from source relation node
+  private final Multimap<NodeRef<Node>, Field> referencedFields = HashMultimap.create();
+
+  private final SessionInfo session;
+
+  private final Map<NodeRef<Parameter>, Expression> parameters;
+  private final WarningCollector warningCollector;
+
+  private final Function<Expression, Type> getPreanalyzedType;
+
+  private final List<Field> sourceFields = new ArrayList<>();
+
+  private ExpressionAnalyzer(
+      Metadata metadata,
+      AccessControl accessControl,
+      Analysis analysis,
+      SessionInfo session,
+      TypeProvider types,
+      WarningCollector warningCollector) {
+    this(
+        metadata,
+        accessControl,
+        (node, correlationSupport) ->
+            new StatementAnalyzer(
+                analysis, accessControl, warningCollector, session, metadata, correlationSupport),
+        session,
+        types,
+        analysis.getParameters(),
+        warningCollector,
+        analysis::getType);
+  }
+
+  ExpressionAnalyzer(
+      Metadata metadata,
+      AccessControl accessControl,
+      BiFunction<Node, CorrelationSupport, StatementAnalyzer> statementAnalyzerFactory,
+      SessionInfo session,
+      TypeProvider symbolTypes,
+      Map<NodeRef<Parameter>, Expression> parameters,
+      WarningCollector warningCollector,
+      Function<Expression, Type> getPreanalyzedType) {
+    this.metadata = requireNonNull(metadata, "metadata is null");
+    this.accessControl = requireNonNull(accessControl, "accessControl is null");
+    this.statementAnalyzerFactory =
+        requireNonNull(statementAnalyzerFactory, "statementAnalyzerFactory is null");
+    this.session = requireNonNull(session, "session is null");
+    this.symbolTypes = requireNonNull(symbolTypes, "symbolTypes is null");
+    this.parameters = requireNonNull(parameters, "parameters is null");
+    this.warningCollector = requireNonNull(warningCollector, "warningCollector is null");
+    this.getPreanalyzedType = requireNonNull(getPreanalyzedType, "getPreanalyzedType is null");
+  }
+
+  public Map<NodeRef<Expression>, Type> getExpressionTypes() {
+    return unmodifiableMap(expressionTypes);
+  }
+
+  public Type setExpressionType(Expression expression, Type type) {
+    requireNonNull(expression, "expression cannot be null");
+    requireNonNull(type, "type cannot be null");
+
+    expressionTypes.put(NodeRef.of(expression), type);
+
+    return type;
+  }
+
+  private Type getExpressionType(Expression expression) {
+    requireNonNull(expression, "expression cannot be null");
+
+    Type type = expressionTypes.get(NodeRef.of(expression));
+    checkState(type != null, "Expression not yet analyzed: %s", expression);
+    return type;
+  }
+
+  public Set<NodeRef<InPredicate>> getSubqueryInPredicates() {
+    return unmodifiableSet(subqueryInPredicates);
+  }
+
+  public Map<NodeRef<Expression>, ResolvedField> getColumnReferences() {
+    return unmodifiableMap(columnReferences);
+  }
+
+  public Type analyze(Expression expression, Scope scope) {
+    Visitor visitor = new Visitor(scope, warningCollector);
+    return visitor.process(
+        expression,
+        new StackableAstVisitor.StackableAstVisitorContext<>(
+            Context.notInLambda(scope, CorrelationSupport.ALLOWED)));
+  }
+
+  public Type analyze(Expression expression, Scope scope, CorrelationSupport correlationSupport) {
+    Visitor visitor = new Visitor(scope, warningCollector);
+    return visitor.process(
+        expression,
+        new StackableAstVisitor.StackableAstVisitorContext<>(
+            Context.notInLambda(scope, correlationSupport)));
+  }
+
+  private Type analyze(Expression expression, Scope scope, Set<String> labels) {
+    Visitor visitor = new Visitor(scope, warningCollector);
+    return visitor.process(
+        expression,
+        new StackableAstVisitor.StackableAstVisitorContext<>(
+            Context.patternRecognition(scope, labels)));
+  }
+
+  private Type analyze(Expression expression, Scope baseScope, Context context) {
+    Visitor visitor = new Visitor(baseScope, warningCollector);
+    return visitor.process(
+        expression, new StackableAstVisitor.StackableAstVisitorContext<>(context));
+  }
+
+  public Set<NodeRef<SubqueryExpression>> getSubqueries() {
+    return unmodifiableSet(subqueries);
+  }
+
+  public Set<NodeRef<ExistsPredicate>> getExistsSubqueries() {
+    return unmodifiableSet(existsSubqueries);
+  }
+
+  public Set<NodeRef<QuantifiedComparisonExpression>> getQuantifiedComparisons() {
+    return unmodifiableSet(quantifiedComparisons);
+  }
+
+  public Multimap<QualifiedObjectName, String> getTableColumnReferences() {
+    return tableColumnReferences;
+  }
+
+  public List<Field> getSourceFields() {
+    return sourceFields;
+  }
+
+  private class Visitor extends StackableAstVisitor<Type, Context> {
+    // Used to resolve FieldReferences (e.g. during local execution planning)
+    private final Scope baseScope;
+    private final WarningCollector warningCollector;
+
+    public Visitor(Scope baseScope, WarningCollector warningCollector) {
+      this.baseScope = requireNonNull(baseScope, "baseScope is null");
+      this.warningCollector = requireNonNull(warningCollector, "warningCollector is null");
+    }
+
+    @Override
+    public Type process(Node node, @Nullable StackableAstVisitorContext<Context> context) {
+      if (node instanceof Expression) {
+        // don't double process a node
+        Type type = expressionTypes.get(NodeRef.of(((Expression) node)));
+        if (type != null) {
+          return type;
+        }
+      }
+      return super.process(node, context);
+    }
+
+    @Override
+    protected Type visitRow(Row node, StackableAstVisitorContext<Context> context) {
+      List<Type> types =
+          node.getItems().stream().map(child -> process(child, context)).collect(toImmutableList());
+
+      Type type = RowType.anonymous(types);
+      return setExpressionType(node, type);
+    }
+
+    @Override
+    protected Type visitCurrentTime(CurrentTime node, StackableAstVisitorContext<Context> context) {
+      if (requireNonNull(node.getFunction()) == CurrentTime.Function.TIMESTAMP) {
+        return setExpressionType(node, INT64);
+      }
+      throw new UnsupportedOperationException(node.toString());
+    }
+
+    @Override
+    protected Type visitSymbolReference(
+        SymbolReference node, StackableAstVisitorContext<Context> context) {
+      //      if (context.getContext().isInLambda()) {
+      //        Optional<ResolvedField> resolvedField =
+      //            context.getContext().getScope().tryResolveField(node,
+      // QualifiedName.of(node.getName()));
+      //        if (resolvedField.isPresent() &&
+      //
+      // context.getContext().getFieldToLambdaArgumentDeclaration().containsKey(FieldId.from(resolvedField.get()))) {
+      //          return setExpressionType(node, resolvedField.get().getType());
+      //        }
+      //      }
+      Type type = symbolTypes.get(Symbol.from(node));
+      return setExpressionType(node, type);
+    }
+
+    @Override
+    protected Type visitIdentifier(Identifier node, StackableAstVisitorContext<Context> context) {
+      ResolvedField resolvedField =
+          context.getContext().getScope().resolveField(node, QualifiedName.of(node.getValue()));
+      return handleResolvedField(node, resolvedField, context);
+    }
+
+    private Type handleResolvedField(
+        Expression node, ResolvedField resolvedField, StackableAstVisitorContext<Context> context) {
+      if (!resolvedField.isLocal()
+          && context.getContext().getCorrelationSupport() != CorrelationSupport.ALLOWED) {
+        throw new SemanticException(
+            String.format(
+                "Reference to column '%s' from outer scope not allowed in this context", node));
+      }
+
+      FieldId fieldId = FieldId.from(resolvedField);
+      Field field = resolvedField.getField();
+      //      if (context.getContext().isInLambda()) {
+      //        LambdaArgumentDeclaration lambdaArgumentDeclaration =
+      //            context.getContext().getFieldToLambdaArgumentDeclaration().get(fieldId);
+      //        if (lambdaArgumentDeclaration != null) {
+      //          // Lambda argument reference is not a column reference
+      //          lambdaArgumentReferences.put(NodeRef.of((Identifier) node),
+      // lambdaArgumentDeclaration);
+      //          return setExpressionType(node, field.getType());
+      //        }
+      //      }
+
+      if (field.getOriginTable().isPresent() && field.getOriginColumnName().isPresent()) {
+        tableColumnReferences.put(field.getOriginTable().get(), field.getOriginColumnName().get());
+      }
+
+      sourceFields.add(field);
+
+      fieldId
+          .getRelationId()
+          .getSourceNode()
+          .ifPresent(source -> referencedFields.put(NodeRef.of(source), field));
+
+      ResolvedField previous = columnReferences.put(NodeRef.of(node), resolvedField);
+      checkState(previous == null, "%s already known to refer to %s", node, previous);
+
+      return setExpressionType(node, field.getType());
+    }
+
+    @Override
+    protected Type visitDereferenceExpression(
+        DereferenceExpression node, StackableAstVisitorContext<Context> context) {
+      if (isQualifiedAllFieldsReference(node)) {
+        throw new SemanticException("<identifier>.* not allowed in this context");
+      }
+
+      QualifiedName qualifiedName = DereferenceExpression.getQualifiedName(node);
+
+      // If this Dereference looks like column reference, try match it to column first.
+      if (qualifiedName != null) {
+        Scope scope = context.getContext().getScope();
+        Optional<ResolvedField> resolvedField = scope.tryResolveField(node, qualifiedName);
+        if (resolvedField.isPresent()) {
+          return handleResolvedField(node, resolvedField.get(), context);
+        }
+        if (!scope.isColumnReference(qualifiedName)) {
+          throw new SemanticException(
+              String.format("Column '%s' cannot be resolved", qualifiedName));
+        }
+      }
+
+      Type baseType = process(node.getBase(), context);
+      if (!(baseType instanceof RowType)) {
+        throw new SemanticException(
+            String.format("Expression %s is not of type ROW", node.getBase()));
+      }
+
+      RowType rowType = (RowType) baseType;
+
+      Identifier field =
+          node.getField().orElseThrow(() -> new NoSuchElementException("No value present"));
+      String fieldName = field.getValue();
+
+      boolean foundFieldName = false;
+      Type rowFieldType = null;
+      for (RowType.Field rowField : rowType.getFields()) {
+        if (fieldName.equalsIgnoreCase(rowField.getName().orElse(null))) {
+          if (foundFieldName) {
+            throw new SemanticException(
+                String.format("Ambiguous row field reference: %s", fieldName));
+          }
+          foundFieldName = true;
+          rowFieldType = rowField.getType();
+        }
+      }
+
+      if (rowFieldType == null) {
+        throw new SemanticException(String.format("Column '%s' cannot be resolved", qualifiedName));
+      }
+
+      return setExpressionType(node, rowFieldType);
+    }
+
+    @Override
+    protected Type visitNotExpression(
+        NotExpression node, StackableAstVisitorContext<Context> context) {
+      coerceType(context, node.getValue(), BOOLEAN, "Value of logical NOT expression");
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    protected Type visitLogicalExpression(
+        LogicalExpression node, StackableAstVisitorContext<Context> context) {
+      for (Expression term : node.getTerms()) {
+        coerceType(context, term, BOOLEAN, "Logical expression term");
+      }
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    protected Type visitComparisonExpression(
+        ComparisonExpression node, StackableAstVisitorContext<Context> context) {
+      OperatorType operatorType = null;
+      switch (node.getOperator()) {
+        case EQUAL:
+        case NOT_EQUAL:
+          operatorType = OperatorType.EQUAL;
+          break;
+        case LESS_THAN:
+        case GREATER_THAN:
+          operatorType = OperatorType.LESS_THAN;
+          break;
+        case LESS_THAN_OR_EQUAL:
+        case GREATER_THAN_OR_EQUAL:
+          operatorType = OperatorType.LESS_THAN_OR_EQUAL;
+          break;
+        case IS_DISTINCT_FROM:
+          operatorType = OperatorType.IS_DISTINCT_FROM;
+          break;
+      }
+
+      return getOperator(context, node, operatorType, node.getLeft(), node.getRight());
+    }
+
+    @Override
+    protected Type visitIsNullPredicate(
+        IsNullPredicate node, StackableAstVisitorContext<Context> context) {
+      process(node.getValue(), context);
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    protected Type visitIsNotNullPredicate(
+        IsNotNullPredicate node, StackableAstVisitorContext<Context> context) {
+      process(node.getValue(), context);
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    protected Type visitNullIfExpression(
+        NullIfExpression node, StackableAstVisitorContext<Context> context) {
+      Type firstType = process(node.getFirst(), context);
+      Type secondType = process(node.getSecond(), context);
+
+      if (!firstType.equals(secondType)) {
+        throw new SemanticException(
+            String.format("Types are not comparable with NULLIF: %s vs %s", firstType, secondType));
+      }
+
+      return setExpressionType(node, firstType);
+    }
+
+    @Override
+    protected Type visitIfExpression(
+        IfExpression node, StackableAstVisitorContext<Context> context) {
+      coerceType(context, node.getCondition(), BOOLEAN, "IF condition");
+
+      Type type;
+      if (node.getFalseValue().isPresent()) {
+        type =
+            coerceToSingleType(
+                context,
+                node,
+                "Result types for IF must be the same",
+                node.getTrueValue(),
+                node.getFalseValue().get());
+      } else {
+        type = process(node.getTrueValue(), context);
+      }
+
+      return setExpressionType(node, type);
+    }
+
+    @Override
+    protected Type visitSearchedCaseExpression(
+        SearchedCaseExpression node, StackableAstVisitorContext<Context> context) {
+      for (WhenClause whenClause : node.getWhenClauses()) {
+        coerceType(context, whenClause.getOperand(), BOOLEAN, "CASE WHEN clause");
+      }
+
+      Type type =
+          coerceToSingleType(
+              context,
+              "All CASE results",
+              getCaseResultExpressions(node.getWhenClauses(), node.getDefaultValue()));
+      setExpressionType(node, type);
+
+      for (WhenClause whenClause : node.getWhenClauses()) {
+        Type whenClauseType = process(whenClause.getResult(), context);
+        setExpressionType(whenClause, whenClauseType);
+      }
+
+      return type;
+    }
+
+    @Override
+    protected Type visitSimpleCaseExpression(
+        SimpleCaseExpression node, StackableAstVisitorContext<Context> context) {
+      coerceCaseOperandToToSingleType(node, context);
+
+      Type type =
+          coerceToSingleType(
+              context,
+              "All CASE results",
+              getCaseResultExpressions(node.getWhenClauses(), node.getDefaultValue()));
+      setExpressionType(node, type);
+
+      for (WhenClause whenClause : node.getWhenClauses()) {
+        Type whenClauseType = process(whenClause.getResult(), context);
+        setExpressionType(whenClause, whenClauseType);
+      }
+
+      return type;
+    }
+
+    private void coerceCaseOperandToToSingleType(
+        SimpleCaseExpression node, StackableAstVisitorContext<Context> context) {
+      Type operandType = process(node.getOperand(), context);
+
+      List<WhenClause> whenClauses = node.getWhenClauses();
+      List<Type> whenOperandTypes = new ArrayList<>(whenClauses.size());
+
+      for (WhenClause whenClause : whenClauses) {
+        Expression whenOperand = whenClause.getOperand();
+        Type whenOperandType = process(whenOperand, context);
+        whenOperandTypes.add(whenOperandType);
+
+        if (!operandType.equals(whenOperandType)) {
+          throw new SemanticException(
+              String.format(
+                  "CASE operand type does not match WHEN clause operand type: %s vs %s",
+                  operandType, whenOperandType));
+        }
+      }
+
+      for (int i = 0; i < whenOperandTypes.size(); i++) {
+        Type whenOperandType = whenOperandTypes.get(i);
+        if (!whenOperandType.equals(operandType)) {
+          //          Expression whenOperand = whenClauses.get(i).getOperand();
+          throw new SemanticException(
+              String.format(
+                  "CASE operand type does not match WHEN clause operand type: %s vs %s",
+                  operandType, whenOperandType));
+          //          addOrReplaceExpressionCoercion(whenOperand, whenOperandType, operandType);
+        }
+      }
+    }
+
+    private List<Expression> getCaseResultExpressions(
+        List<WhenClause> whenClauses, Optional<Expression> defaultValue) {
+      List<Expression> resultExpressions = new ArrayList<>();
+      for (WhenClause whenClause : whenClauses) {
+        resultExpressions.add(whenClause.getResult());
+      }
+      defaultValue.ifPresent(resultExpressions::add);
+      return resultExpressions;
+    }
+
+    @Override
+    protected Type visitCoalesceExpression(
+        CoalesceExpression node, StackableAstVisitorContext<Context> context) {
+      Type type = coerceToSingleType(context, "All COALESCE operands", node.getOperands());
+
+      return setExpressionType(node, type);
+    }
+
+    @Override
+    protected Type visitArithmeticUnary(
+        ArithmeticUnaryExpression node, StackableAstVisitorContext<Context> context) {
+      switch (node.getSign()) {
+        case PLUS:
+          Type type = process(node.getValue(), context);
+
+          if (!type.equals(DOUBLE)
+              && !type.equals(FLOAT)
+              && !type.equals(INT32)
+              && !type.equals(INT64)) {
+            // TODO: figure out a type-agnostic way of dealing with this. Maybe add a special unary
+            // operator
+            // that types can chose to implement, or piggyback on the existence of the negation
+            // operator
+            throw new SemanticException(
+                String.format("Unary '+' operator cannot by applied to %s type", type));
+          }
+          return setExpressionType(node, type);
+        case MINUS:
+          return getOperator(context, node, OperatorType.NEGATION, node.getValue());
+        default:
+          throw new IllegalArgumentException("Unknown sign: " + node.getSign());
+      }
+    }
+
+    @Override
+    protected Type visitArithmeticBinary(
+        ArithmeticBinaryExpression node, StackableAstVisitorContext<Context> context) {
+      return getOperator(
+          context,
+          node,
+          OperatorType.valueOf(node.getOperator().name()),
+          node.getLeft(),
+          node.getRight());
+    }
+
+    @Override
+    protected Type visitLikePredicate(
+        LikePredicate node, StackableAstVisitorContext<Context> context) {
+      Type valueType = process(node.getValue(), context);
+      if (!(valueType instanceof BinaryType)) {
+        throw new SemanticException(
+            String.format(
+                "Left side of LIKE expression must evaluate to a BinaryType (actual: %s)",
+                valueType));
+      }
+
+      Type patternType = process(node.getPattern(), context);
+      if (!(patternType instanceof BinaryType)) {
+        throw new SemanticException(
+            String.format(
+                "Pattern for LIKE expression must evaluate to a BinaryType (actual: %s)",
+                patternType));
+      }
+      if (node.getEscape().isPresent()) {
+        Expression escape = node.getEscape().get();
+        Type escapeType = process(escape, context);
+        if (!(escapeType instanceof BinaryType)) {
+          throw new SemanticException(
+              String.format(
+                  "Escape for LIKE expression must evaluate to a BinaryType (actual: %s)",
+                  escapeType));
+        }
+      }
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    protected Type visitStringLiteral(
+        StringLiteral node, StackableAstVisitorContext<Context> context) {
+      return setExpressionType(node, TEXT);
+    }
+
+    @Override
+    protected Type visitBinaryLiteral(
+        BinaryLiteral node, StackableAstVisitorContext<Context> context) {
+      throw new SemanticException("BinaryLiteral is not supported yet.");
+    }
+
+    @Override
+    protected Type visitLongLiteral(LongLiteral node, StackableAstVisitorContext<Context> context) {
+      if (node.getParsedValue() >= Integer.MIN_VALUE
+          && node.getParsedValue() <= Integer.MAX_VALUE) {
+        return setExpressionType(node, INT32);
+      }
+
+      return setExpressionType(node, INT64);
+    }
+
+    @Override
+    protected Type visitDoubleLiteral(
+        DoubleLiteral node, StackableAstVisitorContext<Context> context) {
+      return setExpressionType(node, DOUBLE);
+    }
+
+    @Override
+    protected Type visitDecimalLiteral(
+        DecimalLiteral node, StackableAstVisitorContext<Context> context) {
+      throw new SemanticException("DecimalLiteral is not supported yet.");
+    }
+
+    @Override
+    protected Type visitBooleanLiteral(
+        BooleanLiteral node, StackableAstVisitorContext<Context> context) {
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    protected Type visitGenericLiteral(
+        GenericLiteral node, StackableAstVisitorContext<Context> context) {
+      throw new SemanticException("GenericLiteral is not supported yet.");
+    }
+
+    @Override
+    protected Type visitNullLiteral(NullLiteral node, StackableAstVisitorContext<Context> context) {
+      return setExpressionType(node, UNKNOWN);
+    }
+
+    @Override
+    protected Type visitFunctionCall(
+        FunctionCall node, StackableAstVisitorContext<Context> context) {
+      // TODO implememt function call
+      throw new SemanticException("FunctionCall is not implemented yet.");
+      //      boolean isAggregation = functionResolver.isAggregationFunction(session,
+      // node.getName(), accessControl);
+      //      // argument of the form `label.*` is only allowed for row pattern count function
+      //      node.getArguments().stream()
+      //          .filter(DereferenceExpression::isQualifiedAllFieldsReference)
+      //          .findAny()
+      //          .ifPresent(allRowsReference -> {
+      //            if (node.getArguments().size() > 1) {
+      //              throw new SemanticException(
+      //                  "label.* syntax is only supported as the only argument of row pattern
+      // count function");
+      //            }
+      //          });
+      //
+      //      if (node.isDistinct() && !isAggregation) {
+      //        throw new SemanticException("DISTINCT is not supported for non-aggregation
+      // functions");
+      //      }
+      //
+      //      List<TypeSignatureProvider> argumentTypes = getCallArgumentTypes(node.getArguments(),
+      // context);
+      //
+      //      ResolvedFunction function;
+      //      try {
+      //        function = functionResolver.resolveFunction(session, node.getName(), argumentTypes,
+      // accessControl);
+      //      } catch (TrinoException e) {
+      //        if (e.getLocation().isPresent()) {
+      //          // If analysis of any of the argument types (which is done lazily to deal with
+      // lambda
+      //          // expressions) fails, we want to report the original reason for the failure
+      //          throw e;
+      //        }
+      //
+      //        // otherwise, it must have failed due to a missing function or other reason, so we
+      // report an error at the
+      //        // current location
+      //
+      //        throw new TrinoException(e::getErrorCode, extractLocation(node), e.getMessage(), e);
+      //      }
+      //
+      //      if (node.getArguments().size() > 127) {
+      //        throw new SemanticException(String.format("Too many arguments for function call
+      // %s()",
+      //            function.getSignature().getName().getFunctionName()));
+      //      }
+      //
+      //      BoundSignature signature = function.getSignature();
+      //      for (int i = 0; i < argumentTypes.size(); i++) {
+      //        Expression expression = node.getArguments().get(i);
+      //        Type expectedType = signature.getArgumentTypes().get(i);
+      //        if (expectedType == null) {
+      //          throw new NullPointerException(format("Type '%s' not found",
+      // signature.getArgumentTypes().get(i)));
+      //        }
+      //        if (node.isDistinct() && !expectedType.isComparable()) {
+      //          throw new SemanticException(String.format("DISTINCT can only be applied to
+      // comparable types (actual: %s)",
+      //              expectedType));
+      //        }
+      //        Type actualType =
+      // plannerContext.getTypeManager().getType(argumentTypes.get(i).getTypeSignature());
+      //
+      //        coerceType(expression, actualType, expectedType, String.format("Function %s argument
+      // %d", function, i));
+      //      }
+      //      resolvedFunctions.put(NodeRef.of(node), function);
+      //
+      //      Type type = signature.getReturnType();
+      //      return setExpressionType(node, type);
+    }
+
+    //    public List<TypeSignatureProvider> getCallArgumentTypes(List<Expression> arguments,
+    //
+    // StackableAstVisitorContext<Context> context) {
+    //      ImmutableList.Builder<TypeSignatureProvider> argumentTypesBuilder =
+    // ImmutableList.builder();
+    //      for (Expression argument : arguments) {
+    //        if (isQualifiedAllFieldsReference(argument)) {
+    //          // to resolve `count(label.*)` correctly, we should skip the argument, like for
+    // `count(*)`
+    //          // process the argument but do not include it in the list
+    //          DereferenceExpression allRowsDereference = (DereferenceExpression) argument;
+    //          String label = label((Identifier) allRowsDereference.getBase());
+    //          if (!context.getContext().getLabels().contains(label)) {
+    //            throw semanticException(INVALID_FUNCTION_ARGUMENT, allRowsDereference.getBase(),
+    //                "%s is not a primary pattern variable or subset name", label);
+    //          }
+    //          labelDereferences.put(NodeRef.of(allRowsDereference), new
+    // LabelPrefixedReference(label));
+    //        } else {
+    //          argumentTypesBuilder.add(new TypeSignatureProvider(process(argument,
+    // context).getTypeSignature()));
+    //        }
+    //      }
+    //
+    //      return argumentTypesBuilder.build();
+    //    }
+
+    private String label(Identifier identifier) {
+      return identifier.getCanonicalValue();
+    }
+
+    @Override
+    protected Type visitCurrentDatabase(
+        CurrentDatabase node, StackableAstVisitorContext<Context> context) {
+      return setExpressionType(node, TEXT);
+    }
+
+    @Override
+    protected Type visitCurrentUser(CurrentUser node, StackableAstVisitorContext<Context> context) {
+      return setExpressionType(node, TEXT);
+    }
+
+    @Override
+    protected Type visitTrim(Trim node, StackableAstVisitorContext<Context> context) {
+      // TODO implement TRIM
+      throw new SemanticException("Trim is not implemented yet");
+      //      ImmutableList.Builder<Type> argumentTypes = ImmutableList.builder();
+      //
+      //      argumentTypes.add(process(node.getTrimSource(), context));
+      //      node.getTrimCharacter().ifPresent(trimChar -> argumentTypes.add(process(trimChar,
+      // context)));
+      //      List<Type> actualTypes = argumentTypes.build();
+      //
+      //      String functionName = node.getSpecification().getFunctionName();
+      //      ResolvedFunction function =
+      //          plannerContext.getMetadata().resolveBuiltinFunction(functionName,
+      // fromTypes(actualTypes));
+      //
+      //      List<Type> expectedTypes = function.getSignature().getArgumentTypes();
+      //      checkState(expectedTypes.size() == actualTypes.size(), "wrong argument number in the
+      // resolved signature");
+      //
+      //      Type actualTrimSourceType = actualTypes.get(0);
+      //      Type expectedTrimSourceType = expectedTypes.get(0);
+      //      coerceType(node.getTrimSource(), actualTrimSourceType, expectedTrimSourceType,
+      //          "source argument of trim function");
+      //
+      //      if (node.getTrimCharacter().isPresent()) {
+      //        Type actualTrimCharType = actualTypes.get(1);
+      //        Type expectedTrimCharType = expectedTypes.get(1);
+      //        coerceType(node.getTrimCharacter().get(), actualTrimCharType, expectedTrimCharType,
+      //            "trim character argument of trim function");
+      //      }
+      //      resolvedFunctions.put(NodeRef.of(node), function);
+      //
+      //      return setExpressionType(node, function.getSignature().getReturnType());
+    }
+
+    @Override
+    protected Type visitParameter(Parameter node, StackableAstVisitorContext<Context> context) {
+
+      if (parameters.isEmpty()) {
+        throw new SemanticException("Query takes no parameters");
+      }
+      if (node.getId() >= parameters.size()) {
+        throw new SemanticException(
+            String.format(
+                "Invalid parameter index %s, max value is %s",
+                node.getId(), parameters.size() - 1));
+      }
+
+      Expression providedValue = parameters.get(NodeRef.of(node));
+      if (providedValue == null) {
+        throw new SemanticException("No value provided for parameter");
+      }
+      Type resultType = process(providedValue, context);
+      return setExpressionType(node, resultType);
+    }
+
+    @Override
+    protected Type visitBetweenPredicate(
+        BetweenPredicate node, StackableAstVisitorContext<Context> context) {
+      Type valueType = process(node.getValue(), context);
+      Type minType = process(node.getMin(), context);
+      Type maxType = process(node.getMax(), context);
+
+      if (valueType.equals(minType) || valueType.equals(maxType)) {
+        throw new SemanticException(
+            String.format("Cannot check if %s is BETWEEN %s and %s", valueType, minType, maxType));
+      }
+
+      if (!valueType.isOrderable()) {
+        throw new SemanticException(
+            String.format("Cannot check if %s is BETWEEN %s and %s", valueType, minType, maxType));
+      }
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    public Type visitCast(Cast node, StackableAstVisitorContext<Context> context) {
+      // TODO implement cast
+      throw new SemanticException("Cast is not implemented yet");
+      //
+      //      Type type;
+      //      try {
+      //        type = plannerContext.getTypeManager().getType(toTypeSignature(node.getType()));
+      //      } catch (TypeNotFoundException e) {
+      //        throw semanticException(TYPE_MISMATCH, node, "Unknown type: %s", node.getType());
+      //      }
+      //
+      //      if (type.equals(UnknownType.UNKNOWN)) {
+      //        throw new SemanticException("UNKNOWN is not a valid type");
+      //      }
+      //
+      //      Type value = process(node.getExpression(), context);
+      //      if (!value.equals(UnknownType.UNKNOWN) && !node.isTypeOnly()) {
+      //        // TODO implement cast
+      //        try {
+      //          plannerContext.getMetadata().getCoercion(value, type);
+      //        } catch (OperatorNotFoundException e) {
+      //          throw semanticException(TYPE_MISMATCH, node, "Cannot cast %s to %s", value, type);
+      //        }
+      //      }
+      //
+      //      return setExpressionType(node, type);
+    }
+
+    @Override
+    protected Type visitInPredicate(InPredicate node, StackableAstVisitorContext<Context> context) {
+      Expression value = node.getValue();
+      Expression valueList = node.getValueList();
+
+      // When an IN-predicate containing a subquery: `x IN (SELECT ...)` is planned, both `value`
+      // and `valueList` are pre-planned.
+      // In the row pattern matching context, expressions can contain labeled column references,
+      // navigations, CALSSIFIER(), and MATCH_NUMBER() calls.
+      // None of these can be pre-planned. Instead, the query fails:
+      // - if such elements are in the `value list` (subquery), the analysis of the subquery fails
+      // as it is done in a non-pattern-matching context.
+      // - if such elements are in `value`, they are captured by the below check.
+      //
+      // NOTE: Theoretically, if the IN-predicate does not contain CLASSIFIER() or MATCH_NUMBER()
+      // calls, it could be pre-planned
+      // on the condition that all column references of the `value` are consistently navigated
+      // (i.e., the expression is effectively evaluated within a single row),
+      // and that the same navigation should be applied to the resulting symbol.
+      // Currently, we only support the case when there are no explicit labels or navigations. This
+      // is a special case of such
+      // consistent navigating, as the column reference `x` defaults to `RUNNING
+      // LAST(universal_pattern_variable.x)`.
+
+      if (valueList instanceof InListExpression) {
+        InListExpression inListExpression = (InListExpression) valueList;
+        Type type =
+            coerceToSingleType(
+                context,
+                "IN value and list items",
+                ImmutableList.<Expression>builder()
+                    .add(value)
+                    .addAll(inListExpression.getValues())
+                    .build());
+        setExpressionType(inListExpression, type);
+      } else if (valueList instanceof SubqueryExpression) {
+        subqueryInPredicates.add(NodeRef.of(node));
+        analyzePredicateWithSubquery(
+            node, process(value, context), (SubqueryExpression) valueList, context);
+      } else {
+        throw new IllegalArgumentException(
+            "Unexpected value list type for InPredicate: "
+                + node.getValueList().getClass().getName());
+      }
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    protected Type visitSubqueryExpression(
+        SubqueryExpression node, StackableAstVisitorContext<Context> context) {
+      Type type = analyzeSubquery(node, context);
+
+      // the implied type of a scalar subquery is that of the unique field in the single-column row
+      if (type instanceof RowType && ((RowType) type).getFields().size() == 1) {
+        type = type.getTypeParameters().get(0);
+      }
+
+      setExpressionType(node, type);
+      subqueries.add(NodeRef.of(node));
+      return type;
+    }
+
+    /** @return the common supertype between the value type and subquery type */
+    private Type analyzePredicateWithSubquery(
+        Expression node,
+        Type declaredValueType,
+        SubqueryExpression subquery,
+        StackableAstVisitorContext<Context> context) {
+      Type valueRowType = declaredValueType;
+      if (!(declaredValueType instanceof RowType) && !(declaredValueType instanceof UnknownType)) {
+        valueRowType = RowType.anonymous(ImmutableList.of(declaredValueType));
+      }
+
+      Type subqueryType = analyzeSubquery(subquery, context);
+      setExpressionType(subquery, subqueryType);
+
+      if (subqueryType.equals(valueRowType)) {
+        throw new SemanticException(
+            String.format(
+                "Value expression and result of subquery must be of the same type: %s vs %s",
+                valueRowType, subqueryType));
+      }
+
+      return subqueryType;
+    }
+
+    private Type analyzeSubquery(
+        SubqueryExpression node, StackableAstVisitorContext<Context> context) {
+      StatementAnalyzer analyzer =
+          statementAnalyzerFactory.apply(node, context.getContext().getCorrelationSupport());
+      Scope subqueryScope = Scope.builder().withParent(context.getContext().getScope()).build();
+      Scope queryScope = analyzer.analyze(node.getQuery(), subqueryScope);
+
+      ImmutableList.Builder<RowType.Field> fields = ImmutableList.builder();
+      for (int i = 0; i < queryScope.getRelationType().getAllFieldCount(); i++) {
+        Field field = queryScope.getRelationType().getFieldByIndex(i);
+        if (!field.isHidden()) {
+          if (field.getName().isPresent()) {
+            fields.add(RowType.field(field.getName().get(), field.getType()));
+          } else {
+            fields.add(RowType.field(field.getType()));
+          }
+        }
+      }
+
+      sourceFields.addAll(queryScope.getRelationType().getVisibleFields());
+      return RowType.from(fields.build());
+    }
+
+    @Override
+    protected Type visitExists(ExistsPredicate node, StackableAstVisitorContext<Context> context) {
+      StatementAnalyzer analyzer =
+          statementAnalyzerFactory.apply(node, context.getContext().getCorrelationSupport());
+      Scope subqueryScope = Scope.builder().withParent(context.getContext().getScope()).build();
+
+      List<RowType.Field> fields =
+          analyzer.analyze(node.getSubquery(), subqueryScope).getRelationType().getAllFields()
+              .stream()
+              .map(
+                  field -> {
+                    if (field.getName().isPresent()) {
+                      return RowType.field(field.getName().get(), field.getType());
+                    }
+
+                    return RowType.field(field.getType());
+                  })
+              .collect(toImmutableList());
+
+      // TODO: this should be multiset(row(...))
+      setExpressionType(node.getSubquery(), RowType.from(fields));
+
+      existsSubqueries.add(NodeRef.of(node));
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    protected Type visitQuantifiedComparisonExpression(
+        QuantifiedComparisonExpression node, StackableAstVisitorContext<Context> context) {
+      quantifiedComparisons.add(NodeRef.of(node));
+
+      Type declaredValueType = process(node.getValue(), context);
+      Type comparisonType =
+          analyzePredicateWithSubquery(
+              node, declaredValueType, (SubqueryExpression) node.getSubquery(), context);
+
+      switch (node.getOperator()) {
+        case LESS_THAN:
+        case LESS_THAN_OR_EQUAL:
+        case GREATER_THAN:
+        case GREATER_THAN_OR_EQUAL:
+          if (!comparisonType.isOrderable()) {
+            throw new SemanticException(
+                String.format(
+                    "Type [%s] must be orderable in order to be used in quantified comparison",
+                    comparisonType));
+          }
+          break;
+        case EQUAL:
+        case NOT_EQUAL:
+          if (!comparisonType.isComparable()) {
+            throw new SemanticException(
+                String.format(
+                    "Type [%s] must be comparable in order to be used in quantified comparison",
+                    comparisonType));
+          }
+          break;
+        default:
+          throw new IllegalStateException(
+              format("Unexpected comparison type: %s", node.getOperator()));
+      }
+
+      return setExpressionType(node, BOOLEAN);
+    }
+
+    @Override
+    public Type visitFieldReference(
+        FieldReference node, StackableAstVisitorContext<Context> context) {
+      ResolvedField field = baseScope.getField(node.getFieldIndex());
+      return handleResolvedField(node, field, context);
+    }
+
+    @Override
+    protected Type visitExpression(Expression node, StackableAstVisitorContext<Context> context) {
+      throw new SemanticException(
+          String.format("not yet implemented: %s", node.getClass().getName()));
+    }
+
+    @Override
+    protected Type visitNode(Node node, StackableAstVisitorContext<Context> context) {
+      throw new SemanticException(
+          String.format("not yet implemented: %s", node.getClass().getName()));
+    }
+
+    private Type getOperator(
+        StackableAstVisitorContext<Context> context,
+        Expression node,
+        OperatorType operatorType,
+        Expression... arguments) {
+      ImmutableList.Builder<Type> argumentTypes = ImmutableList.builder();
+      for (Expression expression : arguments) {
+        argumentTypes.add(process(expression, context));
+      }
+
+      BoundSignature operatorSignature;
+      try {
+        operatorSignature =
+            metadata.resolveOperator(operatorType, argumentTypes.build()).getSignature();
+      } catch (OperatorNotFoundException e) {
+        throw new SemanticException(e.getMessage());
+      }
+
+      for (int i = 0; i < arguments.length; i++) {
+        Expression expression = arguments[i];
+        Type type = operatorSignature.getArgumentTypes().get(i);
+        coerceType(
+            context,
+            expression,
+            type,
+            String.format("Operator %s argument %d", operatorSignature, i));
+      }
+
+      Type type = operatorSignature.getReturnType();
+      return setExpressionType(node, type);
+    }
+
+    private void coerceType(
+        Expression expression, Type actualType, Type expectedType, String message) {
+      if (!actualType.equals(expectedType)) {
+        //        if (!typeCoercion.canCoerce(actualType, expectedType)) {
+        throw new SemanticException(
+            String.format(
+                "%s must evaluate to a %s (actual: %s)", message, expectedType, actualType));
+        //        }
+        //        addOrReplaceExpressionCoercion(expression, actualType, expectedType);
+      }
+    }
+
+    private void coerceType(
+        StackableAstVisitorContext<Context> context,
+        Expression expression,
+        Type expectedType,
+        String message) {
+      Type actualType = process(expression, context);
+      coerceType(expression, actualType, expectedType, message);
+    }
+
+    private Type coerceToSingleType(
+        StackableAstVisitorContext<Context> context,
+        Node node,
+        String message,
+        Expression first,
+        Expression second) {
+      Type firstType = UNKNOWN;
+      if (first != null) {
+        firstType = process(first, context);
+      }
+      Type secondType = UNKNOWN;
+      if (second != null) {
+        secondType = process(second, context);
+      }
+
+      if (!firstType.equals(secondType)) {
+        throw new SemanticException(String.format("%s: %s vs %s", message, firstType, secondType));
+      }
+
+      return firstType;
+    }
+
+    private Type coerceToSingleType(
+        StackableAstVisitorContext<Context> context,
+        String description,
+        List<Expression> expressions) {
+      // determine super type
+      Type superType = UNKNOWN;
+
+      // Use LinkedHashMultimap to preserve order in which expressions are analyzed within IN list
+      Multimap<Type, NodeRef<Expression>> typeExpressions = LinkedHashMultimap.create();
+      for (Expression expression : expressions) {
+        // We need to wrap as NodeRef since LinkedHashMultimap does not allow duplicated values
+        Type type = process(expression, context);
+        typeExpressions.put(type, NodeRef.of(expression));
+      }
+
+      Set<Type> types = typeExpressions.keySet();
+
+      for (Type type : types) {
+        if (superType == UNKNOWN) {
+          superType = type;
+        } else {
+          if (!superType.equals(type)) {
+            throw new SemanticException(
+                String.format(
+                    "%s must be the same type or coercible to a common type. Cannot find common type between %s and %s, all types (without duplicates): %s",
+                    description, superType, type, typeExpressions.keySet()));
+          }
+        }
+        //        Optional<Type> newSuperType = typeCoercion.getCommonSuperType(superType, type);
+        //        if (newSuperType.isEmpty()) {
+        //          throw semanticException(TYPE_MISMATCH, Iterables.get(typeExpressions.get(type),
+        // 0).getNode(),
+        //              "%s must be the same type or coercible to a common type. Cannot find common
+        // type between %s and %s, all types (without duplicates): %s",
+        //              description,
+        //              superType,
+        //              type,
+        //              typeExpressions.keySet());
+        //        }
+        //        superType = newSuperType.get();
+      }
+
+      // verify all expressions can be coerced to the superType
+      //      for (Type type : types) {
+      //        Collection<NodeRef<Expression>> coercionCandidates = typeExpressions.get(type);
+
+      //        if (!type.equals(superType)) {
+      //          if (!typeCoercion.canCoerce(type, superType)) {
+
+      //          }
+      //          addOrReplaceExpressionsCoercion(coercionCandidates, type, superType);
+      //        }
+      //      }
+
+      return superType;
+    }
+
+    //    private void addOrReplaceExpressionCoercion(Expression expression, Type type, Type
+    // superType) {
+    //      addOrReplaceExpressionsCoercion(ImmutableList.of(NodeRef.of(expression)), type,
+    // superType);
+    //    }
+    //
+    //    private void addOrReplaceExpressionsCoercion(Collection<NodeRef<Expression>> expressions,
+    // Type type,
+    //                                                 Type superType) {
+    //      expressions.forEach(expression -> expressionCoercions.put(expression, superType));
+    //      if (typeCoercion.isTypeOnlyCoercion(type, superType)) {
+    //        typeOnlyCoercions.addAll(expressions);
+    //      } else {
+    //        typeOnlyCoercions.removeAll(expressions);
+    //      }
+    //    }
+  }
+
+  private static class Context {
+    private final Scope scope;
+
+    // functionInputTypes and nameToLambdaDeclarationMap can be null or non-null independently. All
+    // 4 combinations are possible.
+
+    // The list of types when expecting a lambda (i.e. processing lambda parameters of a function);
+    // null otherwise.
+    // Empty list represents expecting a lambda with no arguments.
+    private final List<Type> functionInputTypes;
+    // The mapping from names to corresponding lambda argument declarations when inside a lambda;
+    // null otherwise.
+    // Empty map means that the all lambda expressions surrounding the current node has no
+    // arguments.
+    //    private final Map<FieldId, LambdaArgumentDeclaration> fieldToLambdaArgumentDeclaration;
+
+    // Primary row pattern variables and named unions (subsets) of variables
+    // necessary for the analysis of expressions in the context of row pattern recognition
+    private final Set<String> labels;
+
+    private final CorrelationSupport correlationSupport;
+
+    private Context(
+        Scope scope,
+        List<Type> functionInputTypes,
+        Set<String> labels,
+        CorrelationSupport correlationSupport) {
+      this.scope = requireNonNull(scope, "scope is null");
+      this.functionInputTypes = functionInputTypes;
+      //      this.fieldToLambdaArgumentDeclaration = fieldToLambdaArgumentDeclaration;
+      this.labels = labels;
+      this.correlationSupport = requireNonNull(correlationSupport, "correlationSupport is null");
+    }
+
+    public static Context notInLambda(Scope scope, CorrelationSupport correlationSupport) {
+      return new Context(scope, null, null, correlationSupport);
+    }
+
+    public Context expectingLambda(List<Type> functionInputTypes) {
+      return new Context(
+          scope,
+          requireNonNull(functionInputTypes, "functionInputTypes is null"),
+          labels,
+          correlationSupport);
+    }
+
+    public Context notExpectingLambda() {
+      return new Context(scope, null, labels, correlationSupport);
+    }
+
+    public static Context patternRecognition(Scope scope, Set<String> labels) {
+      return new Context(
+          scope, null, requireNonNull(labels, "labels is null"), CorrelationSupport.DISALLOWED);
+    }
+
+    public Context patternRecognition(Set<String> labels) {
+      return new Context(
+          scope,
+          functionInputTypes,
+          requireNonNull(labels, "labels is null"),
+          CorrelationSupport.DISALLOWED);
+    }
+
+    public Context notExpectingLabels() {
+      return new Context(scope, functionInputTypes, null, correlationSupport);
+    }
+
+    Scope getScope() {
+      return scope;
+    }
+
+    //    public boolean isInLambda() {
+    //      return fieldToLambdaArgumentDeclaration != null;
+    //    }
+
+    public boolean isExpectingLambda() {
+      return functionInputTypes != null;
+    }
+
+    public List<Type> getFunctionInputTypes() {
+      checkState(isExpectingLambda());
+      return functionInputTypes;
+    }
+
+    //    public Set<String> getLabels() {
+    //      checkState(isPatternRecognition());
+    //      return labels;
+    //    }
+
+    public CorrelationSupport getCorrelationSupport() {
+      return correlationSupport;
+    }
+  }
+
+  public static ExpressionAnalysis analyzeExpressions(
+      Metadata metadata,
+      SessionInfo session,
+      AccessControl accessControl,
+      TypeProvider types,
+      Iterable<Expression> expressions,
+      Map<NodeRef<Parameter>, Expression> parameters,
+      WarningCollector warningCollector) {
+    Analysis analysis = new Analysis(null, parameters);
+    ExpressionAnalyzer analyzer =
+        new ExpressionAnalyzer(metadata, accessControl, analysis, session, types, warningCollector);
+    for (Expression expression : expressions) {
+      analyzer.analyze(
+          expression,
+          Scope.builder().withRelationType(RelationId.anonymous(), new RelationType()).build());
+    }
+
+    return new ExpressionAnalysis(
+        analyzer.getExpressionTypes(),
+        analyzer.getSubqueryInPredicates(),
+        analyzer.getSubqueries(),
+        analyzer.getExistsSubqueries(),
+        analyzer.getColumnReferences(),
+        analyzer.getQuantifiedComparisons());
+  }
+
   public static ExpressionAnalysis analyzeExpression(
+      Metadata metadata,
       SessionInfo session,
       AccessControl accessControl,
       Scope scope,
@@ -39,25 +1426,131 @@ public class ExpressionAnalyzer {
       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;
+    ExpressionAnalyzer analyzer =
+        new ExpressionAnalyzer(
+            metadata, 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.getSubqueryInPredicates(),
+        analyzer.getSubqueries(),
+        analyzer.getExistsSubqueries(),
+        analyzer.getColumnReferences(),
+        analyzer.getQuantifiedComparisons());
+  }
+
+  public static void analyzeExpressionWithoutSubqueries(
+      Metadata metadata,
+      SessionInfo session,
+      AccessControl accessControl,
+      Scope scope,
+      Analysis analysis,
+      Expression expression,
+      String message,
+      WarningCollector warningCollector,
+      CorrelationSupport correlationSupport) {
+    ExpressionAnalyzer analyzer =
+        new ExpressionAnalyzer(
+            metadata,
+            accessControl,
+            (node, ignored) -> {
+              throw new SemanticException(message);
+            },
+            session,
+            TypeProvider.empty(),
+            analysis.getParameters(),
+            warningCollector,
+            analysis::getType);
+    analyzer.analyze(expression, scope, correlationSupport);
+
+    updateAnalysis(analysis, analyzer, session, accessControl);
+    analysis.addExpressionFields(expression, analyzer.getSourceFields());
+  }
+
+  private static void updateAnalysis(
+      Analysis analysis,
+      ExpressionAnalyzer analyzer,
+      SessionInfo session,
+      AccessControl accessControl) {
+    analysis.addTypes(analyzer.getExpressionTypes());
+    analysis.addColumnReferences(analyzer.getColumnReferences());
+    analysis.addTableColumnReferences(
+        accessControl, session.getIdentity(), analyzer.getTableColumnReferences());
+  }
+
+  public static ExpressionAnalyzer createConstantAnalyzer(
+      Metadata metadata,
+      AccessControl accessControl,
+      SessionInfo session,
+      Map<NodeRef<Parameter>, Expression> parameters,
+      WarningCollector warningCollector) {
+    return createWithoutSubqueries(
+        metadata,
+        accessControl,
+        session,
+        parameters,
+        "Constant expression cannot contain a subquery",
+        warningCollector);
+  }
+
+  public static ExpressionAnalyzer createWithoutSubqueries(
+      Metadata metadata,
+      AccessControl accessControl,
+      SessionInfo session,
+      Map<NodeRef<Parameter>, Expression> parameters,
+      String message,
+      WarningCollector warningCollector) {
+    return createWithoutSubqueries(
+        metadata,
+        accessControl,
+        session,
+        TypeProvider.empty(),
+        parameters,
+        node -> new SemanticException(message),
+        warningCollector);
+  }
+
+  public static ExpressionAnalyzer createWithoutSubqueries(
+      Metadata metadata,
+      AccessControl accessControl,
+      SessionInfo session,
+      TypeProvider symbolTypes,
+      Map<NodeRef<Parameter>, Expression> parameters,
+      Function<Node, ? extends RuntimeException> statementAnalyzerRejection,
+      WarningCollector warningCollector) {
+    return new ExpressionAnalyzer(
+        metadata,
+        accessControl,
+        (node, correlationSupport) -> {
+          throw statementAnalyzerRejection.apply(node);
+        },
+        session,
+        symbolTypes,
+        parameters,
+        warningCollector,
+        expression -> {
+          throw new IllegalStateException("Cannot access preanalyzed types");
+        });
+  }
+
+  public static boolean isNumericType(Type type) {
+    return type.equals(INT32) || type.equals(INT64) || type.equals(FLOAT) || type.equals(DOUBLE);
+  }
+
+  private static boolean isExactNumericWithScaleZero(Type type) {
+    return type.equals(INT32) || type.equals(INT64);
+  }
+
+  public static boolean isStringType(Type type) {
+    return isCharacterStringType(type);
+  }
+
+  public static boolean isCharacterStringType(Type type) {
+    return type instanceof BinaryType;
   }
 
   public static class LabelPrefixedReference {
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 89628784651..5b465e02be7 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
@@ -1911,6 +1911,7 @@ public class StatementAnalyzer {
 
         ExpressionAnalysis expressionAnalysis =
             ExpressionAnalyzer.analyzeExpression(
+                metadata,
                 sessionContext,
                 accessControl,
                 orderByScope,
@@ -2096,6 +2097,7 @@ public class StatementAnalyzer {
 
     private ExpressionAnalysis analyzeExpression(Expression expression, Scope scope) {
       return ExpressionAnalyzer.analyzeExpression(
+          metadata,
           sessionContext,
           accessControl,
           scope,
@@ -2108,6 +2110,7 @@ public class StatementAnalyzer {
     private ExpressionAnalysis analyzeExpression(
         Expression expression, Scope scope, CorrelationSupport correlationSupport) {
       return ExpressionAnalyzer.analyzeExpression(
+          metadata,
           sessionContext,
           accessControl,
           scope,
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/BoundSignature.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/BoundSignature.java
new file mode 100644
index 00000000000..2765386b968
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/BoundSignature.java
@@ -0,0 +1,97 @@
+/*
+ * 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.function;
+
+import org.apache.iotdb.tsfile.read.common.type.Type;
+
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+
+public class BoundSignature {
+
+  private final String functionName;
+  private final Type returnType;
+  private final List<Type> argumentTypes;
+
+  public BoundSignature(String functionName, Type returnType, List<Type> argumentTypes) {
+    this.functionName = requireNonNull(functionName, "functionName is null");
+    this.returnType = requireNonNull(returnType, "returnType is null");
+    this.argumentTypes = argumentTypes;
+  }
+
+  /** The absolute canonical name of the function. */
+  public String getName() {
+    return functionName;
+  }
+
+  public Type getReturnType() {
+    return returnType;
+  }
+
+  public int getArity() {
+    return argumentTypes.size();
+  }
+
+  public Type getArgumentType(int index) {
+    return argumentTypes.get(index);
+  }
+
+  public List<Type> getArgumentTypes() {
+    return argumentTypes;
+  }
+
+  //  public Signature toSignature() {
+  //    return Signature.builder()
+  //        .returnType(returnType)
+  //        .argumentTypes(argumentTypes.stream()
+  //            .map(Type::getTypeSignature)
+  //            .collect(Collectors.toList()))
+  //        .build();
+  //  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    BoundSignature that = (BoundSignature) o;
+    return Objects.equals(functionName, that.functionName)
+        && Objects.equals(returnType, that.returnType)
+        && Objects.equals(argumentTypes, that.argumentTypes);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(functionName, returnType, argumentTypes);
+  }
+
+  @Override
+  public String toString() {
+    return functionName
+        + argumentTypes.stream().map(Type::toString).collect(joining(", ", "(", "):"))
+        + returnType;
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionId.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionId.java
new file mode 100644
index 00000000000..cfc31ab471c
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionId.java
@@ -0,0 +1,68 @@
+/*
+ * 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.function;
+
+import java.util.Locale;
+
+import static java.util.Objects.requireNonNull;
+
+public class FunctionId {
+  private final String id;
+
+  public FunctionId(String id) {
+    requireNonNull(id, "id is null");
+    if (id.isEmpty()) {
+      throw new IllegalArgumentException("id must not be empty");
+    }
+    if (!id.toLowerCase(Locale.US).equals(id)) {
+      throw new IllegalArgumentException("id must be lowercase");
+    }
+    if (id.contains("@")) {
+      throw new IllegalArgumentException("id must not contain '@'");
+    }
+    this.id = id;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    FunctionId that = (FunctionId) o;
+    return id.equals(that.id);
+  }
+
+  @Override
+  public int hashCode() {
+    return id.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return id;
+  }
+
+  public static FunctionId toFunctionId(String canonicalName, Signature signature) {
+    return new FunctionId((canonicalName + signature).toLowerCase(Locale.US));
+  }
+}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionKind.java
similarity index 85%
copy from iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionKind.java
index eeacbb14817..1e7c3da34a7 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/FunctionKind.java
@@ -17,20 +17,11 @@
  * under the License.
  */
 
-package org.apache.iotdb.tsfile.read.common.type;
+package org.apache.iotdb.db.queryengine.plan.relational.function;
 
-public enum TypeEnum {
-  INT32,
-
-  INT64,
-
-  FLOAT,
-
-  DOUBLE,
-
-  BOOLEAN,
-
-  BINARY,
-
-  ROW
+public enum FunctionKind {
+  SCALAR,
+  AGGREGATE,
+  WINDOW,
+  TABLE,
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/LongVariableConstraint.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/LongVariableConstraint.java
new file mode 100644
index 00000000000..1812a409926
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/LongVariableConstraint.java
@@ -0,0 +1,68 @@
+/*
+ * 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.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+public class LongVariableConstraint {
+  private final String name;
+  private final String expression;
+
+  LongVariableConstraint(String name, String expression) {
+    this.name = requireNonNull(name, "name is null");
+    this.expression = requireNonNull(expression, "expression is null");
+  }
+
+  @JsonProperty
+  public String getName() {
+    return name;
+  }
+
+  @JsonProperty
+  public String getExpression() {
+    return expression;
+  }
+
+  @Override
+  public String toString() {
+    return name + ":" + expression;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    LongVariableConstraint that = (LongVariableConstraint) o;
+    return Objects.equals(name, that.name) && Objects.equals(expression, that.expression);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(name, expression);
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/OperatorType.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/OperatorType.java
new file mode 100644
index 00000000000..0a727e8524a
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/OperatorType.java
@@ -0,0 +1,67 @@
+/*
+ * 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.function;
+
+public enum OperatorType {
+  ADD("+", 2),
+  SUBTRACT("-", 2),
+  MULTIPLY("*", 2),
+  DIVIDE("/", 2),
+  MODULUS("%", 2),
+  NEGATION("-", 1),
+  EQUAL("=", 2),
+  /**
+   * Normal comparison operator, but unordered values such as NaN are placed after all normal
+   * values.
+   */
+  COMPARISON_UNORDERED_LAST("COMPARISON_UNORDERED_LAST", 2),
+  /**
+   * Normal comparison operator, but unordered values such as NaN are placed before all normal
+   * values.
+   */
+  COMPARISON_UNORDERED_FIRST("COMPARISON_UNORDERED_FIRST", 2),
+  LESS_THAN("<", 2),
+  LESS_THAN_OR_EQUAL("<=", 2),
+  CAST("CAST", 1),
+  SUBSCRIPT("[]", 2),
+  HASH_CODE("HASH CODE", 1),
+  SATURATED_FLOOR_CAST("SATURATED FLOOR CAST", 1),
+  IS_DISTINCT_FROM("IS DISTINCT FROM", 2),
+  XX_HASH_64("XX HASH 64", 1),
+  INDETERMINATE("INDETERMINATE", 1),
+  READ_VALUE("READ VALUE", 1),
+/**/ ;
+
+  private final String operator;
+  private final int argumentCount;
+
+  OperatorType(String operator, int argumentCount) {
+    this.operator = operator;
+    this.argumentCount = argumentCount;
+  }
+
+  public String getOperator() {
+    return operator;
+  }
+
+  public int getArgumentCount() {
+    return argumentCount;
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/Signature.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/Signature.java
new file mode 100644
index 00000000000..dfa7667072f
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/Signature.java
@@ -0,0 +1,220 @@
+/*
+ * 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.function;
+
+import org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignature;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Stream.concat;
+
+public class Signature {
+
+  private final List<TypeVariableConstraint> typeVariableConstraints;
+  private final List<LongVariableConstraint> longVariableConstraints;
+  private final TypeSignature returnType;
+  private final List<TypeSignature> argumentTypes;
+  private final boolean variableArity;
+
+  private Signature(
+      List<TypeVariableConstraint> typeVariableConstraints,
+      List<LongVariableConstraint> longVariableConstraints,
+      TypeSignature returnType,
+      List<TypeSignature> argumentTypes,
+      boolean variableArity) {
+    requireNonNull(typeVariableConstraints, "typeVariableConstraints is null");
+    requireNonNull(longVariableConstraints, "longVariableConstraints is null");
+
+    this.typeVariableConstraints = new ArrayList<>(typeVariableConstraints);
+    this.longVariableConstraints = new ArrayList<>(longVariableConstraints);
+    this.returnType = requireNonNull(returnType, "returnType is null");
+    this.argumentTypes = new ArrayList<>(requireNonNull(argumentTypes, "argumentTypes is null"));
+    this.variableArity = variableArity;
+  }
+
+  @JsonProperty
+  public TypeSignature getReturnType() {
+    return returnType;
+  }
+
+  @JsonProperty
+  public List<TypeSignature> getArgumentTypes() {
+    return argumentTypes;
+  }
+
+  @JsonProperty
+  public boolean isVariableArity() {
+    return variableArity;
+  }
+
+  @JsonProperty
+  public List<TypeVariableConstraint> getTypeVariableConstraints() {
+    return typeVariableConstraints;
+  }
+
+  @JsonProperty
+  public List<LongVariableConstraint> getLongVariableConstraints() {
+    return longVariableConstraints;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        typeVariableConstraints, longVariableConstraints, returnType, argumentTypes, variableArity);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof Signature)) {
+      return false;
+    }
+    Signature other = (Signature) obj;
+    return Objects.equals(this.typeVariableConstraints, other.typeVariableConstraints)
+        && Objects.equals(this.longVariableConstraints, other.longVariableConstraints)
+        && Objects.equals(this.returnType, other.returnType)
+        && Objects.equals(this.argumentTypes, other.argumentTypes)
+        && Objects.equals(this.variableArity, other.variableArity);
+  }
+
+  @Override
+  public String toString() {
+    List<String> allConstraints =
+        concat(
+                typeVariableConstraints.stream().map(TypeVariableConstraint::toString),
+                longVariableConstraints.stream().map(LongVariableConstraint::toString))
+            .collect(Collectors.toList());
+
+    return (allConstraints.isEmpty() ? "" : allConstraints.stream().collect(joining(",", "<", ">")))
+        + argumentTypes.stream().map(Objects::toString).collect(joining(",", "(", ")"))
+        + ":"
+        + returnType;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static final class Builder {
+    private final List<TypeVariableConstraint> typeVariableConstraints = new ArrayList<>();
+    private final List<LongVariableConstraint> longVariableConstraints = new ArrayList<>();
+    private TypeSignature returnType;
+    private final List<TypeSignature> argumentTypes = new ArrayList<>();
+    private boolean variableArity;
+
+    private Builder() {}
+
+    public Builder typeVariable(String name) {
+      typeVariableConstraints.add(TypeVariableConstraint.builder(name).build());
+      return this;
+    }
+
+    public Builder comparableTypeParameter(String name) {
+      typeVariableConstraints.add(
+          TypeVariableConstraint.builder(name).comparableRequired().build());
+      return this;
+    }
+
+    public Builder orderableTypeParameter(String name) {
+      typeVariableConstraints.add(TypeVariableConstraint.builder(name).orderableRequired().build());
+      return this;
+    }
+
+    public Builder castableToTypeParameter(String name, TypeSignature toType) {
+      typeVariableConstraints.add(TypeVariableConstraint.builder(name).castableTo(toType).build());
+      return this;
+    }
+
+    public Builder castableFromTypeParameter(String name, TypeSignature fromType) {
+      typeVariableConstraints.add(
+          TypeVariableConstraint.builder(name).castableFrom(fromType).build());
+      return this;
+    }
+
+    public Builder variadicTypeParameter(String name, String variadicBound) {
+      typeVariableConstraints.add(
+          TypeVariableConstraint.builder(name).variadicBound(variadicBound).build());
+      return this;
+    }
+
+    public Builder typeVariableConstraint(TypeVariableConstraint typeVariableConstraint) {
+      this.typeVariableConstraints.add(
+          requireNonNull(typeVariableConstraint, "typeVariableConstraint is null"));
+      return this;
+    }
+
+    public Builder typeVariableConstraints(List<TypeVariableConstraint> typeVariableConstraints) {
+      this.typeVariableConstraints.addAll(
+          requireNonNull(typeVariableConstraints, "typeVariableConstraints is null"));
+      return this;
+    }
+
+    //    public Builder returnType(Type returnType) {
+    //      return returnType(returnType.getTypeSignature());
+    //    }
+
+    public Builder returnType(TypeSignature returnType) {
+      this.returnType = requireNonNull(returnType, "returnType is null");
+      return this;
+    }
+
+    public Builder longVariable(String name, String expression) {
+      this.longVariableConstraints.add(new LongVariableConstraint(name, expression));
+      return this;
+    }
+
+    //    public Builder argumentType(Type type) {
+    //      return argumentType(type.getTypeSignature());
+    //    }
+
+    public Builder argumentType(TypeSignature type) {
+      argumentTypes.add(requireNonNull(type, "type is null"));
+      return this;
+    }
+
+    public Builder argumentTypes(List<TypeSignature> argumentTypes) {
+      this.argumentTypes.addAll(requireNonNull(argumentTypes, "argumentTypes is null"));
+      return this;
+    }
+
+    public Builder variableArity() {
+      this.variableArity = true;
+      return this;
+    }
+
+    public Signature build() {
+      return new Signature(
+          typeVariableConstraints,
+          longVariableConstraints,
+          returnType,
+          argumentTypes,
+          variableArity);
+    }
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/TypeVariableConstraint.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/TypeVariableConstraint.java
new file mode 100644
index 00000000000..47354e4da30
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/TypeVariableConstraint.java
@@ -0,0 +1,200 @@
+/*
+ * 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.function;
+
+import org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignature;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+
+public class TypeVariableConstraint {
+
+  private final String name;
+  private final boolean comparableRequired;
+  private final boolean orderableRequired;
+  private final Optional<String> variadicBound;
+  private final Set<TypeSignature> castableTo;
+  private final Set<TypeSignature> castableFrom;
+
+  private TypeVariableConstraint(
+      String name,
+      boolean comparableRequired,
+      boolean orderableRequired,
+      Optional<String> variadicBound,
+      Set<TypeSignature> castableTo,
+      Set<TypeSignature> castableFrom) {
+    this.name = requireNonNull(name, "name is null");
+    this.comparableRequired = comparableRequired;
+    this.orderableRequired = orderableRequired;
+    this.variadicBound = requireNonNull(variadicBound, "variadicBound is null");
+    if (variadicBound.map(bound -> !bound.equalsIgnoreCase("row")).orElse(false)) {
+      throw new IllegalArgumentException("variadicBound must be row but is " + variadicBound.get());
+    }
+    this.castableTo = new HashSet<>(requireNonNull(castableTo, "castableTo is null"));
+    this.castableFrom = new HashSet<>(requireNonNull(castableFrom, "castableFrom is null"));
+  }
+
+  @JsonProperty
+  public String getName() {
+    return name;
+  }
+
+  @JsonProperty
+  public boolean isComparableRequired() {
+    return comparableRequired;
+  }
+
+  @JsonProperty
+  public boolean isOrderableRequired() {
+    return orderableRequired;
+  }
+
+  @JsonProperty
+  public Optional<String> getVariadicBound() {
+    return variadicBound;
+  }
+
+  @JsonProperty
+  public Set<TypeSignature> getCastableTo() {
+    return castableTo;
+  }
+
+  @JsonProperty
+  public Set<TypeSignature> getCastableFrom() {
+    return castableFrom;
+  }
+
+  @Override
+  public String toString() {
+    String value = name;
+    if (comparableRequired) {
+      value += ":comparable";
+    }
+    if (orderableRequired) {
+      value += ":orderable";
+    }
+    if (variadicBound.isPresent()) {
+      value += ":" + variadicBound + "<*>";
+    }
+    if (!castableTo.isEmpty()) {
+      value +=
+          castableTo.stream().map(Object::toString).collect(joining(", ", ":castableTo(", ")"));
+    }
+    if (!castableFrom.isEmpty()) {
+      value +=
+          castableFrom.stream().map(Object::toString).collect(joining(", ", ":castableFrom(", ")"));
+    }
+    return value;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    TypeVariableConstraint that = (TypeVariableConstraint) o;
+    return comparableRequired == that.comparableRequired
+        && orderableRequired == that.orderableRequired
+        && Objects.equals(name, that.name)
+        && Objects.equals(variadicBound, that.variadicBound)
+        && Objects.equals(castableTo, that.castableTo)
+        && Objects.equals(castableFrom, that.castableFrom);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        name, comparableRequired, orderableRequired, variadicBound, castableTo, castableFrom);
+  }
+
+  public static TypeVariableConstraint typeVariable(String name) {
+    return builder(name).build();
+  }
+
+  public static TypeVariableConstraintBuilder builder(String name) {
+    return new TypeVariableConstraintBuilder(name);
+  }
+
+  public static class TypeVariableConstraintBuilder {
+    private final String name;
+    private boolean comparableRequired;
+    private boolean orderableRequired;
+    private String variadicBound;
+    private final Set<TypeSignature> castableTo = new HashSet<>();
+    private final Set<TypeSignature> castableFrom = new HashSet<>();
+
+    private TypeVariableConstraintBuilder(String name) {
+      this.name = name;
+    }
+
+    public TypeVariableConstraintBuilder comparableRequired() {
+      this.comparableRequired = true;
+      return this;
+    }
+
+    public TypeVariableConstraintBuilder orderableRequired() {
+      this.orderableRequired = true;
+      return this;
+    }
+
+    public TypeVariableConstraintBuilder variadicBound(String variadicBound) {
+      this.variadicBound = variadicBound;
+      return this;
+    }
+
+    //    public TypeVariableConstraintBuilder castableTo(Type type) {
+    //      return castableTo(type.getTypeSignature());
+    //    }
+
+    public TypeVariableConstraintBuilder castableTo(TypeSignature type) {
+      this.castableTo.add(type);
+      return this;
+    }
+
+    //    public TypeVariableConstraintBuilder castableFrom(Type type) {
+    //      return castableFrom(type.getTypeSignature());
+    //    }
+
+    public TypeVariableConstraintBuilder castableFrom(TypeSignature type) {
+      this.castableFrom.add(type);
+      return this;
+    }
+
+    public TypeVariableConstraint build() {
+      return new TypeVariableConstraint(
+          name,
+          comparableRequired,
+          orderableRequired,
+          Optional.ofNullable(variadicBound),
+          castableTo,
+          castableFrom);
+    }
+  }
+}
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
index f4880b58b6e..5e708b9c9f0 100644
--- 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
@@ -20,7 +20,10 @@
 package org.apache.iotdb.db.queryengine.plan.relational.metadata;
 
 import org.apache.iotdb.db.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.queryengine.plan.relational.function.OperatorType;
+import org.apache.iotdb.tsfile.read.common.type.Type;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
@@ -55,4 +58,7 @@ public interface Metadata {
    * @throws RuntimeException if table handle is no longer valid
    */
   Map<String, ColumnHandle> getColumnHandles(SessionInfo session, TableHandle tableHandle);
+
+  ResolvedFunction resolveOperator(OperatorType operatorType, List<? extends Type> argumentTypes)
+      throws OperatorNotFoundException;
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/OperatorNotFoundException.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/OperatorNotFoundException.java
new file mode 100644
index 00000000000..dcc57700dd8
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/OperatorNotFoundException.java
@@ -0,0 +1,119 @@
+/*
+ * 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.commons.exception.IoTDBException;
+import org.apache.iotdb.db.queryengine.plan.relational.function.OperatorType;
+import org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignature;
+import org.apache.iotdb.tsfile.read.common.type.Type;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static org.apache.iotdb.rpc.TSStatusCode.OPERATOR_NOT_FOUND;
+
+public class OperatorNotFoundException extends IoTDBException {
+  private final OperatorType operatorType;
+  private final TypeSignature returnType;
+  private final List<Type> argumentTypes;
+
+  public OperatorNotFoundException(
+      OperatorType operatorType, List<? extends Type> argumentTypes, Throwable cause) {
+    super(
+        formatErrorMessage(operatorType, argumentTypes, Optional.empty()),
+        cause,
+        OPERATOR_NOT_FOUND.getStatusCode());
+    this.operatorType = requireNonNull(operatorType, "operatorType is null");
+    this.returnType = null;
+    this.argumentTypes =
+        ImmutableList.copyOf(requireNonNull(argumentTypes, "argumentTypes is null"));
+  }
+
+  public OperatorNotFoundException(
+      OperatorType operatorType,
+      List<? extends Type> argumentTypes,
+      TypeSignature returnType,
+      Throwable cause) {
+    super(
+        formatErrorMessage(operatorType, argumentTypes, Optional.of(returnType)),
+        cause,
+        OPERATOR_NOT_FOUND.getStatusCode());
+    this.operatorType = requireNonNull(operatorType, "operatorType is null");
+    this.argumentTypes =
+        ImmutableList.copyOf(requireNonNull(argumentTypes, "argumentTypes is null"));
+    this.returnType = requireNonNull(returnType, "returnType is null");
+  }
+
+  private static String formatErrorMessage(
+      OperatorType operatorType,
+      List<? extends Type> argumentTypes,
+      Optional<TypeSignature> returnType) {
+    switch (operatorType) {
+      case ADD:
+      case SUBTRACT:
+      case MULTIPLY:
+      case DIVIDE:
+      case MODULUS:
+      case EQUAL:
+      case LESS_THAN:
+      case LESS_THAN_OR_EQUAL:
+        return format(
+            "Cannot apply operator: %s %s %s",
+            argumentTypes.get(0), operatorType.getOperator(), argumentTypes.get(1));
+      case NEGATION:
+        return format("Cannot negate %s", argumentTypes.get(0));
+      case IS_DISTINCT_FROM:
+        return format(
+            "Cannot check if %s is distinct from %s", argumentTypes.get(0), argumentTypes.get(1));
+      case CAST:
+        return format(
+            "Cannot cast %s to %s",
+            argumentTypes.get(0),
+            returnType.orElseThrow(() -> new NoSuchElementException("No value present")));
+      case SUBSCRIPT:
+        return format(
+            "Cannot use %s for subscript of %s", argumentTypes.get(1), argumentTypes.get(0));
+      default:
+        return format(
+            "Operator '%s'%s cannot be applied to %s",
+            operatorType.getOperator(),
+            returnType.map(value -> ":" + value).orElse(""),
+            Joiner.on(", ").join(argumentTypes));
+    }
+  }
+
+  public OperatorType getOperatorType() {
+    return operatorType;
+  }
+
+  public TypeSignature getReturnType() {
+    return returnType;
+  }
+
+  public List<Type> getArgumentTypes() {
+    return argumentTypes;
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ResolvedFunction.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ResolvedFunction.java
new file mode 100644
index 00000000000..da3ccc0cad2
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/ResolvedFunction.java
@@ -0,0 +1,107 @@
+/*
+ * 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.plan.relational.function.BoundSignature;
+import org.apache.iotdb.db.queryengine.plan.relational.function.FunctionId;
+import org.apache.iotdb.db.queryengine.plan.relational.function.FunctionKind;
+
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+public class ResolvedFunction {
+  private final BoundSignature signature;
+  private final FunctionId functionId;
+  private final FunctionKind functionKind;
+  private final boolean deterministic;
+
+  public ResolvedFunction(
+      BoundSignature signature,
+      FunctionId functionId,
+      FunctionKind functionKind,
+      boolean deterministic) {
+    this.signature = requireNonNull(signature, "signature is null");
+    this.functionId = requireNonNull(functionId, "functionId is null");
+    this.functionKind = requireNonNull(functionKind, "functionKind is null");
+    this.deterministic = deterministic;
+  }
+
+  public BoundSignature getSignature() {
+    return signature;
+  }
+
+  public FunctionId getFunctionId() {
+    return functionId;
+  }
+
+  public FunctionKind getFunctionKind() {
+    return functionKind;
+  }
+
+  public boolean isDeterministic() {
+    return deterministic;
+  }
+
+  //  public static boolean isResolved(QualifiedName name) {
+  //    return SerializedResolvedFunction.isSerializedResolvedFunction(name);
+  //  }
+  //
+  //  public QualifiedName toQualifiedName() {
+  //    CatalogSchemaFunctionName name = toCatalogSchemaFunctionName();
+  //    return QualifiedName.of(name.getCatalogName(), name.getSchemaName(),
+  // name.getFunctionName());
+  //  }
+  //
+  //  public CatalogSchemaFunctionName toCatalogSchemaFunctionName() {
+  //    return ResolvedFunctionDecoder.toCatalogSchemaFunctionName(this);
+  //  }
+  //
+  //  public static CatalogSchemaFunctionName extractFunctionName(QualifiedName qualifiedName) {
+  //    checkArgument(isResolved(qualifiedName), "Expected qualifiedName to be a resolved function:
+  // %s", qualifiedName);
+  //    return SerializedResolvedFunction.fromSerializedName(qualifiedName).functionName();
+  //  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ResolvedFunction that = (ResolvedFunction) o;
+    return Objects.equals(signature, that.signature)
+        && Objects.equals(functionId, that.functionId)
+        && functionKind == that.functionKind
+        && deterministic == that.deterministic;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(signature, functionId, functionKind, deterministic);
+  }
+
+  @Override
+  public String toString() {
+    return signature.toString();
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/NamedTypeSignature.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/NamedTypeSignature.java
new file mode 100644
index 00000000000..b0b8e76edc6
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/NamedTypeSignature.java
@@ -0,0 +1,79 @@
+/*
+ * 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.type;
+
+import com.google.errorprone.annotations.Immutable;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public final class NamedTypeSignature {
+  private final Optional<RowFieldName> fieldName;
+  private final TypeSignature typeSignature;
+
+  public NamedTypeSignature(Optional<RowFieldName> fieldName, TypeSignature typeSignature) {
+    this.fieldName = requireNonNull(fieldName, "fieldName is null");
+    this.typeSignature = requireNonNull(typeSignature, "typeSignature is null");
+  }
+
+  public Optional<RowFieldName> getFieldName() {
+    return fieldName;
+  }
+
+  public TypeSignature getTypeSignature() {
+    return typeSignature;
+  }
+
+  public Optional<String> getName() {
+    return getFieldName().map(RowFieldName::getName);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    NamedTypeSignature other = (NamedTypeSignature) o;
+
+    return Objects.equals(this.fieldName, other.fieldName)
+        && Objects.equals(this.typeSignature, other.typeSignature);
+  }
+
+  @Override
+  public String toString() {
+    if (fieldName.isPresent()) {
+      return format("\"%s\" %s", fieldName.get().getName().replace("\"", "\"\""), typeSignature);
+    }
+    return typeSignature.toString();
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(fieldName, typeSignature);
+  }
+}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/ParameterKind.java
similarity index 85%
copy from iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java
copy to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/ParameterKind.java
index eeacbb14817..f52cb50946b 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/ParameterKind.java
@@ -17,20 +17,11 @@
  * under the License.
  */
 
-package org.apache.iotdb.tsfile.read.common.type;
+package org.apache.iotdb.db.queryengine.plan.relational.type;
 
-public enum TypeEnum {
-  INT32,
-
-  INT64,
-
-  FLOAT,
-
-  DOUBLE,
-
-  BOOLEAN,
-
-  BINARY,
-
-  ROW
+public enum ParameterKind {
+  TYPE,
+  NAMED_TYPE,
+  LONG,
+  VARIABLE
 }
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/RowFieldName.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/RowFieldName.java
new file mode 100644
index 00000000000..d1896d8a9ee
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/RowFieldName.java
@@ -0,0 +1,63 @@
+/*
+ * 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.type;
+
+import com.google.errorprone.annotations.Immutable;
+
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public final class RowFieldName {
+  private final String name;
+
+  public RowFieldName(String name) {
+    this.name = requireNonNull(name, "name is null");
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    RowFieldName other = (RowFieldName) o;
+
+    return Objects.equals(this.name, other.name);
+  }
+
+  @Override
+  public String toString() {
+    return '"' + name.replace("\"", "\"\"") + '"';
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(name);
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/StandardTypes.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/StandardTypes.java
new file mode 100644
index 00000000000..37ae2db6a21
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/StandardTypes.java
@@ -0,0 +1,55 @@
+/*
+ * 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.type;
+
+public class StandardTypes {
+  public static final String BIGINT = "bigint";
+  public static final String INTEGER = "integer";
+  public static final String SMALLINT = "smallint";
+  public static final String TINYINT = "tinyint";
+  public static final String BOOLEAN = "boolean";
+  public static final String DATE = "date";
+  public static final String DECIMAL = "decimal";
+  public static final String REAL = "real";
+  public static final String DOUBLE = "double";
+  public static final String HYPER_LOG_LOG = "HyperLogLog";
+  public static final String QDIGEST = "qdigest";
+  public static final String TDIGEST = "tdigest";
+  public static final String P4_HYPER_LOG_LOG = "P4HyperLogLog";
+  public static final String INTERVAL_DAY_TO_SECOND = "interval day to second";
+  public static final String INTERVAL_YEAR_TO_MONTH = "interval year to month";
+  public static final String TIMESTAMP = "timestamp";
+  public static final String TIMESTAMP_WITH_TIME_ZONE = "timestamp with time zone";
+  public static final String TIME = "time";
+  public static final String TIME_WITH_TIME_ZONE = "time with time zone";
+  public static final String VARBINARY = "varbinary";
+  public static final String VARCHAR = "varchar";
+  public static final String CHAR = "char";
+  public static final String ROW = "row";
+  public static final String ARRAY = "array";
+  public static final String MAP = "map";
+  public static final String JSON = "json";
+  public static final String JSON_2016 = "json2016";
+  public static final String IPADDRESS = "ipaddress";
+  public static final String GEOMETRY = "Geometry";
+  public static final String UUID = "uuid";
+
+  private StandardTypes() {}
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignature.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignature.java
new file mode 100644
index 00000000000..f6977d7a6a0
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignature.java
@@ -0,0 +1,207 @@
+/*
+ * 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.type;
+
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.errorprone.annotations.Immutable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+import static org.apache.iotdb.db.queryengine.plan.relational.type.StandardTypes.TIME_WITH_TIME_ZONE;
+import static org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignatureParameter.typeParameter;
+
+@Immutable
+public class TypeSignature {
+  private static final String TIMESTAMP_WITH_TIME_ZONE = "timestamp with time zone";
+  private static final String TIMESTAMP_WITHOUT_TIME_ZONE = "timestamp without time zone";
+
+  private final String base;
+  private final List<TypeSignatureParameter> parameters;
+  private final boolean calculated;
+
+  private int hashCode;
+
+  public TypeSignature(String base, TypeSignatureParameter... parameters) {
+    this(base, asList(parameters));
+  }
+
+  public TypeSignature(String base, List<TypeSignatureParameter> parameters) {
+    checkArgument(base != null, "base is null");
+    this.base = base;
+    checkArgument(!base.isEmpty(), "base is empty");
+    checkArgument(validateName(base), "Bad characters in base type: %s", base);
+    checkArgument(parameters != null, "parameters is null");
+    this.parameters = new ArrayList<>(parameters);
+
+    this.calculated = parameters.stream().anyMatch(TypeSignatureParameter::isCalculated);
+  }
+
+  public String getBase() {
+    return base;
+  }
+
+  public List<TypeSignatureParameter> getParameters() {
+    return parameters;
+  }
+
+  public List<TypeSignature> getTypeParametersAsTypeSignatures() {
+    List<TypeSignature> result = new ArrayList<>();
+    for (TypeSignatureParameter parameter : parameters) {
+      if (parameter.getKind() != ParameterKind.TYPE) {
+        throw new IllegalStateException(
+            format("Expected all parameters to be TypeSignatures but [%s] was found", parameter));
+      }
+      result.add(parameter.getTypeSignature());
+    }
+    return result;
+  }
+
+  public boolean isCalculated() {
+    return calculated;
+  }
+
+  @Override
+  public String toString() {
+    return formatValue(false);
+  }
+
+  public String jsonValue() {
+    return formatValue(true);
+  }
+
+  private String formatValue(boolean json) {
+    if (parameters.isEmpty()) {
+      return base;
+    }
+
+    //    if (base.equalsIgnoreCase(StandardTypes.VARCHAR) &&
+    //        (parameters.size() == 1) &&
+    //        parameters.get(0).isLongLiteral() &&
+    //        parameters.get(0).getLongLiteral() == VarcharType.UNBOUNDED_LENGTH) {
+    //      return base;
+    //    }
+
+    // TODO: this is somewhat of a hack. We need to evolve TypeSignature to be more "structural" for
+    // the special types, similar to DataType from the AST.
+    //   In fact. TypeSignature should become the IR counterpart to DataType from the AST.
+    if (base.equalsIgnoreCase(TIMESTAMP_WITH_TIME_ZONE)) {
+      return format("timestamp(%s) with time zone", parameters.get(0));
+    }
+
+    if (base.equalsIgnoreCase(TIMESTAMP_WITHOUT_TIME_ZONE)) {
+      return format("timestamp(%s) without time zone", parameters.get(0));
+    }
+
+    if (base.equalsIgnoreCase(TIME_WITH_TIME_ZONE)) {
+      return format("time(%s) with time zone", parameters.get(0));
+    }
+
+    StringBuilder typeName = new StringBuilder(base);
+    typeName
+        .append("(")
+        .append(json ? parameters.get(0).jsonValue() : parameters.get(0).toString());
+    for (int i = 1; i < parameters.size(); i++) {
+      typeName
+          .append(",")
+          .append(json ? parameters.get(i).jsonValue() : parameters.get(i).toString());
+    }
+    typeName.append(")");
+    return typeName.toString();
+  }
+
+  @FormatMethod
+  private static void checkArgument(boolean argument, String format, Object... args) {
+    if (!argument) {
+      throw new IllegalArgumentException(format(format, args));
+    }
+  }
+
+  private static boolean validateName(String name) {
+    return name.chars().noneMatch(c -> c == '<' || c == '>' || c == ',');
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    TypeSignature other = (TypeSignature) o;
+
+    return Objects.equals(
+            this.base.toLowerCase(Locale.ENGLISH), other.base.toLowerCase(Locale.ENGLISH))
+        && Objects.equals(this.parameters, other.parameters);
+  }
+
+  @Override
+  public int hashCode() {
+    int hash = hashCode;
+    if (hash == 0) {
+      hash = Objects.hash(base.toLowerCase(Locale.ENGLISH), parameters);
+      if (hash == 0) {
+        hash = 1;
+      }
+      hashCode = hash;
+    }
+
+    return hash;
+  }
+
+  // Type signature constructors for common types
+
+  public static TypeSignature parametricType(String name, TypeSignature... parameters) {
+    return new TypeSignature(
+        name,
+        Arrays.stream(parameters)
+            .map(TypeSignatureParameter::typeParameter)
+            .collect(Collectors.toList()));
+  }
+
+  public static TypeSignature functionType(TypeSignature first, TypeSignature... rest) {
+    List<TypeSignatureParameter> parameters = new ArrayList<>();
+    parameters.add(typeParameter(first));
+
+    Arrays.stream(rest).map(TypeSignatureParameter::typeParameter).forEach(parameters::add);
+
+    return new TypeSignature("function", parameters);
+  }
+
+  public static TypeSignature rowType(TypeSignatureParameter... fields) {
+    return rowType(Arrays.asList(fields));
+  }
+
+  public static TypeSignature rowType(List<TypeSignatureParameter> fields) {
+    checkArgument(
+        fields.stream().allMatch(parameter -> parameter.getKind() == ParameterKind.NAMED_TYPE),
+        "Parameters for ROW type must be NAMED_TYPE parameters");
+
+    return new TypeSignature(StandardTypes.ROW, fields);
+  }
+}
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignatureParameter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignatureParameter.java
new file mode 100644
index 00000000000..9f12515e874
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/type/TypeSignatureParameter.java
@@ -0,0 +1,175 @@
+/*
+ * 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.type;
+
+import com.google.errorprone.annotations.Immutable;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public final class TypeSignatureParameter {
+  private final ParameterKind kind;
+  private final Object value;
+
+  public static TypeSignatureParameter typeParameter(TypeSignature typeSignature) {
+    return new TypeSignatureParameter(ParameterKind.TYPE, typeSignature);
+  }
+
+  public static TypeSignatureParameter numericParameter(long longLiteral) {
+    return new TypeSignatureParameter(ParameterKind.LONG, longLiteral);
+  }
+
+  public static TypeSignatureParameter namedTypeParameter(NamedTypeSignature namedTypeSignature) {
+    return new TypeSignatureParameter(ParameterKind.NAMED_TYPE, namedTypeSignature);
+  }
+
+  public static TypeSignatureParameter namedField(String name, TypeSignature type) {
+    return new TypeSignatureParameter(
+        ParameterKind.NAMED_TYPE,
+        new NamedTypeSignature(Optional.of(new RowFieldName(name)), type));
+  }
+
+  public static TypeSignatureParameter anonymousField(TypeSignature type) {
+    return new TypeSignatureParameter(
+        ParameterKind.NAMED_TYPE, new NamedTypeSignature(Optional.empty(), type));
+  }
+
+  public static TypeSignatureParameter typeVariable(String variable) {
+    return new TypeSignatureParameter(ParameterKind.VARIABLE, variable);
+  }
+
+  private TypeSignatureParameter(ParameterKind kind, Object value) {
+    this.kind = requireNonNull(kind, "kind is null");
+    this.value = requireNonNull(value, "value is null");
+  }
+
+  @Override
+  public String toString() {
+    return value.toString();
+  }
+
+  public String jsonValue() {
+    String prefix = "";
+    if (kind == ParameterKind.VARIABLE) {
+      prefix = "@";
+    }
+
+    String valueJson;
+    if (value instanceof TypeSignature) {
+      TypeSignature typeSignature = (TypeSignature) value;
+      valueJson = typeSignature.jsonValue();
+    } else {
+      valueJson = value.toString();
+    }
+    return prefix + valueJson;
+  }
+
+  public ParameterKind getKind() {
+    return kind;
+  }
+
+  public boolean isTypeSignature() {
+    return kind == ParameterKind.TYPE;
+  }
+
+  public boolean isLongLiteral() {
+    return kind == ParameterKind.LONG;
+  }
+
+  public boolean isNamedTypeSignature() {
+    return kind == ParameterKind.NAMED_TYPE;
+  }
+
+  public boolean isVariable() {
+    return kind == ParameterKind.VARIABLE;
+  }
+
+  private <A> A getValue(ParameterKind expectedParameterKind, Class<A> target) {
+    if (kind != expectedParameterKind) {
+      throw new IllegalArgumentException(
+          format("ParameterKind is [%s] but expected [%s]", kind, expectedParameterKind));
+    }
+    return target.cast(value);
+  }
+
+  public TypeSignature getTypeSignature() {
+    return getValue(ParameterKind.TYPE, TypeSignature.class);
+  }
+
+  public Long getLongLiteral() {
+    return getValue(ParameterKind.LONG, Long.class);
+  }
+
+  public NamedTypeSignature getNamedTypeSignature() {
+    return getValue(ParameterKind.NAMED_TYPE, NamedTypeSignature.class);
+  }
+
+  public String getVariable() {
+    return getValue(ParameterKind.VARIABLE, String.class);
+  }
+
+  public Optional<TypeSignature> getTypeSignatureOrNamedTypeSignature() {
+    switch (kind) {
+      case TYPE:
+        return Optional.of(getTypeSignature());
+      case NAMED_TYPE:
+        return Optional.of(getNamedTypeSignature().getTypeSignature());
+      default:
+        return Optional.empty();
+    }
+  }
+
+  public boolean isCalculated() {
+    switch (kind) {
+      case TYPE:
+        return getTypeSignature().isCalculated();
+      case NAMED_TYPE:
+        return getNamedTypeSignature().getTypeSignature().isCalculated();
+      case LONG:
+        return false;
+      case VARIABLE:
+        return true;
+    }
+    throw new IllegalArgumentException("Unexpected parameter kind: " + kind);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    TypeSignatureParameter other = (TypeSignatureParameter) o;
+
+    return this.kind == other.kind && Objects.equals(this.value, other.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(kind, value);
+  }
+}
diff --git a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/StackableAstVisitor.java b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/StackableAstVisitor.java
new file mode 100644
index 00000000000..d4a7509a675
--- /dev/null
+++ b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/StackableAstVisitor.java
@@ -0,0 +1,65 @@
+/*
+ * 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.relational.sql.tree;
+
+import java.util.LinkedList;
+import java.util.Optional;
+
+public class StackableAstVisitor<R, C>
+    extends AstVisitor<R, StackableAstVisitor.StackableAstVisitorContext<C>> {
+
+  @Override
+  public R process(Node node, StackableAstVisitorContext<C> context) {
+    context.push(node);
+    try {
+      return node.accept(this, context);
+    } finally {
+      context.pop();
+    }
+  }
+
+  public static class StackableAstVisitorContext<C> {
+    private final LinkedList<Node> stack = new LinkedList<>();
+    private final C context;
+
+    public StackableAstVisitorContext(C context) {
+      this.context = context;
+    }
+
+    public C getContext() {
+      return context;
+    }
+
+    private void pop() {
+      stack.pop();
+    }
+
+    void push(Node node) {
+      stack.push(node);
+    }
+
+    public Optional<Node> getPreviousNode() {
+      if (stack.size() > 1) {
+        return Optional.of(stack.get(1));
+      }
+      return Optional.empty();
+    }
+  }
+}
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/RowType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/RowType.java
index ad8b003169a..de47824a6d1 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/RowType.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/RowType.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
@@ -144,4 +145,24 @@ public class RowType implements Type {
   public boolean isOrderable() {
     return orderable;
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    RowType rowType = (RowType) o;
+    return comparable == rowType.comparable
+        && orderable == rowType.orderable
+        && Objects.equals(fields, rowType.fields)
+        && Objects.equals(fieldTypes, rowType.fieldTypes);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(fields, fieldTypes, comparable, orderable);
+  }
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java
index eeacbb14817..ed3e7b99fe5 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeEnum.java
@@ -32,5 +32,7 @@ public enum TypeEnum {
 
   BINARY,
 
-  ROW
+  ROW,
+
+  UNKNOWN
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeFactory.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeFactory.java
index ed52a9fef95..7a82fdb8ae2 100644
--- a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeFactory.java
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/TypeFactory.java
@@ -46,4 +46,26 @@ public class TypeFactory {
             String.format("Invalid TSDataType for TypeFactory: %s", tsDataType));
     }
   }
+
+  public static Type getType(TypeEnum typeEnum) {
+    switch (typeEnum) {
+      case INT32:
+        return IntType.getInstance();
+      case INT64:
+        return LongType.getInstance();
+      case FLOAT:
+        return FloatType.getInstance();
+      case DOUBLE:
+        return DoubleType.getInstance();
+      case BOOLEAN:
+        return BooleanType.getInstance();
+      case BINARY:
+        return BinaryType.getInstance();
+      case UNKNOWN:
+        return UnknownType.getInstance();
+      default:
+        throw new UnsupportedOperationException(
+            String.format("Invalid TypeEnum for TypeFactory: %s", typeEnum));
+    }
+  }
 }
diff --git a/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/UnknownType.java b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/UnknownType.java
new file mode 100644
index 00000000000..0c31d07ddb3
--- /dev/null
+++ b/iotdb-core/tsfile/src/main/java/org/apache/iotdb/tsfile/read/common/type/UnknownType.java
@@ -0,0 +1,87 @@
+/*
+ * 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.tsfile.read.common.type;
+
+import org.apache.iotdb.tsfile.read.common.block.column.BooleanColumnBuilder;
+import org.apache.iotdb.tsfile.read.common.block.column.Column;
+import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.apache.iotdb.tsfile.utils.Preconditions.checkArgument;
+
+public class UnknownType implements Type {
+  public static final UnknownType UNKNOWN = new UnknownType();
+  public static final String NAME = "unknown";
+
+  private UnknownType() {}
+
+  @Override
+  public void writeBoolean(ColumnBuilder columnBuilder, boolean value) {
+    // Ideally, this function should never be invoked for the unknown type.
+    // However, some logic (e.g. AbstractMinMaxBy) relies on writing a default value before the null
+    // check.
+    checkArgument(!value);
+    columnBuilder.appendNull();
+  }
+
+  @Override
+  public boolean getBoolean(Column column, int position) {
+    // Ideally, this function should never be invoked for the unknown type.
+    // However, some logic relies on having a default value before the null check.
+    checkArgument(column.isNull(position));
+    return false;
+  }
+
+  @Override
+  public ColumnBuilder createColumnBuilder(int expectedEntries) {
+    return new BooleanColumnBuilder(null, expectedEntries);
+  }
+
+  @Override
+  public TypeEnum getTypeEnum() {
+    return TypeEnum.UNKNOWN;
+  }
+
+  @Override
+  public String getDisplayName() {
+    return NAME;
+  }
+
+  @Override
+  public boolean isComparable() {
+    return true;
+  }
+
+  @Override
+  public boolean isOrderable() {
+    return true;
+  }
+
+  @Override
+  public List<Type> getTypeParameters() {
+    return Collections.emptyList();
+  }
+
+  public static UnknownType getInstance() {
+    return UNKNOWN;
+  }
+}


(iotdb) 02/02: finish analyze

Posted by ja...@apache.org.
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 4676d100dbf054d7ad65e459e9521703c0fa21ab
Author: JackieTien97 <ja...@gmail.com>
AuthorDate: Thu Feb 29 10:25:32 2024 +0800

    finish analyze
---
 .../iotdb/db/queryengine/common/SessionInfo.java   |  7 +-
 .../plan/relational/analyzer/Analyzer.java         | 59 +++++++++++++++-
 .../relational/analyzer/ExpressionAnalyzer.java    | 24 +++++--
 .../relational/analyzer/StatementAnalyzer.java     | 16 +++--
 .../analyzer/StatementAnalyzerFactory.java         | 60 ++++++++++++++++
 .../plan/relational/analyzer/AnalyzerTest.java     | 79 ++++++++++++++++++++++
 6 files changed, 227 insertions(+), 18 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 a4a28dd2f38..4ec877d1886 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
@@ -55,16 +55,11 @@ public class SessionInfo {
   }
 
   public SessionInfo(
-      long sessionId,
-      String userName,
-      String zoneId,
-      @Nullable String databaseName,
-      ClientVersion version) {
+      long sessionId, String userName, String zoneId, @Nullable String databaseName) {
     this.sessionId = sessionId;
     this.userName = userName;
     this.zoneId = zoneId;
     this.databaseName = databaseName;
-    this.version = version;
   }
 
   public long getSessionId() {
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analyzer.java
index 909b5b7b1d4..741f49017d4 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analyzer.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/Analyzer.java
@@ -19,4 +19,61 @@
 
 package org.apache.iotdb.db.queryengine.plan.relational.analyzer;
 
-public class Analyzer {}
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.queryengine.execution.warnings.WarningCollector;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.Statement;
+
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+
+public class Analyzer {
+
+  private final StatementAnalyzerFactory statementAnalyzerFactory;
+
+  private final SessionInfo session;
+  private final List<Expression> parameters;
+
+  private final Map<NodeRef<Parameter>, Expression> parameterLookup;
+
+  private final WarningCollector warningCollector;
+
+  Analyzer(
+      SessionInfo session,
+      StatementAnalyzerFactory statementAnalyzerFactory,
+      List<Expression> parameters,
+      Map<NodeRef<Parameter>, Expression> parameterLookup,
+      WarningCollector warningCollector) {
+    this.session = requireNonNull(session, "session is null");
+    this.statementAnalyzerFactory =
+        requireNonNull(statementAnalyzerFactory, "statementAnalyzerFactory is null");
+    this.parameters = parameters;
+    this.parameterLookup = parameterLookup;
+    this.warningCollector = requireNonNull(warningCollector, "warningCollector is null");
+  }
+
+  public Analysis analyze(Statement statement) {
+    Analysis analysis = new Analysis(statement, parameterLookup);
+    StatementAnalyzer analyzer =
+        statementAnalyzerFactory.createStatementAnalyzer(
+            analysis, session, warningCollector, CorrelationSupport.ALLOWED);
+
+    analyzer.analyze(statement);
+
+    // TODO access control
+    // check column access permissions for each table
+    //    analysis.getTableColumnReferences().forEach((accessControlInfo, tableColumnReferences) ->
+    //        tableColumnReferences.forEach((tableName, columns) ->
+    //            accessControlInfo.getAccessControl().checkCanSelectFromColumns(
+    //                accessControlInfo.getSecurityContext(session.getRequiredTransactionId(),
+    // session.getQueryId(),
+    //                    session.getStart()),
+    //                tableName,
+    //                columns)));
+
+    return analysis;
+  }
+}
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 9454fa1d1ed..64bd4a98952 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
@@ -148,6 +148,7 @@ public class ExpressionAnalyzer {
   private ExpressionAnalyzer(
       Metadata metadata,
       AccessControl accessControl,
+      StatementAnalyzerFactory statementAnalyzerFactory,
       Analysis analysis,
       SessionInfo session,
       TypeProvider types,
@@ -156,8 +157,8 @@ public class ExpressionAnalyzer {
         metadata,
         accessControl,
         (node, correlationSupport) ->
-            new StatementAnalyzer(
-                analysis, accessControl, warningCollector, session, metadata, correlationSupport),
+            statementAnalyzerFactory.createStatementAnalyzer(
+                analysis, session, warningCollector, correlationSupport),
         session,
         types,
         analysis.getParameters(),
@@ -1394,6 +1395,7 @@ public class ExpressionAnalyzer {
   public static ExpressionAnalysis analyzeExpressions(
       Metadata metadata,
       SessionInfo session,
+      StatementAnalyzerFactory statementAnalyzerFactory,
       AccessControl accessControl,
       TypeProvider types,
       Iterable<Expression> expressions,
@@ -1401,7 +1403,14 @@ public class ExpressionAnalyzer {
       WarningCollector warningCollector) {
     Analysis analysis = new Analysis(null, parameters);
     ExpressionAnalyzer analyzer =
-        new ExpressionAnalyzer(metadata, accessControl, analysis, session, types, warningCollector);
+        new ExpressionAnalyzer(
+            metadata,
+            accessControl,
+            statementAnalyzerFactory,
+            analysis,
+            session,
+            types,
+            warningCollector);
     for (Expression expression : expressions) {
       analyzer.analyze(
           expression,
@@ -1420,6 +1429,7 @@ public class ExpressionAnalyzer {
   public static ExpressionAnalysis analyzeExpression(
       Metadata metadata,
       SessionInfo session,
+      StatementAnalyzerFactory statementAnalyzerFactory,
       AccessControl accessControl,
       Scope scope,
       Analysis analysis,
@@ -1428,7 +1438,13 @@ public class ExpressionAnalyzer {
       CorrelationSupport correlationSupport) {
     ExpressionAnalyzer analyzer =
         new ExpressionAnalyzer(
-            metadata, accessControl, analysis, session, TypeProvider.empty(), warningCollector);
+            metadata,
+            accessControl,
+            statementAnalyzerFactory,
+            analysis,
+            session,
+            TypeProvider.empty(),
+            warningCollector);
     analyzer.analyze(expression, scope, correlationSupport);
 
     updateAnalysis(analysis, analyzer, session, accessControl);
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 5b465e02be7..2f98a73d3f2 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
@@ -153,6 +153,8 @@ import static org.apache.iotdb.tsfile.read.common.type.BooleanType.BOOLEAN;
 
 public class StatementAnalyzer {
 
+  private final StatementAnalyzerFactory statementAnalyzerFactory;
+
   private final Analysis analysis;
 
   private final AccessControl accessControl;
@@ -166,12 +168,14 @@ public class StatementAnalyzer {
   private final CorrelationSupport correlationSupport;
 
   public StatementAnalyzer(
+      StatementAnalyzerFactory statementAnalyzerFactory,
       Analysis analysis,
       AccessControl accessControl,
       WarningCollector warningCollector,
       SessionInfo sessionContext,
       Metadata metadata,
       CorrelationSupport correlationSupport) {
+    this.statementAnalyzerFactory = statementAnalyzerFactory;
     this.analysis = analysis;
     this.accessControl = accessControl;
     this.warningCollector = warningCollector;
@@ -633,13 +637,8 @@ public class StatementAnalyzer {
     @Override
     protected Scope visitTableSubquery(TableSubquery node, Optional<Scope> scope) {
       StatementAnalyzer analyzer =
-          new StatementAnalyzer(
-              analysis,
-              accessControl,
-              warningCollector,
-              sessionContext,
-              metadata,
-              CorrelationSupport.ALLOWED);
+          statementAnalyzerFactory.createStatementAnalyzer(
+              analysis, sessionContext, warningCollector, CorrelationSupport.ALLOWED);
       Scope queryScope =
           analyzer.analyze(
               node.getQuery(),
@@ -1913,6 +1912,7 @@ public class StatementAnalyzer {
             ExpressionAnalyzer.analyzeExpression(
                 metadata,
                 sessionContext,
+                statementAnalyzerFactory,
                 accessControl,
                 orderByScope,
                 analysis,
@@ -2099,6 +2099,7 @@ public class StatementAnalyzer {
       return ExpressionAnalyzer.analyzeExpression(
           metadata,
           sessionContext,
+          statementAnalyzerFactory,
           accessControl,
           scope,
           analysis,
@@ -2112,6 +2113,7 @@ public class StatementAnalyzer {
       return ExpressionAnalyzer.analyzeExpression(
           metadata,
           sessionContext,
+          statementAnalyzerFactory,
           accessControl,
           scope,
           analysis,
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzerFactory.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzerFactory.java
new file mode 100644
index 00000000000..4179e19cf33
--- /dev/null
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzerFactory.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.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.queryengine.execution.warnings.WarningCollector;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+import org.apache.iotdb.db.relational.sql.parser.SqlParser;
+
+import static java.util.Objects.requireNonNull;
+
+public class StatementAnalyzerFactory {
+
+  private final Metadata metadata;
+  private final SqlParser sqlParser;
+  private final AccessControl accessControl;
+
+  public StatementAnalyzerFactory(
+      Metadata metadata, SqlParser sqlParser, AccessControl accessControl) {
+    this.metadata = requireNonNull(metadata, "plannerContext is null");
+    this.sqlParser = requireNonNull(sqlParser, "sqlParser is null");
+    this.accessControl = requireNonNull(accessControl, "accessControl is null");
+  }
+
+  public StatementAnalyzerFactory withSpecializedAccessControl(AccessControl accessControl) {
+    return new StatementAnalyzerFactory(metadata, sqlParser, accessControl);
+  }
+
+  public StatementAnalyzer createStatementAnalyzer(
+      Analysis analysis,
+      SessionInfo session,
+      WarningCollector warningCollector,
+      CorrelationSupport correlationSupport) {
+    return new StatementAnalyzer(
+        this, analysis, accessControl, warningCollector, session, metadata, correlationSupport);
+  }
+
+  public static StatementAnalyzerFactory createTestingStatementAnalyzerFactory(
+      Metadata metadata, AccessControl accessControl) {
+    return new StatementAnalyzerFactory(metadata, new SqlParser(), accessControl);
+  }
+}
diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
new file mode 100644
index 00000000000..3feb40ada8d
--- /dev/null
+++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AnalyzerTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+import org.apache.iotdb.db.relational.sql.parser.SqlParser;
+import org.apache.iotdb.db.relational.sql.tree.Statement;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.time.ZoneId;
+import java.util.Collections;
+
+import static org.apache.iotdb.db.queryengine.execution.warnings.WarningCollector.NOOP;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+public class AnalyzerTest {
+
+  private final SqlParser sqlParser = new SqlParser();
+
+  private final NopAccessControl nopAccessControl = new NopAccessControl();
+
+  @Test
+  public void testRawDataQuery() {
+    String sql = "select s1, status, s1 + 1 as t from table1 where time > 100 and s2 > 10;";
+    Metadata metadata = Mockito.mock(Metadata.class);
+    Mockito.when(metadata.tableExists(Mockito.any())).thenReturn(true);
+
+    Analysis actualAnalysis = analyzeSQL(sql, metadata);
+    assertNotNull(actualAnalysis);
+    System.out.println(actualAnalysis.getTypes());
+  }
+
+  private Analysis analyzeSQL(String sql, Metadata metadata) {
+    try {
+      Statement statement = sqlParser.createStatement(sql);
+      SessionInfo session = new SessionInfo(0, "test", ZoneId.systemDefault().getId(), "testdb");
+      StatementAnalyzerFactory statementAnalyzerFactory =
+          new StatementAnalyzerFactory(metadata, sqlParser, nopAccessControl);
+
+      Analyzer analyzer =
+          new Analyzer(
+              session,
+              statementAnalyzerFactory,
+              Collections.emptyList(),
+              Collections.emptyMap(),
+              NOOP);
+      return analyzer.analyze(statement);
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(sql + ", " + e.getMessage());
+    }
+    fail();
+    return null;
+  }
+
+  private static class NopAccessControl implements AccessControl {}
+}