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


The following commit(s) were added to refs/heads/ty/TableModelGrammar by this 
push:
     new bb518ad0e35 partial
bb518ad0e35 is described below

commit bb518ad0e35377bda3f9c05f51543a0923de4687
Author: JackieTien97 <[email protected]>
AuthorDate: Thu Feb 22 10:55:20 2024 +0800

    partial
---
 .../db/relational/grammar/sql/RelationalSql.g4     |   40 +-
 iotdb-core/relational-parser/pom.xml               |   13 +
 .../relational/sql/parser/AntlrATNCacheFields.java |   70 ++
 .../iotdb/db/relational/sql/parser/AstBuilder.java | 1219 +++++++++++++++-----
 .../db/relational/sql/parser/ErrorHandler.java     |  438 +++++++
 .../RefreshableSqlBaseParserInitializer.java       |   52 +
 .../iotdb/db/relational/sql/parser/SqlParser.java  |   22 +
 .../iotdb/db/relational/sql/tree/AddColumn.java    |   20 +-
 .../iotdb/db/relational/sql/tree/AllColumns.java   |    6 +
 .../iotdb/db/relational/sql/tree/AstVisitor.java   |   16 +
 .../iotdb/db/relational/sql/tree/CreateIndex.java  |  110 ++
 .../iotdb/db/relational/sql/tree/DropDB.java       |   16 +-
 .../sql/tree/{DropDB.java => DropIndex.java}       |   44 +-
 .../iotdb/db/relational/sql/tree/Identifier.java   |    8 +
 .../db/relational/sql/tree/LikePredicate.java      |    8 +
 .../tree/{DropDB.java => NullIfExpression.java}    |   50 +-
 .../sql/tree/{DropDB.java => ShowIndex.java}       |   40 +-
 17 files changed, 1732 insertions(+), 440 deletions(-)

diff --git 
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
 
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
index 3e4d84f7498..d256df02c40 100644
--- 
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
+++ 
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
@@ -130,14 +130,7 @@ charsetDesc
     ;
 
 columnDefinition
-    : identifier type columnCategory? charsetName?
-    ;
-
-columnCategory
-    : ID
-    | ATTRIBUTE
-    | TIME
-    | MEASUREMENT
+    : identifier type (columnCategory=(ID | ATTRIBUTE | TIME | MEASUREMENT))? 
charsetName?
     ;
 
 charsetName
@@ -413,7 +406,7 @@ querySpecification
     ;
 
 groupBy
-    : groupingElement (',' groupingElement)*
+    : setQuantifier? groupingElement (',' groupingElement)*
     ;
 
 groupingElement
@@ -586,11 +579,6 @@ identifierOrString
     | string
     ;
 
-timeZoneSpecifier
-    : TIME ZONE interval  #timeZoneInterval
-    | TIME ZONE string    #timeZoneString
-    ;
-
 comparisonOperator
     : EQ | NEQ | LT | LTE | GT | GTE
     ;
@@ -627,30 +615,6 @@ whenClause
     : WHEN condition=expression THEN result=expression
     ;
 
-rowPattern
-    : patternPrimary patternQuantifier?                 #quantifiedPrimary
-    | rowPattern rowPattern                             #patternConcatenation
-    | rowPattern '|' rowPattern                         #patternAlternation
-    ;
-
-patternPrimary
-    : identifier                                        #patternVariable
-    | '(' ')'                                           #emptyPattern
-    | PERMUTE '(' rowPattern (',' rowPattern)* ')'      #patternPermutation
-    | '(' rowPattern ')'                                #groupedPattern
-    | '^'                                               #partitionStartAnchor
-    | '$'                                               #partitionEndAnchor
-    | '{-' rowPattern '-}'                              #excludedPattern
-    ;
-
-patternQuantifier
-    : ASTERISK (reluctant=QUESTION_MARK)?                                      
                 #zeroOrMoreQuantifier
-    | PLUS (reluctant=QUESTION_MARK)?                                          
                 #oneOrMoreQuantifier
-    | QUESTION_MARK (reluctant=QUESTION_MARK)?                                 
                 #zeroOrOneQuantifier
-    | '{' exactly=INTEGER_VALUE '}' (reluctant=QUESTION_MARK)?                 
                 #rangeQuantifier
-    | '{' (atLeast=INTEGER_VALUE)? ',' (atMost=INTEGER_VALUE)? '}' 
(reluctant=QUESTION_MARK)?   #rangeQuantifier
-    ;
-
 updateAssignment
     : identifier EQ expression
     ;
diff --git a/iotdb-core/relational-parser/pom.xml 
b/iotdb-core/relational-parser/pom.xml
index 4246a6b7450..61e2ff56550 100644
--- a/iotdb-core/relational-parser/pom.xml
+++ b/iotdb-core/relational-parser/pom.xml
@@ -38,14 +38,27 @@
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.google.errorprone</groupId>
+            <artifactId>error_prone_annotations</artifactId>
+            <optional>true</optional>
+        </dependency>
         <dependency>
             <groupId>org.antlr</groupId>
             <artifactId>antlr4-runtime</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.iotdb</groupId>
             <artifactId>iotdb-relational-grammar</artifactId>
             <version>1.3.1-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.spark</groupId>
+            <artifactId>spark-catalyst_2.11</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/AntlrATNCacheFields.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/AntlrATNCacheFields.java
new file mode 100644
index 00000000000..6328ff7a290
--- /dev/null
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/AntlrATNCacheFields.java
@@ -0,0 +1,70 @@
+/*
+ * 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.parser;
+
+import org.antlr.v4.runtime.Lexer;
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.atn.ATN;
+import org.antlr.v4.runtime.atn.LexerATNSimulator;
+import org.antlr.v4.runtime.atn.ParserATNSimulator;
+import org.antlr.v4.runtime.atn.PredictionContextCache;
+import org.antlr.v4.runtime.dfa.DFA;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public class AntlrATNCacheFields {
+  private final ATN atn;
+  private final PredictionContextCache predictionContextCache;
+  private final DFA[] decisionToDFA;
+
+  public AntlrATNCacheFields(ATN atn) {
+    this.atn = requireNonNull(atn, "atn is null");
+    this.predictionContextCache = new PredictionContextCache();
+    this.decisionToDFA = createDecisionToDFA(atn);
+  }
+
+  @SuppressWarnings("ObjectEquality")
+  public void configureLexer(Lexer lexer) {
+    requireNonNull(lexer, "lexer is null");
+    // Intentional identity equals comparison
+    checkArgument(
+        atn == lexer.getATN(), "Lexer ATN mismatch: expected %s, found %s", 
atn, lexer.getATN());
+    lexer.setInterpreter(new LexerATNSimulator(lexer, atn, decisionToDFA, 
predictionContextCache));
+  }
+
+  @SuppressWarnings("ObjectEquality")
+  public void configureParser(Parser parser) {
+    requireNonNull(parser, "parser is null");
+    // Intentional identity equals comparison
+    checkArgument(
+        atn == parser.getATN(), "Parser ATN mismatch: expected %s, found %s", 
atn, parser.getATN());
+    parser.setInterpreter(
+        new ParserATNSimulator(parser, atn, decisionToDFA, 
predictionContextCache));
+  }
+
+  private static DFA[] createDecisionToDFA(ATN atn) {
+    DFA[] decisionToDFA = new DFA[atn.getNumberOfDecisions()];
+    for (int i = 0; i < decisionToDFA.length; i++) {
+      decisionToDFA[i] = new DFA(atn.getDecisionState(i), i);
+    }
+    return decisionToDFA;
+  }
+}
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/AstBuilder.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/AstBuilder.java
index cee120424e5..eeede0436b1 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/AstBuilder.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/AstBuilder.java
@@ -20,17 +20,117 @@
 package org.apache.iotdb.db.relational.sql.parser;
 
 import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlBaseVisitor;
+import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlLexer;
 import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlParser;
+import org.apache.iotdb.db.relational.sql.tree.AddColumn;
+import org.apache.iotdb.db.relational.sql.tree.AliasedRelation;
+import org.apache.iotdb.db.relational.sql.tree.AllColumns;
+import org.apache.iotdb.db.relational.sql.tree.AllRows;
+import org.apache.iotdb.db.relational.sql.tree.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.Cast;
+import org.apache.iotdb.db.relational.sql.tree.CoalesceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ColumnDefinition;
+import org.apache.iotdb.db.relational.sql.tree.ComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.CreateDB;
+import org.apache.iotdb.db.relational.sql.tree.CreateIndex;
+import org.apache.iotdb.db.relational.sql.tree.CreateTable;
+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.DataType;
+import org.apache.iotdb.db.relational.sql.tree.DataTypeParameter;
+import org.apache.iotdb.db.relational.sql.tree.Delete;
+import org.apache.iotdb.db.relational.sql.tree.DereferenceExpression;
+import org.apache.iotdb.db.relational.sql.tree.DescribeTable;
+import org.apache.iotdb.db.relational.sql.tree.DropColumn;
+import org.apache.iotdb.db.relational.sql.tree.DropDB;
+import org.apache.iotdb.db.relational.sql.tree.DropIndex;
+import org.apache.iotdb.db.relational.sql.tree.DropTable;
+import org.apache.iotdb.db.relational.sql.tree.Except;
+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.GenericDataType;
+import org.apache.iotdb.db.relational.sql.tree.GroupBy;
+import org.apache.iotdb.db.relational.sql.tree.GroupingElement;
+import org.apache.iotdb.db.relational.sql.tree.GroupingSets;
+import org.apache.iotdb.db.relational.sql.tree.Identifier;
+import org.apache.iotdb.db.relational.sql.tree.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.Insert;
+import org.apache.iotdb.db.relational.sql.tree.Intersect;
+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.Join;
+import org.apache.iotdb.db.relational.sql.tree.JoinCriteria;
+import org.apache.iotdb.db.relational.sql.tree.JoinOn;
+import org.apache.iotdb.db.relational.sql.tree.JoinUsing;
+import org.apache.iotdb.db.relational.sql.tree.LikePredicate;
+import org.apache.iotdb.db.relational.sql.tree.Limit;
+import org.apache.iotdb.db.relational.sql.tree.LongLiteral;
+import org.apache.iotdb.db.relational.sql.tree.NaturalJoin;
 import org.apache.iotdb.db.relational.sql.tree.Node;
 import org.apache.iotdb.db.relational.sql.tree.NodeLocation;
-
+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.Offset;
+import org.apache.iotdb.db.relational.sql.tree.OrderBy;
+import org.apache.iotdb.db.relational.sql.tree.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.Property;
+import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
+import org.apache.iotdb.db.relational.sql.tree.QuantifiedComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.Query;
+import org.apache.iotdb.db.relational.sql.tree.QueryBody;
+import org.apache.iotdb.db.relational.sql.tree.QuerySpecification;
+import org.apache.iotdb.db.relational.sql.tree.Relation;
+import org.apache.iotdb.db.relational.sql.tree.RenameColumn;
+import org.apache.iotdb.db.relational.sql.tree.RenameTable;
+import org.apache.iotdb.db.relational.sql.tree.SearchedCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.Select;
+import org.apache.iotdb.db.relational.sql.tree.SelectItem;
+import org.apache.iotdb.db.relational.sql.tree.SetProperties;
+import org.apache.iotdb.db.relational.sql.tree.ShowDB;
+import org.apache.iotdb.db.relational.sql.tree.ShowIndex;
+import org.apache.iotdb.db.relational.sql.tree.ShowTables;
+import org.apache.iotdb.db.relational.sql.tree.SimpleCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleGroupBy;
+import org.apache.iotdb.db.relational.sql.tree.SingleColumn;
+import org.apache.iotdb.db.relational.sql.tree.SortItem;
+import org.apache.iotdb.db.relational.sql.tree.StringLiteral;
+import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression;
+import org.apache.iotdb.db.relational.sql.tree.Table;
+import org.apache.iotdb.db.relational.sql.tree.TableSubquery;
+import org.apache.iotdb.db.relational.sql.tree.Trim;
+import org.apache.iotdb.db.relational.sql.tree.Union;
+import org.apache.iotdb.db.relational.sql.tree.Update;
+import org.apache.iotdb.db.relational.sql.tree.UpdateAssignment;
+import org.apache.iotdb.db.relational.sql.tree.Use;
+import org.apache.iotdb.db.relational.sql.tree.WhenClause;
+import org.apache.iotdb.db.relational.sql.tree.With;
+import org.apache.iotdb.db.relational.sql.tree.WithQuery;
+
+import com.google.common.collect.ImmutableList;
 import org.antlr.v4.runtime.ParserRuleContext;
 import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.TerminalNode;
 
 import javax.annotation.Nullable;
 
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
 import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
+import static org.apache.iotdb.db.relational.sql.tree.GroupingSets.Type.CUBE;
+import static 
org.apache.iotdb.db.relational.sql.tree.GroupingSets.Type.EXPLICIT;
+import static org.apache.iotdb.db.relational.sql.tree.GroupingSets.Type.ROLLUP;
 
 public class AstBuilder extends RelationalSqlBaseVisitor<Node> {
 
@@ -47,124 +147,183 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     return visit(ctx.statement());
   }
 
+  // ******************* statements **********************
   @Override
   public Node 
visitUseDatabaseStatement(RelationalSqlParser.UseDatabaseStatementContext ctx) {
-    return super.visitUseDatabaseStatement(ctx);
+    return new Use(getLocation(ctx), (Identifier) visit(ctx.database));
   }
 
   @Override
   public Node 
visitShowDatabasesStatement(RelationalSqlParser.ShowDatabasesStatementContext 
ctx) {
-    return super.visitShowDatabasesStatement(ctx);
+    return new ShowDB(getLocation(ctx));
   }
 
   @Override
   public Node 
visitCreateDbStatement(RelationalSqlParser.CreateDbStatementContext ctx) {
-    return super.visitCreateDbStatement(ctx);
-  }
+    List<Property> properties = ImmutableList.of();
+    if (ctx.properties() != null) {
+      properties = visit(ctx.properties().propertyAssignments().property(), 
Property.class);
+    }
 
-  @Override
-  public Node visitDropDbStatement(RelationalSqlParser.DropDbStatementContext 
ctx) {
-    return super.visitDropDbStatement(ctx);
+    return new CreateDB(
+        getLocation(ctx),
+        ctx.EXISTS() != null,
+        ((Identifier) visit(ctx.database)).getValue(),
+        properties);
   }
 
   @Override
-  public Node visitCharsetDesc(RelationalSqlParser.CharsetDescContext ctx) {
-    return super.visitCharsetDesc(ctx);
+  public Node visitDropDbStatement(RelationalSqlParser.DropDbStatementContext 
ctx) {
+    return new DropDB(getLocation(ctx), (Identifier) visit(ctx.database), 
ctx.EXISTS() != null);
   }
 
   @Override
   public Node 
visitCreateTableStatement(RelationalSqlParser.CreateTableStatementContext ctx) {
-    return super.visitCreateTableStatement(ctx);
+    List<Property> properties = ImmutableList.of();
+    if (ctx.properties() != null) {
+      properties = visit(ctx.properties().propertyAssignments().property(), 
Property.class);
+    }
+    return new CreateTable(
+        getLocation(ctx),
+        getQualifiedName(ctx.qualifiedName()),
+        visit(ctx.columnDefinition(), ColumnDefinition.class),
+        ctx.EXISTS() != null,
+        ctx.charsetDesc() == null
+            ? null
+            : ((Identifier) 
visit(ctx.charsetDesc().identifierOrString())).getValue(),
+        properties);
   }
 
   @Override
   public Node 
visitColumnDefinition(RelationalSqlParser.ColumnDefinitionContext ctx) {
-    return super.visitColumnDefinition(ctx);
-  }
-
-  @Override
-  public Node visitColumnCategory(RelationalSqlParser.ColumnCategoryContext 
ctx) {
-    return super.visitColumnCategory(ctx);
-  }
-
-  @Override
-  public Node visitCharsetName(RelationalSqlParser.CharsetNameContext ctx) {
-    return super.visitCharsetName(ctx);
+    return new ColumnDefinition(
+        getLocation(ctx),
+        (Identifier) visit(ctx.identifier()),
+        (DataType) visit(ctx.type()),
+        getColumnCategory(ctx.columnCategory),
+        ctx.charsetName() == null
+            ? null
+            : ((Identifier) visit(ctx.charsetName().identifier())).getValue());
   }
 
   @Override
   public Node 
visitDropTableStatement(RelationalSqlParser.DropTableStatementContext ctx) {
-    return super.visitDropTableStatement(ctx);
+    return new DropTable(
+        getLocation(ctx), getQualifiedName(ctx.qualifiedName()), ctx.EXISTS() 
!= null);
   }
 
   @Override
   public Node 
visitShowTableStatement(RelationalSqlParser.ShowTableStatementContext ctx) {
-    return super.visitShowTableStatement(ctx);
+    if (ctx.database == null) {
+      return new ShowTables(getLocation(ctx));
+    } else {
+      return new ShowTables(getLocation(ctx), (Identifier) 
visit(ctx.database));
+    }
   }
 
   @Override
   public Node 
visitDescTableStatement(RelationalSqlParser.DescTableStatementContext ctx) {
-    return super.visitDescTableStatement(ctx);
+    return new DescribeTable(getLocation(ctx), getQualifiedName(ctx.table));
   }
 
   @Override
   public Node visitRenameTable(RelationalSqlParser.RenameTableContext ctx) {
-    return super.visitRenameTable(ctx);
+    return new RenameTable(
+        getLocation(ctx), getQualifiedName(ctx.from), (Identifier) 
visit(ctx.to));
   }
 
   @Override
   public Node visitAddColumn(RelationalSqlParser.AddColumnContext ctx) {
-    return super.visitAddColumn(ctx);
+    return new AddColumn(getQualifiedName(ctx.tableName), (ColumnDefinition) 
visit(ctx.column));
   }
 
   @Override
   public Node visitRenameColumn(RelationalSqlParser.RenameColumnContext ctx) {
-    return super.visitRenameColumn(ctx);
+    return new RenameColumn(
+        getLocation(ctx),
+        getQualifiedName(ctx.tableName),
+        (Identifier) visit(ctx.from),
+        (Identifier) visit(ctx.to));
   }
 
   @Override
   public Node visitDropColumn(RelationalSqlParser.DropColumnContext ctx) {
-    return super.visitDropColumn(ctx);
+    return new DropColumn(
+        getLocation(ctx), getQualifiedName(ctx.tableName), (Identifier) 
visit(ctx.column));
   }
 
   @Override
   public Node 
visitSetTableProperties(RelationalSqlParser.SetTablePropertiesContext ctx) {
-    return super.visitSetTableProperties(ctx);
+    List<Property> properties = ImmutableList.of();
+    if (ctx.propertyAssignments() != null) {
+      properties = visit(ctx.propertyAssignments().property(), Property.class);
+    }
+    return new SetProperties(
+        getLocation(ctx),
+        SetProperties.Type.TABLE,
+        getQualifiedName(ctx.qualifiedName()),
+        properties);
   }
 
   @Override
   public Node 
visitCreateIndexStatement(RelationalSqlParser.CreateIndexStatementContext ctx) {
-    return super.visitCreateIndexStatement(ctx);
-  }
-
-  @Override
-  public Node visitIdentifierList(RelationalSqlParser.IdentifierListContext 
ctx) {
-    return super.visitIdentifierList(ctx);
+    return new CreateIndex(
+        getLocation(ctx),
+        getQualifiedName(ctx.tableName),
+        (Identifier) visit(ctx.indexName),
+        visit(ctx.identifierList().identifier(), Identifier.class));
   }
 
   @Override
   public Node 
visitDropIndexStatement(RelationalSqlParser.DropIndexStatementContext ctx) {
-    return super.visitDropIndexStatement(ctx);
+    return new DropIndex(
+        getLocation(ctx), getQualifiedName(ctx.tableName), (Identifier) 
visit(ctx.indexName));
   }
 
   @Override
   public Node 
visitShowIndexStatement(RelationalSqlParser.ShowIndexStatementContext ctx) {
-    return super.visitShowIndexStatement(ctx);
+    return new ShowIndex(getLocation(ctx), getQualifiedName(ctx.tableName));
   }
 
   @Override
   public Node visitInsertStatement(RelationalSqlParser.InsertStatementContext 
ctx) {
-    return super.visitInsertStatement(ctx);
+    if (ctx.columnAliases() != null) {
+      return new Insert(
+          new Table(getQualifiedName(ctx.tableName)),
+          visit(ctx.columnAliases().identifier(), Identifier.class),
+          (Query) visit(ctx.query()));
+    } else {
+      return new Insert(new Table(getQualifiedName(ctx.tableName)), (Query) 
visit(ctx.query()));
+    }
   }
 
   @Override
   public Node visitDeleteStatement(RelationalSqlParser.DeleteStatementContext 
ctx) {
-    return super.visitDeleteStatement(ctx);
+    if (ctx.booleanExpression() != null) {
+      return new Delete(
+          getLocation(ctx),
+          new Table(getLocation(ctx), getQualifiedName(ctx.tableName)),
+          (Expression) visit(ctx.booleanExpression()));
+    } else {
+      return new Delete(
+          getLocation(ctx), new Table(getLocation(ctx), 
getQualifiedName(ctx.tableName)));
+    }
   }
 
   @Override
   public Node visitUpdateStatement(RelationalSqlParser.UpdateStatementContext 
ctx) {
-    return super.visitUpdateStatement(ctx);
+    if (ctx.booleanExpression() != null) {
+      return new Update(
+          getLocation(ctx),
+          new Table(getLocation(ctx), getQualifiedName(ctx.qualifiedName())),
+          visit(ctx.updateAssignment(), UpdateAssignment.class),
+          (Expression) visit(ctx.booleanExpression()));
+    } else {
+      return new Update(
+          getLocation(ctx),
+          new Table(getLocation(ctx), getQualifiedName(ctx.qualifiedName())),
+          visit(ctx.updateAssignment(), UpdateAssignment.class));
+    }
   }
 
   @Override
@@ -323,74 +482,220 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     return super.visitExplainAnalyze(ctx);
   }
 
+  // ********************** query expressions ********************
   @Override
   public Node visitQuery(RelationalSqlParser.QueryContext ctx) {
-    return super.visitQuery(ctx);
+    Query body = (Query) visit(ctx.queryNoWith());
+
+    return new Query(
+        getLocation(ctx),
+        visitIfPresent(ctx.with(), With.class),
+        body.getQueryBody(),
+        body.getOrderBy(),
+        body.getOffset(),
+        body.getLimit());
   }
 
   @Override
   public Node visitWith(RelationalSqlParser.WithContext ctx) {
-    return super.visitWith(ctx);
+    return new With(
+        getLocation(ctx), ctx.RECURSIVE() != null, visit(ctx.namedQuery(), 
WithQuery.class));
   }
 
   @Override
-  public Node visitProperties(RelationalSqlParser.PropertiesContext ctx) {
-    return super.visitProperties(ctx);
+  public Node visitNamedQuery(RelationalSqlParser.NamedQueryContext ctx) {
+    if (ctx.columnAliases() != null) {
+      List<Identifier> columns = visit(ctx.columnAliases().identifier(), 
Identifier.class);
+      return new WithQuery(
+          getLocation(ctx), (Identifier) visit(ctx.name), (Query) 
visit(ctx.query()), columns);
+    } else {
+      return new WithQuery(
+          getLocation(ctx), (Identifier) visit(ctx.name), (Query) 
visit(ctx.query()));
+    }
   }
 
   @Override
-  public Node 
visitPropertyAssignments(RelationalSqlParser.PropertyAssignmentsContext ctx) {
-    return super.visitPropertyAssignments(ctx);
+  public Node visitQueryNoWith(RelationalSqlParser.QueryNoWithContext ctx) {
+    QueryBody term = (QueryBody) visit(ctx.queryTerm());
+
+    Optional<OrderBy> orderBy = Optional.empty();
+    if (ctx.ORDER() != null) {
+      orderBy =
+          Optional.of(new OrderBy(getLocation(ctx.ORDER()), 
visit(ctx.sortItem(), SortItem.class)));
+    }
+
+    Optional<Offset> offset = Optional.empty();
+    if (ctx.OFFSET() != null) {
+      Expression rowCount;
+      if (ctx.offset.INTEGER_VALUE() != null) {
+        rowCount = new LongLiteral(getLocation(ctx.offset.INTEGER_VALUE()), 
ctx.offset.getText());
+      } else {
+        rowCount = new Parameter(getLocation(ctx.offset.QUESTION_MARK()), 
parameterPosition);
+        parameterPosition++;
+      }
+      offset = Optional.of(new Offset(getLocation(ctx.OFFSET()), rowCount));
+    }
+
+    Optional<Node> limit = Optional.empty();
+    if (ctx.LIMIT() != null) {
+      if (ctx.limit == null) {
+        throw new IllegalStateException("Missing LIMIT value");
+      }
+      Expression rowCount;
+      if (ctx.limit.ALL() != null) {
+        rowCount = new AllRows(getLocation(ctx.limit.ALL()));
+      } else if (ctx.limit.rowCount().INTEGER_VALUE() != null) {
+        rowCount =
+            new LongLiteral(getLocation(ctx.limit.rowCount().INTEGER_VALUE()), 
ctx.limit.getText());
+      } else {
+        rowCount =
+            new Parameter(getLocation(ctx.limit.rowCount().QUESTION_MARK()), 
parameterPosition);
+        parameterPosition++;
+      }
+
+      limit = Optional.of(new Limit(getLocation(ctx.LIMIT()), rowCount));
+    }
+
+    if (term instanceof QuerySpecification) {
+      // When we have a simple query specification
+      // followed by order by, offset, limit or fetch,
+      // fold the order by, limit, offset or fetch clauses
+      // into the query specification (analyzer/planner
+      // expects this structure to resolve references with respect
+      // to columns defined in the query specification)
+      QuerySpecification query = (QuerySpecification) term;
+
+      return new Query(
+          getLocation(ctx),
+          Optional.empty(),
+          new QuerySpecification(
+              getLocation(ctx),
+              query.getSelect(),
+              query.getFrom(),
+              query.getWhere(),
+              query.getGroupBy(),
+              query.getHaving(),
+              orderBy,
+              offset,
+              limit),
+          Optional.empty(),
+          Optional.empty(),
+          Optional.empty());
+    }
+
+    return new Query(getLocation(ctx), Optional.empty(), term, orderBy, 
offset, limit);
   }
 
   @Override
-  public Node visitProperty(RelationalSqlParser.PropertyContext ctx) {
-    return super.visitProperty(ctx);
-  }
+  public Node 
visitQuerySpecification(RelationalSqlParser.QuerySpecificationContext ctx) {
+    Optional<Relation> from = Optional.empty();
+    List<SelectItem> selectItems = visit(ctx.selectItem(), SelectItem.class);
 
-  @Override
-  public Node 
visitDefaultPropertyValue(RelationalSqlParser.DefaultPropertyValueContext ctx) {
-    return super.visitDefaultPropertyValue(ctx);
+    List<Relation> relations = visit(ctx.relation(), Relation.class);
+    if (!relations.isEmpty()) {
+      // synthesize implicit join nodes
+      Iterator<Relation> iterator = relations.iterator();
+      Relation relation = iterator.next();
+
+      while (iterator.hasNext()) {
+        relation = new Join(getLocation(ctx), Join.Type.IMPLICIT, relation, 
iterator.next());
+      }
+
+      from = Optional.of(relation);
+    }
+
+    return new QuerySpecification(
+        getLocation(ctx),
+        new Select(getLocation(ctx.SELECT()), isDistinct(ctx.setQuantifier()), 
selectItems),
+        from,
+        visitIfPresent(ctx.where, Expression.class),
+        visitIfPresent(ctx.groupBy(), GroupBy.class),
+        visitIfPresent(ctx.having, Expression.class),
+        Optional.empty(),
+        Optional.empty(),
+        Optional.empty());
   }
 
   @Override
-  public Node 
visitNonDefaultPropertyValue(RelationalSqlParser.NonDefaultPropertyValueContext 
ctx) {
-    return super.visitNonDefaultPropertyValue(ctx);
+  public Node visitGroupBy(RelationalSqlParser.GroupByContext ctx) {
+    return new GroupBy(
+        getLocation(ctx),
+        isDistinct(ctx.setQuantifier()),
+        visit(ctx.groupingElement(), GroupingElement.class));
   }
 
   @Override
-  public Node visitQueryNoWith(RelationalSqlParser.QueryNoWithContext ctx) {
-    return super.visitQueryNoWith(ctx);
+  public Node 
visitSingleGroupingSet(RelationalSqlParser.SingleGroupingSetContext ctx) {
+    return new SimpleGroupBy(
+        getLocation(ctx), visit(ctx.groupingSet().expression(), 
Expression.class));
   }
 
   @Override
-  public Node visitLimitRowCount(RelationalSqlParser.LimitRowCountContext ctx) 
{
-    return super.visitLimitRowCount(ctx);
+  public Node visitRollup(RelationalSqlParser.RollupContext ctx) {
+    return new GroupingSets(
+        getLocation(ctx),
+        ROLLUP,
+        ctx.groupingSet().stream()
+            .map(groupingSet -> visit(groupingSet.expression(), 
Expression.class))
+            .collect(toList()));
   }
 
   @Override
-  public Node visitRowCount(RelationalSqlParser.RowCountContext ctx) {
-    return super.visitRowCount(ctx);
+  public Node visitCube(RelationalSqlParser.CubeContext ctx) {
+    return new GroupingSets(
+        getLocation(ctx),
+        CUBE,
+        ctx.groupingSet().stream()
+            .map(groupingSet -> visit(groupingSet.expression(), 
Expression.class))
+            .collect(toList()));
   }
 
   @Override
-  public Node 
visitQueryTermDefault(RelationalSqlParser.QueryTermDefaultContext ctx) {
-    return super.visitQueryTermDefault(ctx);
+  public Node 
visitMultipleGroupingSets(RelationalSqlParser.MultipleGroupingSetsContext ctx) {
+    return new GroupingSets(
+        getLocation(ctx),
+        EXPLICIT,
+        ctx.groupingSet().stream()
+            .map(groupingSet -> visit(groupingSet.expression(), 
Expression.class))
+            .collect(toList()));
   }
 
   @Override
   public Node visitSetOperation(RelationalSqlParser.SetOperationContext ctx) {
-    return super.visitSetOperation(ctx);
+    QueryBody left = (QueryBody) visit(ctx.left);
+    QueryBody right = (QueryBody) visit(ctx.right);
+
+    boolean distinct = ctx.setQuantifier() == null || 
ctx.setQuantifier().DISTINCT() != null;
+
+    switch (ctx.operator.getType()) {
+      case RelationalSqlLexer.UNION:
+        return new Union(getLocation(ctx.UNION()), ImmutableList.of(left, 
right), distinct);
+      case RelationalSqlLexer.INTERSECT:
+        return new Intersect(getLocation(ctx.INTERSECT()), 
ImmutableList.of(left, right), distinct);
+      case RelationalSqlLexer.EXCEPT:
+        return new Except(getLocation(ctx.EXCEPT()), left, right, distinct);
+      default:
+        throw new IllegalArgumentException("Unsupported set operation: " + 
ctx.operator.getText());
+    }
   }
 
   @Override
-  public Node 
visitQueryPrimaryDefault(RelationalSqlParser.QueryPrimaryDefaultContext ctx) {
-    return super.visitQueryPrimaryDefault(ctx);
+  public Node visitProperty(RelationalSqlParser.PropertyContext ctx) {
+    NodeLocation location = getLocation(ctx);
+    Identifier name = (Identifier) visit(ctx.identifier());
+    RelationalSqlParser.PropertyValueContext valueContext = 
ctx.propertyValue();
+    if (valueContext instanceof 
RelationalSqlParser.DefaultPropertyValueContext) {
+      return new Property(location, name);
+    }
+    Expression value =
+        (Expression)
+            visit(((RelationalSqlParser.NonDefaultPropertyValueContext) 
valueContext).expression());
+    return new Property(location, name, value);
   }
 
   @Override
   public Node visitTable(RelationalSqlParser.TableContext ctx) {
-    return super.visitTable(ctx);
+    return new Table(getLocation(ctx), getQualifiedName(ctx.qualifiedName()));
   }
 
   @Override
@@ -405,17 +710,15 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
 
   @Override
   public Node visitSortItem(RelationalSqlParser.SortItemContext ctx) {
-    return super.visitSortItem(ctx);
-  }
-
-  @Override
-  public Node 
visitQuerySpecification(RelationalSqlParser.QuerySpecificationContext ctx) {
-    return super.visitQuerySpecification(ctx);
-  }
-
-  @Override
-  public Node visitGroupBy(RelationalSqlParser.GroupByContext ctx) {
-    return super.visitGroupBy(ctx);
+    return new SortItem(
+        getLocation(ctx),
+        (Expression) visit(ctx.expression()),
+        Optional.ofNullable(ctx.ordering)
+            .map(AstBuilder::getOrderingType)
+            .orElse(SortItem.Ordering.ASCENDING),
+        Optional.ofNullable(ctx.nullOrdering)
+            .map(AstBuilder::getNullOrderingType)
+            .orElse(SortItem.NullOrdering.UNDEFINED));
   }
 
   @Override
@@ -443,26 +746,6 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     return super.visitCountGrouping(ctx);
   }
 
-  @Override
-  public Node 
visitSingleGroupingSet(RelationalSqlParser.SingleGroupingSetContext ctx) {
-    return super.visitSingleGroupingSet(ctx);
-  }
-
-  @Override
-  public Node visitRollup(RelationalSqlParser.RollupContext ctx) {
-    return super.visitRollup(ctx);
-  }
-
-  @Override
-  public Node visitCube(RelationalSqlParser.CubeContext ctx) {
-    return super.visitCube(ctx);
-  }
-
-  @Override
-  public Node 
visitMultipleGroupingSets(RelationalSqlParser.MultipleGroupingSetsContext ctx) {
-    return super.visitMultipleGroupingSets(ctx);
-  }
-
   @Override
   public Node 
visitLeftClosedRightOpen(RelationalSqlParser.LeftClosedRightOpenContext ctx) {
     return super.visitLeftClosedRightOpen(ctx);
@@ -493,89 +776,127 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     return super.visitKeepExpression(ctx);
   }
 
-  @Override
-  public Node visitGroupingSet(RelationalSqlParser.GroupingSetContext ctx) {
-    return super.visitGroupingSet(ctx);
-  }
-
-  @Override
-  public Node visitNamedQuery(RelationalSqlParser.NamedQueryContext ctx) {
-    return super.visitNamedQuery(ctx);
-  }
-
-  @Override
-  public Node visitSetQuantifier(RelationalSqlParser.SetQuantifierContext ctx) 
{
-    return super.visitSetQuantifier(ctx);
-  }
-
   @Override
   public Node visitSelectSingle(RelationalSqlParser.SelectSingleContext ctx) {
-    return super.visitSelectSingle(ctx);
+    if (ctx.identifier() != null) {
+      return new SingleColumn(
+          getLocation(ctx),
+          (Expression) visit(ctx.expression()),
+          (Identifier) visit(ctx.identifier()));
+    } else {
+      return new SingleColumn(getLocation(ctx), (Expression) 
visit(ctx.expression()));
+    }
   }
 
   @Override
   public Node visitSelectAll(RelationalSqlParser.SelectAllContext ctx) {
-    return super.visitSelectAll(ctx);
-  }
+    List<Identifier> aliases = ImmutableList.of();
+    if (ctx.columnAliases() != null) {
+      aliases = visit(ctx.columnAliases().identifier(), Identifier.class);
+    }
 
-  @Override
-  public Node visitRelationDefault(RelationalSqlParser.RelationDefaultContext 
ctx) {
-    return super.visitRelationDefault(ctx);
+    if (ctx.primaryExpression() != null) {
+      return new AllColumns(getLocation(ctx), (Expression) 
visit(ctx.primaryExpression()), aliases);
+    } else {
+      return new AllColumns(getLocation(ctx), aliases);
+    }
   }
 
+  // *************** from clause *****************
   @Override
   public Node visitJoinRelation(RelationalSqlParser.JoinRelationContext ctx) {
-    return super.visitJoinRelation(ctx);
+    Relation left = (Relation) visit(ctx.left);
+    Relation right;
+
+    if (ctx.CROSS() != null) {
+      right = (Relation) visit(ctx.right);
+      return new Join(getLocation(ctx), Join.Type.CROSS, left, right);
+    }
+
+    JoinCriteria criteria;
+    if (ctx.NATURAL() != null) {
+      right = (Relation) visit(ctx.right);
+      criteria = new NaturalJoin();
+    } else {
+      right = (Relation) visit(ctx.rightRelation);
+      if (ctx.joinCriteria().ON() != null) {
+        criteria = new JoinOn((Expression) 
visit(ctx.joinCriteria().booleanExpression()));
+      } else if (ctx.joinCriteria().USING() != null) {
+        criteria = new JoinUsing(visit(ctx.joinCriteria().identifier(), 
Identifier.class));
+      } else {
+        throw new IllegalArgumentException("Unsupported join criteria");
+      }
+    }
+
+    Join.Type joinType;
+    if (ctx.joinType().LEFT() != null) {
+      joinType = Join.Type.LEFT;
+    } else if (ctx.joinType().RIGHT() != null) {
+      joinType = Join.Type.RIGHT;
+    } else if (ctx.joinType().FULL() != null) {
+      joinType = Join.Type.FULL;
+    } else {
+      joinType = Join.Type.INNER;
+    }
+
+    return new Join(getLocation(ctx), joinType, left, right, criteria);
   }
 
   @Override
-  public Node visitJoinType(RelationalSqlParser.JoinTypeContext ctx) {
-    return super.visitJoinType(ctx);
-  }
+  public Node visitAliasedRelation(RelationalSqlParser.AliasedRelationContext 
ctx) {
+    Relation child = (Relation) visit(ctx.relationPrimary());
 
-  @Override
-  public Node visitJoinCriteria(RelationalSqlParser.JoinCriteriaContext ctx) {
-    return super.visitJoinCriteria(ctx);
-  }
+    if (ctx.identifier() == null) {
+      return child;
+    }
 
-  @Override
-  public Node visitAliasedRelation(RelationalSqlParser.AliasedRelationContext 
ctx) {
-    return super.visitAliasedRelation(ctx);
-  }
+    List<Identifier> aliases = null;
+    if (ctx.columnAliases() != null) {
+      aliases = visit(ctx.columnAliases().identifier(), Identifier.class);
+    }
 
-  @Override
-  public Node visitColumnAliases(RelationalSqlParser.ColumnAliasesContext ctx) 
{
-    return super.visitColumnAliases(ctx);
+    return new AliasedRelation(
+        getLocation(ctx), child, (Identifier) visit(ctx.identifier()), 
aliases);
   }
 
   @Override
   public Node visitTableName(RelationalSqlParser.TableNameContext ctx) {
-    return super.visitTableName(ctx);
+    return new Table(getLocation(ctx), getQualifiedName(ctx.qualifiedName()));
   }
 
   @Override
   public Node 
visitSubqueryRelation(RelationalSqlParser.SubqueryRelationContext ctx) {
-    return super.visitSubqueryRelation(ctx);
+    return new TableSubquery(getLocation(ctx), (Query) visit(ctx.query()));
   }
 
   @Override
   public Node 
visitParenthesizedRelation(RelationalSqlParser.ParenthesizedRelationContext 
ctx) {
-    return super.visitParenthesizedRelation(ctx);
+    return visit(ctx.relation());
   }
 
+  // ********************* predicates *******************
+
   @Override
-  public Node visitExpression(RelationalSqlParser.ExpressionContext ctx) {
-    return super.visitExpression(ctx);
+  public Node visitPredicated(RelationalSqlParser.PredicatedContext ctx) {
+    if (ctx.predicate() != null) {
+      return visit(ctx.predicate());
+    }
+
+    return visit(ctx.valueExpression);
   }
 
   @Override
-  public Node visitLogicalNot(RelationalSqlParser.LogicalNotContext ctx) {
-    return super.visitLogicalNot(ctx);
+  public Node visitComparison(RelationalSqlParser.ComparisonContext ctx) {
+    return new ComparisonExpression(
+        getLocation(ctx.comparisonOperator()),
+        getComparisonOperator(((TerminalNode) 
ctx.comparisonOperator().getChild(0)).getSymbol()),
+        (Expression) visit(ctx.value),
+        (Expression) visit(ctx.right));
   }
 
   @Override
-  public Node visitPredicated(RelationalSqlParser.PredicatedContext ctx) {
-    return super.visitPredicated(ctx);
+  public Node visitLogicalNot(RelationalSqlParser.LogicalNotContext ctx) {
+    return super.visitLogicalNot(ctx);
   }
 
   @Override
@@ -588,314 +909,423 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     return super.visitAnd(ctx);
   }
 
-  @Override
-  public Node visitComparison(RelationalSqlParser.ComparisonContext ctx) {
-    return super.visitComparison(ctx);
-  }
-
   @Override
   public Node 
visitQuantifiedComparison(RelationalSqlParser.QuantifiedComparisonContext ctx) {
-    return super.visitQuantifiedComparison(ctx);
+    return new QuantifiedComparisonExpression(
+        getLocation(ctx.comparisonOperator()),
+        getComparisonOperator(((TerminalNode) 
ctx.comparisonOperator().getChild(0)).getSymbol()),
+        getComparisonQuantifier(
+            ((TerminalNode) 
ctx.comparisonQuantifier().getChild(0)).getSymbol()),
+        (Expression) visit(ctx.value),
+        new SubqueryExpression(getLocation(ctx.query()), (Query) 
visit(ctx.query())));
   }
 
   @Override
   public Node visitBetween(RelationalSqlParser.BetweenContext ctx) {
-    return super.visitBetween(ctx);
+    Expression expression =
+        new BetweenPredicate(
+            getLocation(ctx),
+            (Expression) visit(ctx.value),
+            (Expression) visit(ctx.lower),
+            (Expression) visit(ctx.upper));
+
+    if (ctx.NOT() != null) {
+      expression = new NotExpression(getLocation(ctx), expression);
+    }
+
+    return expression;
   }
 
   @Override
   public Node visitInList(RelationalSqlParser.InListContext ctx) {
-    return super.visitInList(ctx);
+    Expression result =
+        new InPredicate(
+            getLocation(ctx),
+            (Expression) visit(ctx.value),
+            new InListExpression(getLocation(ctx), visit(ctx.expression(), 
Expression.class)));
+
+    if (ctx.NOT() != null) {
+      result = new NotExpression(getLocation(ctx), result);
+    }
+
+    return result;
   }
 
   @Override
   public Node visitInSubquery(RelationalSqlParser.InSubqueryContext ctx) {
-    return super.visitInSubquery(ctx);
+    Expression result =
+        new InPredicate(
+            getLocation(ctx),
+            (Expression) visit(ctx.value),
+            new SubqueryExpression(getLocation(ctx), (Query) 
visit(ctx.query())));
+
+    if (ctx.NOT() != null) {
+      result = new NotExpression(getLocation(ctx), result);
+    }
+
+    return result;
   }
 
   @Override
   public Node visitLike(RelationalSqlParser.LikeContext ctx) {
-    return super.visitLike(ctx);
+    Expression result;
+    if (ctx.escape != null) {
+      result =
+          new LikePredicate(
+              getLocation(ctx),
+              (Expression) visit(ctx.value),
+              (Expression) visit(ctx.pattern),
+              (Expression) visit(ctx.escape));
+    } else {
+      result =
+          new LikePredicate(
+              getLocation(ctx), (Expression) visit(ctx.value), (Expression) 
visit(ctx.pattern));
+    }
+
+    if (ctx.NOT() != null) {
+      result = new NotExpression(getLocation(ctx), result);
+    }
+
+    return result;
   }
 
   @Override
   public Node visitNullPredicate(RelationalSqlParser.NullPredicateContext ctx) 
{
-    return super.visitNullPredicate(ctx);
+    Expression child = (Expression) visit(ctx.value);
+
+    if (ctx.NOT() == null) {
+      return new IsNullPredicate(getLocation(ctx), child);
+    }
+
+    return new IsNotNullPredicate(getLocation(ctx), child);
   }
 
   @Override
   public Node visitDistinctFrom(RelationalSqlParser.DistinctFromContext ctx) {
-    return super.visitDistinctFrom(ctx);
-  }
+    Expression expression =
+        new ComparisonExpression(
+            getLocation(ctx),
+            ComparisonExpression.Operator.IS_DISTINCT_FROM,
+            (Expression) visit(ctx.value),
+            (Expression) visit(ctx.right));
 
-  @Override
-  public Node 
visitValueExpressionDefault(RelationalSqlParser.ValueExpressionDefaultContext 
ctx) {
-    return super.visitValueExpressionDefault(ctx);
-  }
+    if (ctx.NOT() != null) {
+      expression = new NotExpression(getLocation(ctx), expression);
+    }
 
-  @Override
-  public Node visitConcatenation(RelationalSqlParser.ConcatenationContext ctx) 
{
-    return super.visitConcatenation(ctx);
+    return expression;
   }
 
   @Override
-  public Node 
visitArithmeticBinary(RelationalSqlParser.ArithmeticBinaryContext ctx) {
-    return super.visitArithmeticBinary(ctx);
+  public Node visitExists(RelationalSqlParser.ExistsContext ctx) {
+    return new ExistsPredicate(
+        getLocation(ctx), new SubqueryExpression(getLocation(ctx), (Query) 
visit(ctx.query())));
   }
 
+  // ************** value expressions **************
   @Override
   public Node visitArithmeticUnary(RelationalSqlParser.ArithmeticUnaryContext 
ctx) {
-    return super.visitArithmeticUnary(ctx);
-  }
+    Expression child = (Expression) visit(ctx.valueExpression());
 
-  @Override
-  public Node visitDereference(RelationalSqlParser.DereferenceContext ctx) {
-    return super.visitDereference(ctx);
+    switch (ctx.operator.getType()) {
+      case RelationalSqlLexer.MINUS:
+        return ArithmeticUnaryExpression.negative(getLocation(ctx), child);
+      case RelationalSqlLexer.PLUS:
+        return ArithmeticUnaryExpression.positive(getLocation(ctx), child);
+      default:
+        throw new UnsupportedOperationException("Unsupported sign: " + 
ctx.operator.getText());
+    }
   }
 
   @Override
-  public Node visitSimpleCase(RelationalSqlParser.SimpleCaseContext ctx) {
-    return super.visitSimpleCase(ctx);
+  public Node 
visitArithmeticBinary(RelationalSqlParser.ArithmeticBinaryContext ctx) {
+    return new ArithmeticBinaryExpression(
+        getLocation(ctx.operator),
+        getArithmeticBinaryOperator(ctx.operator),
+        (Expression) visit(ctx.left),
+        (Expression) visit(ctx.right));
   }
 
   @Override
-  public Node visitColumnReference(RelationalSqlParser.ColumnReferenceContext 
ctx) {
-    return super.visitColumnReference(ctx);
+  public Node visitConcatenation(RelationalSqlParser.ConcatenationContext ctx) 
{
+    return new FunctionCall(
+        getLocation(ctx.CONCAT()),
+        QualifiedName.of("concat"),
+        ImmutableList.of((Expression) visit(ctx.left), (Expression) 
visit(ctx.right)));
   }
 
+  // ********************* primary expressions **********************
   @Override
-  public Node 
visitSpecialDateTimeFunction(RelationalSqlParser.SpecialDateTimeFunctionContext 
ctx) {
-    return super.visitSpecialDateTimeFunction(ctx);
+  public Node 
visitParenthesizedExpression(RelationalSqlParser.ParenthesizedExpressionContext 
ctx) {
+    return visit(ctx.expression());
   }
 
   @Override
-  public Node 
visitSubqueryExpression(RelationalSqlParser.SubqueryExpressionContext ctx) {
-    return super.visitSubqueryExpression(ctx);
+  public Node visitCast(RelationalSqlParser.CastContext ctx) {
+    return new Cast(
+        getLocation(ctx), (Expression) visit(ctx.expression()), (DataType) 
visit(ctx.type()));
   }
 
   @Override
-  public Node visitCurrentDatabase(RelationalSqlParser.CurrentDatabaseContext 
ctx) {
-    return super.visitCurrentDatabase(ctx);
+  public Node 
visitSpecialDateTimeFunction(RelationalSqlParser.SpecialDateTimeFunctionContext 
ctx) {
+    CurrentTime.Function function = getDateTimeFunctionType(ctx.name);
+    return new CurrentTime(getLocation(ctx), function);
   }
 
   @Override
-  public Node visitSubstring(RelationalSqlParser.SubstringContext ctx) {
-    return super.visitSubstring(ctx);
+  public Node visitTrim(RelationalSqlParser.TrimContext ctx) {
+    if (ctx.FROM() != null && ctx.trimsSpecification() == null && ctx.trimChar 
== null) {
+      throw parseError(
+          "The 'trim' function must have specification, char or both arguments 
when it takes FROM",
+          ctx);
+    }
+
+    Trim.Specification specification =
+        ctx.trimsSpecification() == null
+            ? Trim.Specification.BOTH
+            : toTrimSpecification((Token) 
ctx.trimsSpecification().getChild(0).getPayload());
+    if (ctx.trimChar != null) {
+      return new Trim(
+          getLocation(ctx),
+          specification,
+          (Expression) visit(ctx.trimSource),
+          (Expression) visit(ctx.trimChar));
+    } else {
+      return new Trim(getLocation(ctx), specification, (Expression) 
visit(ctx.trimSource));
+    }
+  }
+
+  private static Trim.Specification toTrimSpecification(Token token) {
+    switch (token.getType()) {
+      case RelationalSqlLexer.BOTH:
+        return Trim.Specification.BOTH;
+      case RelationalSqlLexer.LEADING:
+        return Trim.Specification.LEADING;
+      case RelationalSqlLexer.TRAILING:
+        return Trim.Specification.TRAILING;
+      default:
+        throw new IllegalArgumentException("Unsupported trim specification: " 
+ token.getText());
+    }
   }
 
   @Override
-  public Node visitLiteral(RelationalSqlParser.LiteralContext ctx) {
-    return super.visitLiteral(ctx);
+  public Node visitSubstring(RelationalSqlParser.SubstringContext ctx) {
+    return new FunctionCall(
+        getLocation(ctx),
+        QualifiedName.of("substr"),
+        visit(ctx.valueExpression(), Expression.class));
   }
 
   @Override
-  public Node visitCast(RelationalSqlParser.CastContext ctx) {
-    return super.visitCast(ctx);
+  public Node visitCurrentDatabase(RelationalSqlParser.CurrentDatabaseContext 
ctx) {
+    return new CurrentDatabase(getLocation(ctx));
   }
 
   @Override
   public Node visitCurrentUser(RelationalSqlParser.CurrentUserContext ctx) {
-    return super.visitCurrentUser(ctx);
+    return new CurrentUser(getLocation(ctx));
   }
 
   @Override
-  public Node 
visitParenthesizedExpression(RelationalSqlParser.ParenthesizedExpressionContext 
ctx) {
-    return super.visitParenthesizedExpression(ctx);
+  public Node 
visitSubqueryExpression(RelationalSqlParser.SubqueryExpressionContext ctx) {
+    return new SubqueryExpression(getLocation(ctx), (Query) 
visit(ctx.query()));
   }
 
   @Override
-  public Node visitTrim(RelationalSqlParser.TrimContext ctx) {
-    return super.visitTrim(ctx);
+  public Node visitDereference(RelationalSqlParser.DereferenceContext ctx) {
+    return new DereferenceExpression(
+        getLocation(ctx), (Expression) visit(ctx.base), (Identifier) 
visit(ctx.fieldName));
   }
 
   @Override
-  public Node visitFunctionCall(RelationalSqlParser.FunctionCallContext ctx) {
-    return super.visitFunctionCall(ctx);
+  public Node visitColumnReference(RelationalSqlParser.ColumnReferenceContext 
ctx) {
+    return visit(ctx.identifier());
   }
 
   @Override
-  public Node visitExists(RelationalSqlParser.ExistsContext ctx) {
-    return super.visitExists(ctx);
+  public Node visitSimpleCase(RelationalSqlParser.SimpleCaseContext ctx) {
+    if (ctx.elseExpression != null) {
+      return new SimpleCaseExpression(
+          getLocation(ctx),
+          (Expression) visit(ctx.operand),
+          visit(ctx.whenClause(), WhenClause.class),
+          (Expression) visit(ctx.elseExpression));
+    } else {
+      return new SimpleCaseExpression(
+          getLocation(ctx),
+          (Expression) visit(ctx.operand),
+          visit(ctx.whenClause(), WhenClause.class));
+    }
   }
 
   @Override
   public Node visitSearchedCase(RelationalSqlParser.SearchedCaseContext ctx) {
-    return super.visitSearchedCase(ctx);
+    if (ctx.elseExpression != null) {
+      return new SearchedCaseExpression(
+          getLocation(ctx),
+          visit(ctx.whenClause(), WhenClause.class),
+          (Expression) visit(ctx.elseExpression));
+    } else {
+      return new SearchedCaseExpression(
+          getLocation(ctx), visit(ctx.whenClause(), WhenClause.class));
+    }
   }
 
   @Override
-  public Node visitNullLiteral(RelationalSqlParser.NullLiteralContext ctx) {
-    return super.visitNullLiteral(ctx);
+  public Node visitWhenClause(RelationalSqlParser.WhenClauseContext ctx) {
+    return new WhenClause(
+        getLocation(ctx), (Expression) visit(ctx.condition), (Expression) 
visit(ctx.result));
   }
 
   @Override
-  public Node visitNumericLiteral(RelationalSqlParser.NumericLiteralContext 
ctx) {
-    return super.visitNumericLiteral(ctx);
-  }
+  public Node visitFunctionCall(RelationalSqlParser.FunctionCallContext ctx) {
 
-  @Override
-  public Node visitBooleanLiteral(RelationalSqlParser.BooleanLiteralContext 
ctx) {
-    return super.visitBooleanLiteral(ctx);
-  }
+    QualifiedName name = getQualifiedName(ctx.qualifiedName());
 
-  @Override
-  public Node visitStringLiteral(RelationalSqlParser.StringLiteralContext ctx) 
{
-    return super.visitStringLiteral(ctx);
-  }
+    boolean distinct = isDistinct(ctx.setQuantifier());
 
-  @Override
-  public Node visitBinaryLiteral(RelationalSqlParser.BinaryLiteralContext ctx) 
{
-    return super.visitBinaryLiteral(ctx);
-  }
+    if (name.toString().equalsIgnoreCase("if")) {
+      check(
+          ctx.expression().size() == 2 || ctx.expression().size() == 3,
+          "Invalid number of arguments for 'if' function",
+          ctx);
+      check(!distinct, "DISTINCT not valid for 'if' function", ctx);
 
-  @Override
-  public Node visitParameter(RelationalSqlParser.ParameterContext ctx) {
-    return super.visitParameter(ctx);
-  }
+      Expression elseExpression = null;
+      if (ctx.expression().size() == 3) {
+        elseExpression = (Expression) visit(ctx.expression(2));
+      }
 
-  @Override
-  public Node 
visitTrimsSpecification(RelationalSqlParser.TrimsSpecificationContext ctx) {
-    return super.visitTrimsSpecification(ctx);
-  }
+      return new IfExpression(
+          getLocation(ctx),
+          (Expression) visit(ctx.expression(0)),
+          (Expression) visit(ctx.expression(1)),
+          elseExpression);
+    }
 
-  @Override
-  public Node 
visitBasicStringLiteral(RelationalSqlParser.BasicStringLiteralContext ctx) {
-    return super.visitBasicStringLiteral(ctx);
-  }
-
-  @Override
-  public Node 
visitUnicodeStringLiteral(RelationalSqlParser.UnicodeStringLiteralContext ctx) {
-    return super.visitUnicodeStringLiteral(ctx);
-  }
+    if (name.toString().equalsIgnoreCase("nullif")) {
+      check(ctx.expression().size() == 2, "Invalid number of arguments for 
'nullif' function", ctx);
+      check(!distinct, "DISTINCT not valid for 'nullif' function", ctx);
 
-  @Override
-  public Node 
visitIdentifierOrString(RelationalSqlParser.IdentifierOrStringContext ctx) {
-    return super.visitIdentifierOrString(ctx);
-  }
+      return new NullIfExpression(
+          getLocation(ctx),
+          (Expression) visit(ctx.expression(0)),
+          (Expression) visit(ctx.expression(1)));
+    }
 
-  @Override
-  public Node 
visitTimeZoneInterval(RelationalSqlParser.TimeZoneIntervalContext ctx) {
-    return super.visitTimeZoneInterval(ctx);
-  }
+    if (name.toString().equalsIgnoreCase("coalesce")) {
+      check(
+          ctx.expression().size() >= 2,
+          "The 'coalesce' function must have at least two arguments",
+          ctx);
+      check(!distinct, "DISTINCT not valid for 'coalesce' function", ctx);
 
-  @Override
-  public Node visitTimeZoneString(RelationalSqlParser.TimeZoneStringContext 
ctx) {
-    return super.visitTimeZoneString(ctx);
-  }
+      return new CoalesceExpression(getLocation(ctx), visit(ctx.expression(), 
Expression.class));
+    }
 
-  @Override
-  public Node 
visitComparisonOperator(RelationalSqlParser.ComparisonOperatorContext ctx) {
-    return super.visitComparisonOperator(ctx);
-  }
+    List<Expression> arguments = visit(ctx.expression(), Expression.class);
+    if (ctx.label != null) {
+      arguments =
+          ImmutableList.of(
+              new DereferenceExpression(getLocation(ctx.label), (Identifier) 
visit(ctx.label)));
+    }
 
-  @Override
-  public Node 
visitComparisonQuantifier(RelationalSqlParser.ComparisonQuantifierContext ctx) {
-    return super.visitComparisonQuantifier(ctx);
+    return new FunctionCall(getLocation(ctx), name, distinct, arguments);
   }
 
-  @Override
-  public Node visitBooleanValue(RelationalSqlParser.BooleanValueContext ctx) {
-    return super.visitBooleanValue(ctx);
-  }
+  // ************** literals **************
 
   @Override
-  public Node visitInterval(RelationalSqlParser.IntervalContext ctx) {
-    return super.visitInterval(ctx);
-  }
-
-  @Override
-  public Node visitIntervalField(RelationalSqlParser.IntervalFieldContext ctx) 
{
-    return super.visitIntervalField(ctx);
-  }
-
-  @Override
-  public Node visitTimeDuration(RelationalSqlParser.TimeDurationContext ctx) {
-    return super.visitTimeDuration(ctx);
+  public Node visitNullLiteral(RelationalSqlParser.NullLiteralContext ctx) {
+    return new NullLiteral(getLocation(ctx));
   }
 
   @Override
-  public Node visitGenericType(RelationalSqlParser.GenericTypeContext ctx) {
-    return super.visitGenericType(ctx);
+  public Node 
visitBasicStringLiteral(RelationalSqlParser.BasicStringLiteralContext ctx) {
+    return super.visitBasicStringLiteral(ctx);
   }
 
   @Override
-  public Node visitTypeParameter(RelationalSqlParser.TypeParameterContext ctx) 
{
-    return super.visitTypeParameter(ctx);
+  public Node 
visitUnicodeStringLiteral(RelationalSqlParser.UnicodeStringLiteralContext ctx) {
+    return super.visitUnicodeStringLiteral(ctx);
   }
 
   @Override
-  public Node visitWhenClause(RelationalSqlParser.WhenClauseContext ctx) {
-    return super.visitWhenClause(ctx);
+  public Node visitBinaryLiteral(RelationalSqlParser.BinaryLiteralContext ctx) 
{
+    return super.visitBinaryLiteral(ctx);
   }
 
   @Override
-  public Node 
visitQuantifiedPrimary(RelationalSqlParser.QuantifiedPrimaryContext ctx) {
-    return super.visitQuantifiedPrimary(ctx);
+  public Node visitNumericLiteral(RelationalSqlParser.NumericLiteralContext 
ctx) {
+    return super.visitNumericLiteral(ctx);
   }
 
   @Override
-  public Node 
visitPatternConcatenation(RelationalSqlParser.PatternConcatenationContext ctx) {
-    return super.visitPatternConcatenation(ctx);
+  public Node visitBooleanLiteral(RelationalSqlParser.BooleanLiteralContext 
ctx) {
+    return super.visitBooleanLiteral(ctx);
   }
 
   @Override
-  public Node 
visitPatternAlternation(RelationalSqlParser.PatternAlternationContext ctx) {
-    return super.visitPatternAlternation(ctx);
+  public Node visitStringLiteral(RelationalSqlParser.StringLiteralContext ctx) 
{
+    return super.visitStringLiteral(ctx);
   }
 
   @Override
-  public Node visitPatternVariable(RelationalSqlParser.PatternVariableContext 
ctx) {
-    return super.visitPatternVariable(ctx);
+  public Node visitParameter(RelationalSqlParser.ParameterContext ctx) {
+    return super.visitParameter(ctx);
   }
 
   @Override
-  public Node visitEmptyPattern(RelationalSqlParser.EmptyPatternContext ctx) {
-    return super.visitEmptyPattern(ctx);
+  public Node 
visitTrimsSpecification(RelationalSqlParser.TrimsSpecificationContext ctx) {
+    return super.visitTrimsSpecification(ctx);
   }
 
   @Override
-  public Node 
visitPatternPermutation(RelationalSqlParser.PatternPermutationContext ctx) {
-    return super.visitPatternPermutation(ctx);
-  }
+  public Node 
visitIdentifierOrString(RelationalSqlParser.IdentifierOrStringContext ctx) {
+    String s = null;
+    if (ctx.identifier() != null) {
+      return visit(ctx.identifier());
+    } else if (ctx.string() != null) {
+      s = ((StringLiteral) visit(ctx.string())).getValue();
+    }
 
-  @Override
-  public Node visitGroupedPattern(RelationalSqlParser.GroupedPatternContext 
ctx) {
-    return super.visitGroupedPattern(ctx);
+    return new Identifier(getLocation(ctx), s);
   }
 
   @Override
-  public Node 
visitPartitionStartAnchor(RelationalSqlParser.PartitionStartAnchorContext ctx) {
-    return super.visitPartitionStartAnchor(ctx);
+  public Node visitBooleanValue(RelationalSqlParser.BooleanValueContext ctx) {
+    return super.visitBooleanValue(ctx);
   }
 
   @Override
-  public Node 
visitPartitionEndAnchor(RelationalSqlParser.PartitionEndAnchorContext ctx) {
-    return super.visitPartitionEndAnchor(ctx);
+  public Node visitInterval(RelationalSqlParser.IntervalContext ctx) {
+    return super.visitInterval(ctx);
   }
 
   @Override
-  public Node visitExcludedPattern(RelationalSqlParser.ExcludedPatternContext 
ctx) {
-    return super.visitExcludedPattern(ctx);
+  public Node visitIntervalField(RelationalSqlParser.IntervalFieldContext ctx) 
{
+    return super.visitIntervalField(ctx);
   }
 
   @Override
-  public Node 
visitZeroOrMoreQuantifier(RelationalSqlParser.ZeroOrMoreQuantifierContext ctx) {
-    return super.visitZeroOrMoreQuantifier(ctx);
+  public Node visitTimeDuration(RelationalSqlParser.TimeDurationContext ctx) {
+    return super.visitTimeDuration(ctx);
   }
 
   @Override
-  public Node 
visitOneOrMoreQuantifier(RelationalSqlParser.OneOrMoreQuantifierContext ctx) {
-    return super.visitOneOrMoreQuantifier(ctx);
-  }
+  public Node visitGenericType(RelationalSqlParser.GenericTypeContext ctx) {
+    List<DataTypeParameter> parameters =
+        ctx.typeParameter().stream()
+            .map(this::visit)
+            .map(DataTypeParameter.class::cast)
+            .collect(toImmutableList());
 
-  @Override
-  public Node 
visitZeroOrOneQuantifier(RelationalSqlParser.ZeroOrOneQuantifierContext ctx) {
-    return super.visitZeroOrOneQuantifier(ctx);
+    return new GenericDataType(getLocation(ctx), (Identifier) 
visit(ctx.identifier()), parameters);
   }
 
   @Override
-  public Node visitRangeQuantifier(RelationalSqlParser.RangeQuantifierContext 
ctx) {
-    return super.visitRangeQuantifier(ctx);
+  public Node visitTypeParameter(RelationalSqlParser.TypeParameterContext ctx) 
{
+    return super.visitTypeParameter(ctx);
   }
 
   @Override
@@ -1030,22 +1460,15 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
 
   @Override
   public Node 
visitUnquotedIdentifier(RelationalSqlParser.UnquotedIdentifierContext ctx) {
-    return super.visitUnquotedIdentifier(ctx);
+    return new Identifier(getLocation(ctx), ctx.getText(), false);
   }
 
   @Override
   public Node 
visitQuotedIdentifier(RelationalSqlParser.QuotedIdentifierContext ctx) {
-    return super.visitQuotedIdentifier(ctx);
-  }
+    String token = ctx.getText();
+    String identifier = token.substring(1, token.length() - 1).replace("\"\"", 
"\"");
 
-  @Override
-  public Node 
visitBackQuotedIdentifier(RelationalSqlParser.BackQuotedIdentifierContext ctx) {
-    return super.visitBackQuotedIdentifier(ctx);
-  }
-
-  @Override
-  public Node visitDigitIdentifier(RelationalSqlParser.DigitIdentifierContext 
ctx) {
-    return super.visitDigitIdentifier(ctx);
+    return new Identifier(getLocation(ctx), identifier, true);
   }
 
   @Override
@@ -1078,6 +1501,157 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     return super.visitNonReserved(ctx);
   }
 
+  // ***************** helpers *****************
+  private <T> Optional<T> visitIfPresent(ParserRuleContext context, Class<T> 
clazz) {
+    return Optional.ofNullable(context).map(this::visit).map(clazz::cast);
+  }
+
+  private <T> List<T> visit(List<? extends ParserRuleContext> contexts, 
Class<T> clazz) {
+    return 
contexts.stream().map(this::visit).map(clazz::cast).collect(toList());
+  }
+
+  private QualifiedName 
getQualifiedName(RelationalSqlParser.QualifiedNameContext context) {
+    return QualifiedName.of(visit(context.identifier(), Identifier.class));
+  }
+
+  private static boolean isDistinct(RelationalSqlParser.SetQuantifierContext 
setQuantifier) {
+    return setQuantifier != null && setQuantifier.DISTINCT() != null;
+  }
+
+  private static boolean isHexDigit(char c) {
+    return ((c >= '0') && (c <= '9')) || ((c >= 'A') && (c <= 'F')) || ((c >= 
'a') && (c <= 'f'));
+  }
+
+  private static boolean isValidUnicodeEscape(char c) {
+    return c < 0x7F && c > 0x20 && !isHexDigit(c) && c != '"' && c != '+' && c 
!= '\'';
+  }
+
+  private static Optional<String> getTextIfPresent(ParserRuleContext context) {
+    return Optional.ofNullable(context).map(ParseTree::getText);
+  }
+
+  private Optional<Identifier> getIdentifierIfPresent(ParserRuleContext 
context) {
+    return Optional.ofNullable(context).map(c -> (Identifier) visit(c));
+  }
+
+  private static ColumnDefinition.ColumnCategory getColumnCategory(Token 
category) {
+    if (category == null) {
+      return ColumnDefinition.ColumnCategory.MEASUREMENT;
+    }
+    switch (category.getType()) {
+      case RelationalSqlLexer.ID:
+        return ColumnDefinition.ColumnCategory.ID;
+      case RelationalSqlLexer.ATTRIBUTE:
+        return ColumnDefinition.ColumnCategory.ATTRIBUTE;
+      case RelationalSqlLexer.TIME:
+        return ColumnDefinition.ColumnCategory.TIME;
+      case RelationalSqlLexer.MEASUREMENT:
+        return ColumnDefinition.ColumnCategory.MEASUREMENT;
+      default:
+        throw new UnsupportedOperationException(
+            "Unsupported ColumnCategory: " + category.getText());
+    }
+  }
+
+  private static ArithmeticBinaryExpression.Operator 
getArithmeticBinaryOperator(Token operator) {
+    switch (operator.getType()) {
+      case RelationalSqlLexer.PLUS:
+        return ArithmeticBinaryExpression.Operator.ADD;
+      case RelationalSqlLexer.MINUS:
+        return ArithmeticBinaryExpression.Operator.SUBTRACT;
+      case RelationalSqlLexer.ASTERISK:
+        return ArithmeticBinaryExpression.Operator.MULTIPLY;
+      case RelationalSqlLexer.SLASH:
+        return ArithmeticBinaryExpression.Operator.DIVIDE;
+      case RelationalSqlLexer.PERCENT:
+        return ArithmeticBinaryExpression.Operator.MODULUS;
+      default:
+        throw new UnsupportedOperationException("Unsupported operator: " + 
operator.getText());
+    }
+  }
+
+  private static ComparisonExpression.Operator getComparisonOperator(Token 
symbol) {
+    switch (symbol.getType()) {
+      case RelationalSqlLexer.EQ:
+        return ComparisonExpression.Operator.EQUAL;
+      case RelationalSqlLexer.NEQ:
+        return ComparisonExpression.Operator.NOT_EQUAL;
+      case RelationalSqlLexer.LT:
+        return ComparisonExpression.Operator.LESS_THAN;
+      case RelationalSqlLexer.LTE:
+        return ComparisonExpression.Operator.LESS_THAN_OR_EQUAL;
+      case RelationalSqlLexer.GT:
+        return ComparisonExpression.Operator.GREATER_THAN;
+      case RelationalSqlLexer.GTE:
+        return ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL;
+      default:
+        throw new IllegalArgumentException("Unsupported operator: " + 
symbol.getText());
+    }
+  }
+
+  private static CurrentTime.Function getDateTimeFunctionType(Token token) {
+    switch (token.getType()) {
+      case RelationalSqlLexer.CURRENT_DATE:
+        return CurrentTime.Function.DATE;
+      case RelationalSqlLexer.CURRENT_TIME:
+        return CurrentTime.Function.TIME;
+      case RelationalSqlLexer.CURRENT_TIMESTAMP:
+      case RelationalSqlLexer.NOW:
+        return CurrentTime.Function.TIMESTAMP;
+      case RelationalSqlLexer.LOCALTIME:
+        return CurrentTime.Function.LOCALTIME;
+      case RelationalSqlLexer.LOCALTIMESTAMP:
+        return CurrentTime.Function.LOCALTIMESTAMP;
+      default:
+        throw new IllegalArgumentException("Unsupported special function: " + 
token.getText());
+    }
+  }
+
+  private static SortItem.NullOrdering getNullOrderingType(Token token) {
+    switch (token.getType()) {
+      case RelationalSqlLexer.FIRST:
+        return SortItem.NullOrdering.FIRST;
+      case RelationalSqlLexer.LAST:
+        return SortItem.NullOrdering.LAST;
+      default:
+        throw new IllegalArgumentException("Unsupported ordering: " + 
token.getText());
+    }
+  }
+
+  private static SortItem.Ordering getOrderingType(Token token) {
+    switch (token.getType()) {
+      case RelationalSqlLexer.ASC:
+        return SortItem.Ordering.ASCENDING;
+      case RelationalSqlLexer.DESC:
+        return SortItem.Ordering.DESCENDING;
+      default:
+        throw new IllegalArgumentException("Unsupported ordering: " + 
token.getText());
+    }
+  }
+
+  private static QuantifiedComparisonExpression.Quantifier 
getComparisonQuantifier(Token symbol) {
+    switch (symbol.getType()) {
+      case RelationalSqlLexer.ALL:
+        return QuantifiedComparisonExpression.Quantifier.ALL;
+      case RelationalSqlLexer.ANY:
+        return QuantifiedComparisonExpression.Quantifier.ANY;
+      case RelationalSqlLexer.SOME:
+        return QuantifiedComparisonExpression.Quantifier.SOME;
+      default:
+        throw new IllegalArgumentException("Unsupported quantifier: " + 
symbol.getText());
+    }
+  }
+
+  private List<Identifier> 
getIdentifiers(List<RelationalSqlParser.IdentifierContext> identifiers) {
+    return identifiers.stream().map(context -> (Identifier) 
visit(context)).collect(toList());
+  }
+
+  private static void check(boolean condition, String message, 
ParserRuleContext context) {
+    if (!condition) {
+      throw parseError(message, context);
+    }
+  }
+
   private NodeLocation getLocation(TerminalNode terminalNode) {
     requireNonNull(terminalNode, "terminalNode is null");
     return getLocation(terminalNode.getSymbol());
@@ -1098,4 +1672,21 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
                 + (token.getLine() == 1 ? baseLocation.getColumnNumber() : 0))
         : new NodeLocation(token.getLine(), token.getCharPositionInLine() + 1);
   }
+
+  private static ParsingException parseError(String message, ParserRuleContext 
context) {
+    return new ParsingException(
+        message,
+        null,
+        context.getStart().getLine(),
+        context.getStart().getCharPositionInLine() + 1);
+  }
+
+  private static void validateArgumentAlias(Identifier alias, 
ParserRuleContext context) {
+    check(
+        alias.isDelimited() || 
!alias.getValue().equalsIgnoreCase("COPARTITION"),
+        "The word \"COPARTITION\" is ambiguous in this context. "
+            + "To alias an argument, precede the alias with \"AS\". "
+            + "To specify co-partitioning, change the argument order so that 
the last argument cannot be aliased.",
+        context);
+  }
 }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/ErrorHandler.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/ErrorHandler.java
new file mode 100644
index 00000000000..a03fa07b4f3
--- /dev/null
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/ErrorHandler.java
@@ -0,0 +1,438 @@
+/*
+ * 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.parser;
+
+import com.google.common.collect.ImmutableSet;
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.NoViableAltException;
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.antlr.v4.runtime.RuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenStream;
+import org.antlr.v4.runtime.Vocabulary;
+import org.antlr.v4.runtime.atn.ATN;
+import org.antlr.v4.runtime.atn.ATNState;
+import org.antlr.v4.runtime.atn.NotSetTransition;
+import org.antlr.v4.runtime.atn.PrecedencePredicateTransition;
+import org.antlr.v4.runtime.atn.RuleStartState;
+import org.antlr.v4.runtime.atn.RuleStopState;
+import org.antlr.v4.runtime.atn.RuleTransition;
+import org.antlr.v4.runtime.atn.Transition;
+import org.antlr.v4.runtime.atn.WildcardTransition;
+import org.antlr.v4.runtime.misc.IntervalSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static org.antlr.v4.runtime.atn.ATNState.RULE_START;
+
+public class ErrorHandler extends BaseErrorListener {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(ErrorHandler.class);
+
+  private final Map<Integer, String> specialRules;
+  private final Map<Integer, String> specialTokens;
+  private final Set<Integer> ignoredRules;
+
+  private ErrorHandler(
+      Map<Integer, String> specialRules,
+      Map<Integer, String> specialTokens,
+      Set<Integer> ignoredRules) {
+    this.specialRules = new HashMap<>(specialRules);
+    this.specialTokens = specialTokens;
+    this.ignoredRules = new HashSet<>(ignoredRules);
+  }
+
+  @Override
+  public void syntaxError(
+      Recognizer<?, ?> recognizer,
+      Object offendingSymbol,
+      int line,
+      int charPositionInLine,
+      String message,
+      RecognitionException e) {
+    try {
+      Parser parser = (Parser) recognizer;
+
+      Result result = analyze(e, parser);
+
+      // pick the candidate tokens associated largest token index processed 
(i.e., the path that
+      // consumed the most input)
+      String expected = 
result.getExpected().stream().sorted().collect(Collectors.joining(", "));
+
+      message =
+          String.format(
+              "mismatched input '%s'. Expecting: %s",
+              
parser.getTokenStream().get(result.getErrorTokenIndex()).getText(), expected);
+    } catch (Exception exception) {
+      LOGGER.warn(
+          "Unexpected failure when handling parsing error. This is likely a 
bug in the implementation",
+          exception);
+    }
+
+    throw new ParsingException(message, e, line, charPositionInLine + 1);
+  }
+
+  private Result analyze(RecognitionException e, Parser parser) {
+    ATN atn = parser.getATN();
+
+    ATNState currentState;
+    Token currentToken;
+    RuleContext context;
+
+    if (e != null) {
+      currentState = atn.states.get(e.getOffendingState());
+      currentToken = e.getOffendingToken();
+      context = e.getCtx();
+
+      if (e instanceof NoViableAltException) {
+        currentToken = ((NoViableAltException) e).getStartToken();
+      }
+    } else {
+      currentState = atn.states.get(parser.getState());
+      currentToken = parser.getCurrentToken();
+      context = parser.getContext();
+    }
+
+    Analyzer analyzer = new Analyzer(parser, specialRules, specialTokens, 
ignoredRules);
+    return analyzer.process(currentState, currentToken.getTokenIndex(), 
context);
+  }
+
+  private static class ParsingState {
+    public final ATNState state;
+    public final int tokenIndex;
+    public final boolean suppressed;
+    public final Parser parser;
+
+    public ParsingState(ATNState state, int tokenIndex, boolean suppressed, 
Parser parser) {
+      this.state = state;
+      this.tokenIndex = tokenIndex;
+      this.suppressed = suppressed;
+      this.parser = parser;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      ParsingState that = (ParsingState) o;
+      return tokenIndex == that.tokenIndex && state.equals(that.state);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(state, tokenIndex);
+    }
+
+    @Override
+    public String toString() {
+      Token token = parser.getTokenStream().get(tokenIndex);
+
+      String text = firstNonNull(token.getText(), "?");
+      text = text.replace("\\", "\\\\");
+      text = text.replace("\n", "\\n");
+      text = text.replace("\r", "\\r");
+      text = text.replace("\t", "\\t");
+
+      return String.format(
+          "%s%s:%s @ %s:<%s>:%s",
+          suppressed ? "-" : "+",
+          parser.getRuleNames()[state.ruleIndex],
+          state.stateNumber,
+          tokenIndex,
+          parser.getVocabulary().getSymbolicName(token.getType()),
+          text);
+    }
+  }
+
+  private static class Analyzer {
+    private final Parser parser;
+    private final ATN atn;
+    private final Vocabulary vocabulary;
+    private final Map<Integer, String> specialRules;
+    private final Map<Integer, String> specialTokens;
+    private final Set<Integer> ignoredRules;
+    private final TokenStream stream;
+
+    private int furthestTokenIndex = -1;
+    private final Set<String> candidates = new HashSet<>();
+
+    private final Map<ParsingState, Set<Integer>> memo = new HashMap<>();
+
+    public Analyzer(
+        Parser parser,
+        Map<Integer, String> specialRules,
+        Map<Integer, String> specialTokens,
+        Set<Integer> ignoredRules) {
+      this.parser = parser;
+      this.stream = parser.getTokenStream();
+      this.atn = parser.getATN();
+      this.vocabulary = parser.getVocabulary();
+      this.specialRules = specialRules;
+      this.specialTokens = specialTokens;
+      this.ignoredRules = ignoredRules;
+    }
+
+    public Result process(ATNState currentState, int tokenIndex, RuleContext 
context) {
+      RuleStartState startState = atn.ruleToStartState[currentState.ruleIndex];
+
+      if (isReachable(currentState, startState)) {
+        // We've been dropped inside a rule in a state that's reachable via 
epsilon transitions.
+        // This is,
+        // effectively, equivalent to starting at the beginning (or 
immediately outside) the rule.
+        // In that case, backtrack to the beginning to be able to take 
advantage of logic that
+        // remaps
+        // some rules to well-known names for reporting purposes
+        currentState = startState;
+      }
+
+      Set<Integer> endTokens =
+          process(new ParsingState(currentState, tokenIndex, false, parser), 
0);
+      while (!endTokens.isEmpty() && context.invokingState != -1) {
+        ATNState nextState =
+            ((RuleTransition) 
atn.states.get(context.invokingState).transition(0)).followState;
+        endTokens =
+            endTokens.stream()
+                .flatMap(
+                    endToken ->
+                        process(new ParsingState(nextState, endToken, false, 
parser), 0).stream())
+                .collect(Collectors.toSet());
+        context = context.parent;
+      }
+
+      return new Result(furthestTokenIndex, candidates);
+    }
+
+    private boolean isReachable(ATNState target, RuleStartState from) {
+      Deque<ATNState> activeStates = new ArrayDeque<>();
+      activeStates.add(from);
+
+      while (!activeStates.isEmpty()) {
+        ATNState current = activeStates.pop();
+
+        if (current.stateNumber == target.stateNumber) {
+          return true;
+        }
+
+        for (int i = 0; i < current.getNumberOfTransitions(); i++) {
+          Transition transition = current.transition(i);
+
+          if (transition.isEpsilon()) {
+            activeStates.push(transition.target);
+          }
+        }
+      }
+
+      return false;
+    }
+
+    private Set<Integer> process(ParsingState start, int precedence) {
+      Set<Integer> result = memo.get(start);
+      if (result != null) {
+        return result;
+      }
+
+      ImmutableSet.Builder<Integer> endTokens = ImmutableSet.builder();
+
+      // Simulates the ATN by consuming input tokens and walking transitions.
+      // The ATN can be in multiple states (similar to an NFA)
+      Deque<ParsingState> activeStates = new ArrayDeque<>();
+      activeStates.add(start);
+
+      while (!activeStates.isEmpty()) {
+        ParsingState current = activeStates.pop();
+
+        ATNState state = current.state;
+        int tokenIndex = current.tokenIndex;
+        boolean suppressed = current.suppressed;
+
+        while (stream.get(tokenIndex).getChannel() == Token.HIDDEN_CHANNEL) {
+          // Ignore whitespace
+          tokenIndex++;
+        }
+        int currentToken = stream.get(tokenIndex).getType();
+
+        if (state.getStateType() == RULE_START) {
+          int rule = state.ruleIndex;
+
+          if (specialRules.containsKey(rule)) {
+            if (!suppressed) {
+              recordLabel(tokenIndex, specialRules.get(rule));
+            }
+            suppressed = true;
+          } else if (ignoredRules.contains(rule)) {
+            // TODO expand ignored rules like we expand special rules
+            continue;
+          }
+        }
+
+        if (state instanceof RuleStopState) {
+          endTokens.add(tokenIndex);
+          continue;
+        }
+
+        for (int i = 0; i < state.getNumberOfTransitions(); i++) {
+          Transition transition = state.transition(i);
+
+          if (transition instanceof RuleTransition) {
+            RuleTransition ruleTransition = (RuleTransition) transition;
+            for (int endToken :
+                process(
+                    new ParsingState(ruleTransition.target, tokenIndex, 
suppressed, parser),
+                    ruleTransition.precedence)) {
+              activeStates.push(
+                  new ParsingState(
+                      ruleTransition.followState,
+                      endToken,
+                      suppressed && endToken == currentToken,
+                      parser));
+            }
+          } else if (transition instanceof PrecedencePredicateTransition) {
+            if (precedence < ((PrecedencePredicateTransition) 
transition).precedence) {
+              activeStates.push(
+                  new ParsingState(transition.target, tokenIndex, suppressed, 
parser));
+            }
+          } else if (transition.isEpsilon()) {
+            activeStates.push(new ParsingState(transition.target, tokenIndex, 
suppressed, parser));
+          } else if (transition instanceof WildcardTransition) {
+            throw new UnsupportedOperationException("not yet implemented: 
wildcard transition");
+          } else {
+            IntervalSet labels = transition.label();
+
+            if (transition instanceof NotSetTransition) {
+              labels =
+                  labels.complement(IntervalSet.of(Token.MIN_USER_TOKEN_TYPE, 
atn.maxTokenType));
+            }
+
+            // Surprisingly, TokenStream (i.e. BufferedTokenStream) may not 
have loaded all the
+            // tokens from the
+            // underlying stream. TokenStream.get() does not force tokens to 
be buffered -- it just
+            // returns what's
+            // in the current buffer, or fail with an IndexOutOfBoundsError. 
Since Antlr decided the
+            // error occurred
+            // within the current set of buffered tokens, stop when we reach 
the end of the buffer.
+            if (labels.contains(currentToken) && tokenIndex < stream.size() - 
1) {
+              activeStates.push(new ParsingState(transition.target, tokenIndex 
+ 1, false, parser));
+            } else {
+              if (!suppressed) {
+                recordLabel(tokenIndex, getTokenNames(labels));
+              }
+            }
+          }
+        }
+      }
+
+      result = endTokens.build();
+      memo.put(start, result);
+      return result;
+    }
+
+    private void recordLabel(int tokenIndex, String label) {
+      recordLabel(tokenIndex, ImmutableSet.of(label));
+    }
+
+    private void recordLabel(int tokenIndex, Set<String> labels) {
+      if (tokenIndex >= furthestTokenIndex) {
+        if (tokenIndex > furthestTokenIndex) {
+          candidates.clear();
+          furthestTokenIndex = tokenIndex;
+        }
+
+        candidates.addAll(labels);
+      }
+    }
+
+    private Set<String> getTokenNames(IntervalSet tokens) {
+      Set<String> names = new HashSet<>();
+      for (int i = 0; i < tokens.size(); i++) {
+        int token = tokens.get(i);
+        if (token == Token.EOF) {
+          names.add("<EOF>");
+        } else {
+          names.add(specialTokens.getOrDefault(token, 
vocabulary.getDisplayName(token)));
+        }
+      }
+
+      return names;
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private final Map<Integer, String> specialRules = new HashMap<>();
+    private final Map<Integer, String> specialTokens = new HashMap<>();
+    private final Set<Integer> ignoredRules = new HashSet<>();
+
+    public Builder specialRule(int ruleId, String name) {
+      specialRules.put(ruleId, name);
+      return this;
+    }
+
+    public Builder specialToken(int tokenId, String name) {
+      specialTokens.put(tokenId, name);
+      return this;
+    }
+
+    public Builder ignoredRule(int ruleId) {
+      ignoredRules.add(ruleId);
+      return this;
+    }
+
+    public ErrorHandler build() {
+      return new ErrorHandler(specialRules, specialTokens, ignoredRules);
+    }
+  }
+
+  private static class Result {
+    private final int errorTokenIndex;
+    private final Set<String> expected;
+
+    public Result(int errorTokenIndex, Set<String> expected) {
+      this.errorTokenIndex = errorTokenIndex;
+      this.expected = expected;
+    }
+
+    public int getErrorTokenIndex() {
+      return errorTokenIndex;
+    }
+
+    public Set<String> getExpected() {
+      return expected;
+    }
+  }
+}
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/RefreshableSqlBaseParserInitializer.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/RefreshableSqlBaseParserInitializer.java
new file mode 100644
index 00000000000..e1a3581f19d
--- /dev/null
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/RefreshableSqlBaseParserInitializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.parser;
+
+import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlLexer;
+import org.apache.iotdb.db.relational.grammar.sql.RelationalSqlParser;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+
+public final class RefreshableSqlBaseParserInitializer
+    implements BiConsumer<RelationalSqlLexer, RelationalSqlParser> {
+
+  private final AtomicReference<SqlBaseParserAndLexerATNCaches> caches = new 
AtomicReference<>();
+
+  public RefreshableSqlBaseParserInitializer() {
+    refresh();
+  }
+
+  public void refresh() {
+    caches.set(new SqlBaseParserAndLexerATNCaches());
+  }
+
+  @Override
+  public void accept(RelationalSqlLexer lexer, RelationalSqlParser parser) {
+    SqlBaseParserAndLexerATNCaches atnCaches = this.caches.get();
+    atnCaches.lexer.configureLexer(lexer);
+    atnCaches.parser.configureParser(parser);
+  }
+
+  private static final class SqlBaseParserAndLexerATNCaches {
+    public final AntlrATNCacheFields lexer = new 
AntlrATNCacheFields(RelationalSqlLexer._ATN);
+    public final AntlrATNCacheFields parser = new 
AntlrATNCacheFields(RelationalSqlParser._ATN);
+  }
+}
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/SqlParser.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/SqlParser.java
new file mode 100644
index 00000000000..933a39b2338
--- /dev/null
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/parser/SqlParser.java
@@ -0,0 +1,22 @@
+/*
+ * 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.parser;
+
+public class SqlParser {}
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AddColumn.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AddColumn.java
index 9391152a1a1..d51ce9b1ba8 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AddColumn.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AddColumn.java
@@ -29,25 +29,25 @@ import static java.util.Objects.requireNonNull;
 
 public class AddColumn extends Statement {
 
-  private final Identifier name;
+  private final QualifiedName tableName;
   private final ColumnDefinition column;
 
-  public AddColumn(Identifier name, ColumnDefinition column) {
+  public AddColumn(QualifiedName tableName, ColumnDefinition column) {
     super(null);
 
-    this.name = requireNonNull(name, "name is null");
+    this.tableName = requireNonNull(tableName, "tableName is null");
     this.column = requireNonNull(column, "column is null");
   }
 
-  public AddColumn(NodeLocation location, Identifier name, ColumnDefinition 
column) {
+  public AddColumn(NodeLocation location, QualifiedName tableName, 
ColumnDefinition column) {
     super(requireNonNull(location, "location is null"));
 
-    this.name = requireNonNull(name, "name is null");
+    this.tableName = requireNonNull(tableName, "tableName is null");
     this.column = requireNonNull(column, "column is null");
   }
 
-  public Identifier getName() {
-    return name;
+  public QualifiedName getTableName() {
+    return tableName;
   }
 
   public ColumnDefinition getColumn() {
@@ -66,7 +66,7 @@ public class AddColumn extends Statement {
 
   @Override
   public int hashCode() {
-    return Objects.hash(name, column);
+    return Objects.hash(tableName, column);
   }
 
   @Override
@@ -78,11 +78,11 @@ public class AddColumn extends Statement {
       return false;
     }
     AddColumn o = (AddColumn) obj;
-    return Objects.equals(name, o.name) && Objects.equals(column, o.column);
+    return Objects.equals(tableName, o.tableName) && Objects.equals(column, 
o.column);
   }
 
   @Override
   public String toString() {
-    return toStringHelper(this).add("name", name).add("column", 
column).toString();
+    return toStringHelper(this).add("name", tableName).add("column", 
column).toString();
   }
 }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AllColumns.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AllColumns.java
index 6709bd11062..2c069d09108 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AllColumns.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AllColumns.java
@@ -59,6 +59,12 @@ public class AllColumns extends SelectItem {
     this.aliases = ImmutableList.copyOf(requireNonNull(aliases, "aliases is 
null"));
   }
 
+  public AllColumns(NodeLocation location, List<Identifier> aliases) {
+    super(requireNonNull(location, "location is null"));
+    this.target = null;
+    this.aliases = ImmutableList.copyOf(requireNonNull(aliases, "aliases is 
null"));
+  }
+
   public List<Identifier> getAliases() {
     return aliases;
   }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AstVisitor.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AstVisitor.java
index a68703715ec..c548b7a487d 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AstVisitor.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/AstVisitor.java
@@ -207,6 +207,10 @@ public abstract class AstVisitor<R, C> {
     return visitExpression(node, context);
   }
 
+  protected R visitNullIfExpression(NullIfExpression node, C context) {
+    return visitExpression(node, context);
+  }
+
   protected R visitNullLiteral(NullLiteral node, C context) {
     return visitLiteral(node, context);
   }
@@ -351,6 +355,18 @@ public abstract class AstVisitor<R, C> {
     return visitStatement(node, context);
   }
 
+  protected R visitCreateIndex(CreateIndex node, C context) {
+    return visitStatement(node, context);
+  }
+
+  protected R visitDropIndex(DropIndex node, C context) {
+    return visitStatement(node, context);
+  }
+
+  protected R visitShowIndex(ShowIndex node, C context) {
+    return visitStatement(node, context);
+  }
+
   protected R visitInsert(Insert node, C context) {
     return visitStatement(node, context);
   }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/CreateIndex.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/CreateIndex.java
new file mode 100644
index 00000000000..782a3ae81c9
--- /dev/null
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/CreateIndex.java
@@ -0,0 +1,110 @@
+/*
+ * 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 com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public class CreateIndex extends Statement {
+
+  private final QualifiedName tableName;
+
+  private final Identifier indexName;
+
+  private final List<Identifier> columnList;
+
+  public CreateIndex(QualifiedName tableName, Identifier indexName, 
List<Identifier> columnList) {
+    super(null);
+    this.tableName = requireNonNull(tableName, "tableName is null");
+    this.indexName = requireNonNull(indexName, "indexName is null");
+    requireNonNull(columnList, "columnList is null");
+    checkArgument(!columnList.isEmpty(), "size of columnList should be larger 
than 1");
+    this.columnList = columnList;
+  }
+
+  public CreateIndex(
+      NodeLocation location,
+      QualifiedName tableName,
+      Identifier indexName,
+      List<Identifier> columnList) {
+    super(requireNonNull(location, "location is null"));
+    this.tableName = requireNonNull(tableName, "tableName is null");
+    this.indexName = requireNonNull(indexName, "indexName is null");
+    requireNonNull(columnList, "columnList is null");
+    checkArgument(!columnList.isEmpty(), "size of columnList should be larger 
than 1");
+    this.columnList = columnList;
+  }
+
+  public QualifiedName getTableName() {
+    return tableName;
+  }
+
+  public Identifier getIndexName() {
+    return indexName;
+  }
+
+  public List<Identifier> getColumnList() {
+    return columnList;
+  }
+
+  @Override
+  public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
+    return visitor.visitCreateIndex(this, context);
+  }
+
+  @Override
+  public List<Node> getChildren() {
+    return ImmutableList.of();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    CreateIndex that = (CreateIndex) o;
+    return Objects.equals(tableName, that.tableName)
+        && Objects.equals(indexName, that.indexName)
+        && Objects.equals(columnList, that.columnList);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(tableName, indexName, columnList);
+  }
+
+  @Override
+  public String toString() {
+    return toStringHelper(this)
+        .add("tableName", tableName)
+        .add("indexName", indexName)
+        .add("columnList", columnList)
+        .toString();
+  }
+}
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
index d51f2330c9f..3db7d7d1fc6 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
@@ -29,23 +29,23 @@ import static java.util.Objects.requireNonNull;
 
 public class DropDB extends Statement {
 
-  private final Identifier catalogName;
+  private final Identifier dbName;
   private final boolean exists;
 
   public DropDB(Identifier catalogName, boolean exists) {
     super(null);
-    this.catalogName = requireNonNull(catalogName, "catalogName is null");
+    this.dbName = requireNonNull(catalogName, "catalogName is null");
     this.exists = exists;
   }
 
   public DropDB(NodeLocation location, Identifier catalogName, boolean exists) 
{
     super(requireNonNull(location, "location is null"));
-    this.catalogName = requireNonNull(catalogName, "catalogName is null");
+    this.dbName = requireNonNull(catalogName, "catalogName is null");
     this.exists = exists;
   }
 
-  public Identifier getCatalogName() {
-    return catalogName;
+  public Identifier getDbName() {
+    return dbName;
   }
 
   public boolean isExists() {
@@ -71,16 +71,16 @@ public class DropDB extends Statement {
       return false;
     }
     DropDB o = (DropDB) obj;
-    return Objects.equals(catalogName, o.catalogName) && (exists == o.exists);
+    return Objects.equals(dbName, o.dbName) && (exists == o.exists);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(catalogName, exists);
+    return Objects.hash(dbName, exists);
   }
 
   @Override
   public String toString() {
-    return toStringHelper(this).add("catalogName", catalogName).add("exists", 
exists).toString();
+    return toStringHelper(this).add("catalogName", dbName).add("exists", 
exists).toString();
   }
 }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropIndex.java
similarity index 56%
copy from 
iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
copy to 
iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropIndex.java
index d51f2330c9f..49d8b2e332b 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropIndex.java
@@ -27,34 +27,35 @@ import java.util.Objects;
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static java.util.Objects.requireNonNull;
 
-public class DropDB extends Statement {
+public class DropIndex extends Statement {
 
-  private final Identifier catalogName;
-  private final boolean exists;
+  private final QualifiedName tableName;
 
-  public DropDB(Identifier catalogName, boolean exists) {
+  private final Identifier indexName;
+
+  public DropIndex(QualifiedName tableName, Identifier indexName) {
     super(null);
-    this.catalogName = requireNonNull(catalogName, "catalogName is null");
-    this.exists = exists;
+    this.tableName = requireNonNull(tableName, "tableName is null");
+    this.indexName = requireNonNull(indexName, "indexName is null");
   }
 
-  public DropDB(NodeLocation location, Identifier catalogName, boolean exists) 
{
+  public DropIndex(NodeLocation location, QualifiedName tableName, Identifier 
indexName) {
     super(requireNonNull(location, "location is null"));
-    this.catalogName = requireNonNull(catalogName, "catalogName is null");
-    this.exists = exists;
+    this.tableName = requireNonNull(tableName, "tableName is null");
+    this.indexName = requireNonNull(indexName, "indexName is null");
   }
 
-  public Identifier getCatalogName() {
-    return catalogName;
+  public QualifiedName getTableName() {
+    return tableName;
   }
 
-  public boolean isExists() {
-    return exists;
+  public Identifier getIndexName() {
+    return indexName;
   }
 
   @Override
   public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
-    return visitor.visitDropDB(this, context);
+    return visitor.visitDropIndex(this, context);
   }
 
   @Override
@@ -63,24 +64,25 @@ public class DropDB extends Statement {
   }
 
   @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
+  public boolean equals(Object o) {
+    if (this == o) {
       return true;
     }
-    if ((obj == null) || (getClass() != obj.getClass())) {
+    if (o == null || getClass() != o.getClass()) {
       return false;
     }
-    DropDB o = (DropDB) obj;
-    return Objects.equals(catalogName, o.catalogName) && (exists == o.exists);
+    DropIndex dropIndex = (DropIndex) o;
+    return Objects.equals(tableName, dropIndex.tableName)
+        && Objects.equals(indexName, dropIndex.indexName);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(catalogName, exists);
+    return Objects.hash(tableName, indexName);
   }
 
   @Override
   public String toString() {
-    return toStringHelper(this).add("catalogName", catalogName).add("exists", 
exists).toString();
+    return toStringHelper(this).add("tableName", tableName).add("indexName", 
indexName).toString();
   }
 }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/Identifier.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/Identifier.java
index caac01cc149..eb69f6d608c 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/Identifier.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/Identifier.java
@@ -56,6 +56,14 @@ public class Identifier extends Expression {
         delimited || isValidIdentifier(value), "value contains illegal 
characters: %s", value);
   }
 
+  public Identifier(NodeLocation location, String value) {
+    super(requireNonNull(location, "location is null"));
+    this.value = requireNonNull(value, "value is null");
+    this.delimited = !isValidIdentifier(value);
+
+    checkArgument(!value.isEmpty(), "value is empty");
+  }
+
   public Identifier(String value, boolean delimited) {
     super(null);
     this.value = requireNonNull(value, "value is null");
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/LikePredicate.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/LikePredicate.java
index 45bb808be8c..e30fe360492 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/LikePredicate.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/LikePredicate.java
@@ -49,6 +49,14 @@ public class LikePredicate extends Expression {
     this.escape = null;
   }
 
+  public LikePredicate(
+      NodeLocation location, Expression value, Expression pattern, Expression 
escape) {
+    super(requireNonNull(location, "location is null"));
+    this.value = requireNonNull(value, "value is null");
+    this.pattern = requireNonNull(pattern, "pattern is null");
+    this.escape = requireNonNull(escape, "escape is null");
+  }
+
   public Expression getValue() {
     return value;
   }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/NullIfExpression.java
similarity index 54%
copy from 
iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
copy to 
iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/NullIfExpression.java
index d51f2330c9f..5d7b9f2ef1e 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/NullIfExpression.java
@@ -24,63 +24,63 @@ import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Objects;
 
-import static com.google.common.base.MoreObjects.toStringHelper;
 import static java.util.Objects.requireNonNull;
 
-public class DropDB extends Statement {
+/** NULLIF(V1,V2): CASE WHEN V1=V2 THEN NULL ELSE V1 END */
+public class NullIfExpression extends Expression {
+  private final Expression first;
+  private final Expression second;
 
-  private final Identifier catalogName;
-  private final boolean exists;
-
-  public DropDB(Identifier catalogName, boolean exists) {
+  public NullIfExpression(Expression first, Expression second) {
     super(null);
-    this.catalogName = requireNonNull(catalogName, "catalogName is null");
-    this.exists = exists;
+    this.first = requireNonNull(first, "first is null");
+    this.second = requireNonNull(first, "second is null");
   }
 
-  public DropDB(NodeLocation location, Identifier catalogName, boolean exists) 
{
+  public NullIfExpression(NodeLocation location, Expression first, Expression 
second) {
     super(requireNonNull(location, "location is null"));
-    this.catalogName = requireNonNull(catalogName, "catalogName is null");
-    this.exists = exists;
+    this.first = requireNonNull(first, "first is null");
+    this.second = requireNonNull(first, "second is null");
   }
 
-  public Identifier getCatalogName() {
-    return catalogName;
+  public Expression getFirst() {
+    return first;
   }
 
-  public boolean isExists() {
-    return exists;
+  public Expression getSecond() {
+    return second;
   }
 
   @Override
   public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
-    return visitor.visitDropDB(this, context);
+    return visitor.visitNullIfExpression(this, context);
   }
 
   @Override
   public List<Node> getChildren() {
-    return ImmutableList.of();
+    return ImmutableList.of(first, second);
   }
 
   @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
+  public boolean equals(Object o) {
+    if (this == o) {
       return true;
     }
-    if ((obj == null) || (getClass() != obj.getClass())) {
+    if (o == null || getClass() != o.getClass()) {
       return false;
     }
-    DropDB o = (DropDB) obj;
-    return Objects.equals(catalogName, o.catalogName) && (exists == o.exists);
+
+    NullIfExpression that = (NullIfExpression) o;
+    return Objects.equals(first, that.first) && Objects.equals(second, 
that.second);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(catalogName, exists);
+    return Objects.hash(first, second);
   }
 
   @Override
-  public String toString() {
-    return toStringHelper(this).add("catalogName", catalogName).add("exists", 
exists).toString();
+  public boolean shallowEquals(Node other) {
+    return sameClass(this, other);
   }
 }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/ShowIndex.java
similarity index 60%
copy from 
iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
copy to 
iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/ShowIndex.java
index d51f2330c9f..c03d0ec4261 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/DropDB.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/tree/ShowIndex.java
@@ -27,34 +27,26 @@ import java.util.Objects;
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static java.util.Objects.requireNonNull;
 
-public class DropDB extends Statement {
+public class ShowIndex extends Statement {
+  private final QualifiedName tableName;
 
-  private final Identifier catalogName;
-  private final boolean exists;
-
-  public DropDB(Identifier catalogName, boolean exists) {
+  public ShowIndex(QualifiedName tableName) {
     super(null);
-    this.catalogName = requireNonNull(catalogName, "catalogName is null");
-    this.exists = exists;
+    this.tableName = requireNonNull(tableName, "tableName is null");
   }
 
-  public DropDB(NodeLocation location, Identifier catalogName, boolean exists) 
{
+  public ShowIndex(NodeLocation location, QualifiedName tableName) {
     super(requireNonNull(location, "location is null"));
-    this.catalogName = requireNonNull(catalogName, "catalogName is null");
-    this.exists = exists;
-  }
-
-  public Identifier getCatalogName() {
-    return catalogName;
+    this.tableName = requireNonNull(tableName, "tableName is null");
   }
 
-  public boolean isExists() {
-    return exists;
+  public QualifiedName getTableName() {
+    return tableName;
   }
 
   @Override
   public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
-    return visitor.visitDropDB(this, context);
+    return visitor.visitShowIndex(this, context);
   }
 
   @Override
@@ -63,24 +55,24 @@ public class DropDB extends Statement {
   }
 
   @Override
-  public boolean equals(Object obj) {
-    if (this == obj) {
+  public boolean equals(Object o) {
+    if (this == o) {
       return true;
     }
-    if ((obj == null) || (getClass() != obj.getClass())) {
+    if (o == null || getClass() != o.getClass()) {
       return false;
     }
-    DropDB o = (DropDB) obj;
-    return Objects.equals(catalogName, o.catalogName) && (exists == o.exists);
+    ShowIndex showIndex = (ShowIndex) o;
+    return Objects.equals(tableName, showIndex.tableName);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(catalogName, exists);
+    return Objects.hash(tableName);
   }
 
   @Override
   public String toString() {
-    return toStringHelper(this).add("catalogName", catalogName).add("exists", 
exists).toString();
+    return toStringHelper(this).add("tableName", tableName).toString();
   }
 }

Reply via email to