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 <[email protected]>
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;
+  }
+}


Reply via email to