package com.alibaba.druid.sql.parser;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLCommentHint;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLExprImpl;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLObjectImpl;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.SQLOrderingSpecification;
import com.alibaba.druid.sql.ast.SQLOver;
import com.alibaba.druid.sql.ast.SQLWindow;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLDateExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLListExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.ast.expr.SQLRealExpr;
import com.alibaba.druid.sql.ast.expr.SQLTimestampExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprHint;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLLateralViewTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUnionOperator;
import com.alibaba.druid.sql.ast.statement.SQLUnionQuery;
import com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUnnestTableSource;
import com.alibaba.druid.sql.ast.statement.SQLValuesQuery;
import com.alibaba.druid.sql.ast.statement.SQLValuesTableSource;
import com.alibaba.druid.sql.ast.statement.SQLWithSubqueryClause;
import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOrderingExpr;
import com.alibaba.druid.util.FnvHash;
import com.alibaba.druid.util.StringUtils;
import java.util.List;

public class SQLSelectParser extends SQLParser {
   protected SQLExprParser exprParser;
   protected SQLSelectListCache selectListCache;

   public SQLSelectParser(String sql) {
      super(sql);
   }

   public SQLSelectParser(Lexer lexer) {
      super(lexer);
   }

   public SQLSelectParser(SQLExprParser exprParser) {
      this(exprParser, (SQLSelectListCache)null);
   }

   public SQLSelectParser(SQLExprParser exprParser, SQLSelectListCache selectListCache) {
      super(exprParser.getLexer(), exprParser.getDbType());
      this.exprParser = exprParser;
      this.selectListCache = selectListCache;
   }

   public SQLSelect select() {
      SQLSelect select = new SQLSelect();
      if (this.lexer.token == Token.WITH) {
         SQLWithSubqueryClause with = this.parseWith();
         select.setWithSubQuery(with);
      }

      SQLSelectQuery query = this.query(select);
      select.setQuery(query);
      SQLOrderBy orderBy = this.parseOrderBy();
      if (query instanceof SQLSelectQueryBlock) {
         SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)query;
         if (queryBlock.getOrderBy() == null) {
            queryBlock.setOrderBy(orderBy);
            if (this.lexer.token == Token.LIMIT) {
               SQLLimit limit = this.exprParser.parseLimit();
               queryBlock.setLimit(limit);
            }
         } else {
            select.setOrderBy(orderBy);
            if (this.lexer.token == Token.LIMIT) {
               SQLLimit limit = this.exprParser.parseLimit();
               select.setLimit(limit);
            }
         }

         if (orderBy != null) {
            this.parseFetchClause(queryBlock);
         }
      } else {
         select.setOrderBy(orderBy);
      }

      if (this.dbType == DbType.dm) {
         this.parseFor(select);
      }

      if (this.lexer.token == Token.LIMIT) {
         SQLLimit limit = this.exprParser.parseLimit();
         select.setLimit(limit);
      }

      this.parseFor(select);

      while(this.lexer.token == Token.HINT) {
         this.exprParser.parseHints(select.getHints());
      }

      return select;
   }

   private void parseFor(SQLSelect select) {
      if (this.lexer.token() == Token.FOR) {
         this.lexer.nextToken();
         this.accept(Token.UPDATE);
         select.setForUpdate(true);
         if (!this.lexer.identifierEquals(FnvHash.Constants.NO_WAIT) && !this.lexer.identifierEquals(FnvHash.Constants.NOWAIT)) {
            if (this.lexer.identifierEquals(FnvHash.Constants.WAIT)) {
               this.lexer.nextToken();
               SQLExpr waitTime = this.exprParser.primary();
               select.setWaitTime(waitTime);
            }
         } else {
            this.lexer.nextToken();
            select.setNoWait(true);
         }
      }

   }

   protected SQLUnionQuery createSQLUnionQuery() {
      SQLUnionQuery union = new SQLUnionQuery();
      union.setDbType(this.dbType);
      return union;
   }

   public SQLUnionQuery unionRest(SQLUnionQuery union) {
      if (this.lexer.token == Token.ORDER) {
         SQLOrderBy orderBy = this.exprParser.parseOrderBy();
         union.setOrderBy(orderBy);
         return this.unionRest(union);
      } else {
         if (this.lexer.token == Token.LIMIT) {
            SQLLimit limit = this.exprParser.parseLimit();
            union.setLimit(limit);
         }

         return union;
      }
   }

   public SQLSelectQuery queryRest(SQLSelectQuery selectQuery) {
      return this.queryRest(selectQuery, true);
   }

   public SQLSelectQuery queryRest(SQLSelectQuery selectQuery, boolean acceptUnion) {
      if (!acceptUnion) {
         return selectQuery;
      } else if (this.lexer.token != Token.UNION) {
         if (this.lexer.token == Token.EXCEPT) {
            this.lexer.nextToken();
            SQLUnionQuery union = new SQLUnionQuery();
            union.setLeft(selectQuery);
            if (this.lexer.token == Token.ALL) {
               this.lexer.nextToken();
               union.setOperator(SQLUnionOperator.EXCEPT_ALL);
            } else if (this.lexer.token == Token.DISTINCT) {
               this.lexer.nextToken();
               union.setOperator(SQLUnionOperator.EXCEPT_DISTINCT);
            } else {
               union.setOperator(SQLUnionOperator.EXCEPT);
            }

            boolean paren = this.lexer.token == Token.LPAREN;
            SQLSelectQuery right = this.query(union, false);
            union.setRight(right);
            if (!paren) {
               if (right instanceof SQLSelectQueryBlock) {
                  SQLSelectQueryBlock rightQuery = (SQLSelectQueryBlock)right;
                  SQLOrderBy orderBy = rightQuery.getOrderBy();
                  if (orderBy != null) {
                     union.setOrderBy(orderBy);
                     rightQuery.setOrderBy((SQLOrderBy)null);
                  }

                  SQLLimit limit = rightQuery.getLimit();
                  if (limit != null) {
                     union.setLimit(limit);
                     rightQuery.setLimit((SQLLimit)null);
                  }
               } else if (right instanceof SQLUnionQuery) {
                  SQLUnionQuery rightUnion = (SQLUnionQuery)right;
                  SQLOrderBy orderBy = rightUnion.getOrderBy();
                  if (orderBy != null) {
                     union.setOrderBy(orderBy);
                     rightUnion.setOrderBy((SQLOrderBy)null);
                  }

                  SQLLimit limit = rightUnion.getLimit();
                  if (limit != null) {
                     union.setLimit(limit);
                     rightUnion.setLimit((SQLLimit)null);
                  }
               }
            }

            return this.queryRest(union, true);
         } else if (this.lexer.token == Token.INTERSECT) {
            this.lexer.nextToken();
            SQLUnionQuery union = new SQLUnionQuery();
            union.setLeft(selectQuery);
            if (this.lexer.token() == Token.DISTINCT) {
               this.lexer.nextToken();
               union.setOperator(SQLUnionOperator.INTERSECT_DISTINCT);
            } else if (this.lexer.token == Token.ALL) {
               this.lexer.nextToken();
               union.setOperator(SQLUnionOperator.INTERSECT_ALL);
            } else {
               union.setOperator(SQLUnionOperator.INTERSECT);
            }

            boolean paren = this.lexer.token == Token.LPAREN;
            SQLSelectQuery right = this.query(union, false);
            union.setRight(right);
            if (!paren) {
               if (right instanceof SQLSelectQueryBlock) {
                  SQLSelectQueryBlock rightQuery = (SQLSelectQueryBlock)right;
                  SQLOrderBy orderBy = rightQuery.getOrderBy();
                  if (orderBy != null) {
                     union.setOrderBy(orderBy);
                     rightQuery.setOrderBy((SQLOrderBy)null);
                  }

                  SQLLimit limit = rightQuery.getLimit();
                  if (limit != null) {
                     union.setLimit(limit);
                     rightQuery.setLimit((SQLLimit)null);
                  }
               } else if (right instanceof SQLUnionQuery) {
                  SQLUnionQuery rightUnion = (SQLUnionQuery)right;
                  SQLOrderBy orderBy = rightUnion.getOrderBy();
                  if (orderBy != null) {
                     union.setOrderBy(orderBy);
                     rightUnion.setOrderBy((SQLOrderBy)null);
                  }

                  SQLLimit limit = rightUnion.getLimit();
                  if (limit != null) {
                     union.setLimit(limit);
                     rightUnion.setLimit((SQLLimit)null);
                  }
               }
            }

            return this.queryRest(union, true);
         } else if (acceptUnion && this.lexer.token == Token.MINUS) {
            this.lexer.nextToken();
            SQLUnionQuery union = new SQLUnionQuery();
            union.setLeft(selectQuery);
            if (this.lexer.token == Token.ALL) {
               this.lexer.nextToken();
               union.setOperator(SQLUnionOperator.MINUS_ALL);
            } else if (this.lexer.token == Token.DISTINCT) {
               this.lexer.nextToken();
               union.setOperator(SQLUnionOperator.MINUS_DISTINCT);
            } else {
               union.setOperator(SQLUnionOperator.MINUS);
            }

            SQLSelectQuery right = this.query(union, false);
            union.setRight(right);
            return this.queryRest(union, true);
         } else {
            return selectQuery;
         }
      } else {
         SQLUnionQuery union;
         do {
            this.lexer.nextToken();
            union = this.createSQLUnionQuery();
            if (union.getRelations().isEmpty()) {
               union.setLeft(selectQuery);
            }

            if (this.lexer.token == Token.ALL) {
               union.setOperator(SQLUnionOperator.UNION_ALL);
               this.lexer.nextToken();
            } else if (this.lexer.token == Token.DISTINCT) {
               union.setOperator(SQLUnionOperator.DISTINCT);
               this.lexer.nextToken();
            }

            boolean paren = this.lexer.token == Token.LPAREN;
            SQLSelectQuery right = this.query(paren ? null : union, false);
            union.setRight(right);

            while(this.lexer.isEnabled(SQLParserFeature.EnableMultiUnion) && this.lexer.token == Token.UNION) {
               Lexer.SavePoint mark = this.lexer.mark();
               this.lexer.nextToken();
               if (this.lexer.token == Token.ALL) {
                  if (union.getOperator() != SQLUnionOperator.UNION_ALL) {
                     this.lexer.reset(mark);
                     break;
                  }

                  this.lexer.nextToken();
               } else if (this.lexer.token == Token.DISTINCT) {
                  if (union.getOperator() != SQLUnionOperator.DISTINCT) {
                     this.lexer.reset(mark);
                     break;
                  }

                  this.lexer.nextToken();
               } else if (union.getOperator() != SQLUnionOperator.UNION) {
                  this.lexer.reset(mark);
                  break;
               }

               paren = this.lexer.token == Token.LPAREN;
               SQLSelectQuery r = this.query(paren ? null : union, false);
               union.addRelation(r);
               right = r;
            }

            if (!paren) {
               if (right instanceof SQLSelectQueryBlock) {
                  SQLSelectQueryBlock rightQuery = (SQLSelectQueryBlock)right;
                  SQLOrderBy orderBy = rightQuery.getOrderBy();
                  if (orderBy != null) {
                     union.setOrderBy(orderBy);
                     rightQuery.setOrderBy((SQLOrderBy)null);
                  }

                  SQLLimit limit = rightQuery.getLimit();
                  if (limit != null) {
                     union.setLimit(limit);
                     rightQuery.setLimit((SQLLimit)null);
                  }
               } else if (right instanceof SQLUnionQuery) {
                  SQLUnionQuery rightUnion = (SQLUnionQuery)right;
                  SQLOrderBy orderBy = rightUnion.getOrderBy();
                  if (orderBy != null) {
                     union.setOrderBy(orderBy);
                     rightUnion.setOrderBy((SQLOrderBy)null);
                  }

                  SQLLimit limit = rightUnion.getLimit();
                  if (limit != null) {
                     union.setLimit(limit);
                     rightUnion.setLimit((SQLLimit)null);
                  }
               }
            }

            union = this.unionRest(union);
            selectQuery = union;
         } while(this.lexer.token() == Token.UNION);

         selectQuery = this.queryRest(union, true);
         return selectQuery;
      }
   }

   private void setToLeft(SQLSelectQuery selectQuery, SQLUnionQuery parentUnion, SQLUnionQuery union, SQLSelectQuery right) {
      SQLUnionOperator operator = union.getOperator();
      if (union.getLeft() instanceof SQLUnionQuery) {
         SQLUnionQuery left;
         for(left = (SQLUnionQuery)union.getLeft(); left.getLeft() instanceof SQLUnionQuery; left = (SQLUnionQuery)left.getLeft()) {
         }

         left.setLeft(new SQLUnionQuery(parentUnion.getLeft(), parentUnion.getOperator(), left.getLeft()));
         parentUnion.setLeft(union.getLeft());
         parentUnion.setRight(union.getRight());
      } else {
         parentUnion.setRight(right);
         union.setLeft(parentUnion.getLeft());
         parentUnion.setLeft(union);
         union.setRight(selectQuery);
         union.setOperator(parentUnion.getOperator());
         parentUnion.setOperator(operator);
      }

   }

   public SQLSelectQuery query() {
      return this.query((SQLObject)null, true);
   }

   public SQLSelectQuery query(SQLObject parent) {
      return this.query(parent, true);
   }

   public SQLSelectQuery query(SQLObject parent, boolean acceptUnion) {
      if (this.lexer.token == Token.LPAREN) {
         this.lexer.nextToken();
         SQLSelectQuery select = this.query();
         this.accept(Token.RPAREN);
         return this.queryRest(select, acceptUnion);
      } else if (this.lexer.token() == Token.VALUES) {
         return this.valuesQuery(acceptUnion);
      } else {
         SQLSelectQueryBlock queryBlock = new SQLSelectQueryBlock(this.dbType);
         if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
            queryBlock.addBeforeComment(this.lexer.readAndResetComments());
         }

         this.accept(Token.SELECT);
         if (this.lexer.token() == Token.HINT) {
            this.exprParser.parseHints(queryBlock.getHints());
         }

         if (this.lexer.token == Token.COMMENT) {
            this.lexer.nextToken();
         }

         if (DbType.informix == this.dbType) {
            if (this.lexer.identifierEquals(FnvHash.Constants.SKIP)) {
               this.lexer.nextToken();
               SQLExpr offset = this.exprParser.primary();
               queryBlock.setOffset(offset);
            }

            if (this.lexer.identifierEquals(FnvHash.Constants.FIRST)) {
               this.lexer.nextToken();
               SQLExpr first = this.exprParser.primary();
               queryBlock.setFirst(first);
            }
         }

         if (this.lexer.token == Token.DISTINCT) {
            queryBlock.setDistionOption(2);
            this.lexer.nextToken();
         } else if (this.lexer.token == Token.UNIQUE) {
            queryBlock.setDistionOption(3);
            this.lexer.nextToken();
         } else if (this.lexer.token == Token.ALL) {
            queryBlock.setDistionOption(1);
            this.lexer.nextToken();
         }

         this.parseSelectList(queryBlock);
         if (this.lexer.token() == Token.INTO) {
            this.lexer.nextToken();
            SQLExpr expr = this.expr();
            if (this.lexer.token() != Token.COMMA) {
               queryBlock.setInto(expr);
            }
         }

         this.parseFrom(queryBlock);
         this.parseWhere(queryBlock);
         this.parseGroupBy(queryBlock);
         if (this.lexer.identifierEquals(FnvHash.Constants.WINDOW)) {
            this.parseWindow(queryBlock);
         }

         this.parseSortBy(queryBlock);
         this.parseFetchClause(queryBlock);
         if (this.lexer.token() == Token.FOR) {
            this.lexer.nextToken();
            this.accept(Token.UPDATE);
            queryBlock.setForUpdate(true);
            if (!this.lexer.identifierEquals(FnvHash.Constants.NO_WAIT) && !this.lexer.identifierEquals(FnvHash.Constants.NOWAIT)) {
               if (this.lexer.identifierEquals(FnvHash.Constants.WAIT)) {
                  this.lexer.nextToken();
                  SQLExpr waitTime = this.exprParser.primary();
                  queryBlock.setWaitTime(waitTime);
               }
            } else {
               this.lexer.nextToken();
               queryBlock.setNoWait(true);
            }
         }

         return this.queryRest(queryBlock, acceptUnion);
      }
   }

   protected SQLSelectQuery valuesQuery(boolean acceptUnion) {
      this.lexer.nextToken();
      SQLValuesQuery valuesQuery = new SQLValuesQuery();

      while(true) {
         if (this.lexer.token == Token.LPAREN) {
            this.lexer.nextToken();
            SQLListExpr listExpr = new SQLListExpr();
            this.exprParser.exprList(listExpr.getItems(), listExpr);
            this.accept(Token.RPAREN);
            valuesQuery.addValue(listExpr);
         } else {
            this.exprParser.exprList(valuesQuery.getValues(), valuesQuery);
         }

         if (this.lexer.token != Token.COMMA) {
            return this.queryRest(valuesQuery, acceptUnion);
         }

         this.lexer.nextToken();
      }
   }

   protected void withSubquery(SQLSelect select) {
      if (this.lexer.token == Token.WITH) {
         this.lexer.nextToken();
         SQLWithSubqueryClause withQueryClause = new SQLWithSubqueryClause();
         if (this.lexer.token == Token.RECURSIVE || this.lexer.identifierEquals(FnvHash.Constants.RECURSIVE)) {
            this.lexer.nextToken();
            withQueryClause.setRecursive(true);
         }

         while(true) {
            SQLWithSubqueryClause.Entry entry = new SQLWithSubqueryClause.Entry();
            entry.setParent(withQueryClause);
            String alias = this.lexer.stringVal();
            this.lexer.nextToken();
            entry.setAlias(alias);
            if (this.lexer.token == Token.LPAREN) {
               this.lexer.nextToken();
               this.exprParser.names(entry.getColumns());
               this.accept(Token.RPAREN);
            }

            this.accept(Token.AS);
            this.accept(Token.LPAREN);
            entry.setSubQuery(this.select());
            this.accept(Token.RPAREN);
            withQueryClause.addEntry(entry);
            if (this.lexer.token != Token.COMMA) {
               select.setWithSubQuery(withQueryClause);
               break;
            }

            this.lexer.nextToken();
         }
      }

   }

   public SQLWithSubqueryClause parseWith() {
      SQLWithSubqueryClause withQueryClause = new SQLWithSubqueryClause();
      if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
         withQueryClause.addBeforeComment(this.lexer.readAndResetComments());
      }

      this.accept(Token.WITH);
      if (this.lexer.token == Token.RECURSIVE || this.lexer.identifierEquals(FnvHash.Constants.RECURSIVE)) {
         this.lexer.nextToken();
         withQueryClause.setRecursive(true);
      }

      while(true) {
         SQLWithSubqueryClause.Entry entry = new SQLWithSubqueryClause.Entry();
         entry.setParent(withQueryClause);
         String alias = this.lexer.stringVal();
         this.lexer.nextToken();
         entry.setAlias(alias);
         if (this.lexer.token == Token.LPAREN) {
            this.lexer.nextToken();
            this.exprParser.names(entry.getColumns());
            this.accept(Token.RPAREN);
         }

         this.accept(Token.AS);
         this.accept(Token.LPAREN);
         switch (this.lexer.token) {
            case SELECT:
            case WITH:
               entry.setSubQuery(this.select());
            default:
               this.accept(Token.RPAREN);
               withQueryClause.addEntry(entry);
               if (this.lexer.token != Token.COMMA) {
                  return withQueryClause;
               }

               this.lexer.nextToken();
         }
      }
   }

   public void parseWhere(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token == Token.WHERE) {
         this.lexer.nextTokenIdent();
         List<String> beforeComments = null;
         if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
            beforeComments = this.lexer.readAndResetComments();
         }

         SQLExpr where;
         if (this.lexer.token == Token.IDENTIFIER) {
            String ident = this.lexer.stringVal();
            long hash_lower = this.lexer.hash_lower();
            this.lexer.nextTokenEq();
            SQLExpr identExpr;
            if (this.lexer.token == Token.LITERAL_CHARS) {
               String literal = this.lexer.stringVal;
               if (hash_lower == FnvHash.Constants.TIMESTAMP) {
                  identExpr = new SQLTimestampExpr(literal);
                  this.lexer.nextToken();
               } else if (hash_lower == FnvHash.Constants.DATE) {
                  identExpr = new SQLDateExpr(literal);
                  this.lexer.nextToken();
               } else if (hash_lower == FnvHash.Constants.REAL) {
                  identExpr = new SQLRealExpr(Float.parseFloat(literal));
                  this.lexer.nextToken();
               } else {
                  identExpr = new SQLIdentifierExpr(ident, hash_lower);
               }
            } else {
               identExpr = new SQLIdentifierExpr(ident, hash_lower);
            }

            if (this.lexer.token == Token.DOT) {
               identExpr = this.exprParser.primaryRest(identExpr);
            }

            if (this.lexer.token == Token.EQ) {
               this.lexer.nextToken();

               SQLExpr rightExp;
               try {
                  rightExp = this.exprParser.bitOr();
               } catch (EOFParserException e) {
                  throw new ParserException("EOF, " + ident + "=", e);
               }

               where = new SQLBinaryOpExpr(identExpr, SQLBinaryOperator.Equality, rightExp, this.dbType);
               switch (this.lexer.token) {
                  case BETWEEN:
                  case IS:
                  case EQ:
                  case IN:
                  case CONTAINS:
                  case BANG_TILDE_STAR:
                  case TILDE_EQ:
                  case LT:
                  case LTEQ:
                  case LTEQGT:
                  case GT:
                  case GTEQ:
                  case LTGT:
                  case BANGEQ:
                  case LIKE:
                  case NOT:
                     where = this.exprParser.relationalRest(where);
                  default:
                     SQLExpr var12 = this.exprParser.andRest(where);
                     var12 = this.exprParser.xorRest(var12);
                     where = this.exprParser.orRest(var12);
               }
            } else {
               identExpr = this.exprParser.primaryRest(identExpr);
               where = this.exprParser.exprRest(identExpr);
            }
         } else {
            while(this.lexer.token == Token.HINT) {
               this.lexer.nextToken();
            }

            where = this.exprParser.expr();

            while(this.lexer.token == Token.HINT) {
               this.lexer.nextToken();
            }
         }

         if (beforeComments != null) {
            where.addBeforeComment(beforeComments);
         }

         if (this.lexer.hasComment() && this.lexer.isKeepComments() && this.lexer.token != Token.INSERT) {
            where.addAfterComment(this.lexer.readAndResetComments());
         }

         queryBlock.setWhere(where);
      }
   }

   protected void parseSortBy(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token() == Token.ORDER) {
         SQLOrderBy orderBy = this.parseOrderBy();
         queryBlock.setOrderBy(orderBy);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.DISTRIBUTE)) {
         this.lexer.nextToken();
         this.accept(Token.BY);

         while(true) {
            SQLSelectOrderByItem distributeByItem = this.exprParser.parseSelectOrderByItem();
            queryBlock.addDistributeBy(distributeByItem);
            if (this.lexer.token() != Token.COMMA) {
               break;
            }

            this.lexer.nextToken();
         }
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.SORT)) {
         this.lexer.nextToken();
         this.accept(Token.BY);

         while(true) {
            SQLSelectOrderByItem sortByItem = this.exprParser.parseSelectOrderByItem();
            queryBlock.addSortBy(sortByItem);
            if (this.lexer.token() != Token.COMMA) {
               break;
            }

            this.lexer.nextToken();
         }
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.CLUSTER)) {
         this.lexer.nextToken();
         this.accept(Token.BY);

         while(true) {
            SQLSelectOrderByItem clusterByItem = this.exprParser.parseSelectOrderByItem();
            queryBlock.addClusterBy(clusterByItem);
            if (this.lexer.token() != Token.COMMA) {
               break;
            }

            this.lexer.nextToken();
         }
      }

   }

   protected void parseWindow(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.identifierEquals(FnvHash.Constants.WINDOW) || this.lexer.token == Token.WINDOW) {
         this.lexer.nextToken();

         while(true) {
            SQLName name = this.exprParser.name();
            this.accept(Token.AS);
            SQLOver over = new SQLOver();
            this.exprParser.over(over);
            queryBlock.addWindow(new SQLWindow(name, over));
            if (this.lexer.token != Token.COMMA) {
               return;
            }

            this.lexer.nextToken();
         }
      }
   }

   protected void parseGroupBy(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token == Token.GROUP) {
         this.lexer.nextTokenBy();
         this.accept(Token.BY);
         SQLSelectGroupByClause groupBy = new SQLSelectGroupByClause();
         if (this.lexer.token == Token.HINT) {
            groupBy.setHint(this.exprParser.parseHint());
         }

         if (this.lexer.token == Token.ALL) {
            this.lexer.nextToken();
            if (queryBlock.getDbType() == DbType.sqlserver) {
               groupBy.setAll(true);
            } else if (!this.lexer.identifierEquals(FnvHash.Constants.GROUPING)) {
               throw new ParserException("group by all syntax error. " + this.lexer.info());
            }
         } else if (this.lexer.token == Token.DISTINCT) {
            this.lexer.nextToken();
            groupBy.setDistinct(true);
         }

         if (this.lexer.identifierEquals(FnvHash.Constants.ROLLUP)) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            groupBy.setWithRollUp(true);
         }

         if (this.lexer.identifierEquals(FnvHash.Constants.CUBE)) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            groupBy.setWithCube(true);
         }

         while(true) {
            SQLExpr item = this.parseGroupByItem();
            item.setParent(groupBy);
            groupBy.addItem(item);
            if (this.lexer.token == Token.COMMA) {
               this.lexer.nextToken();
            } else if (!this.lexer.identifierEquals(FnvHash.Constants.GROUPING)) {
               if (groupBy.isWithRollUp() || groupBy.isWithCube()) {
                  this.accept(Token.RPAREN);
                  groupBy.setParen(true);
                  if (this.lexer.token == Token.COMMA && this.dbType == DbType.odps) {
                     this.lexer.nextToken();
                     item = new SQLMethodInvokeExpr(groupBy.isWithCube() ? "CUBE" : "ROLLUP");
                     ((SQLMethodInvokeExpr)item).getArguments().addAll(groupBy.getItems());
                     groupBy.getItems().clear();
                     groupBy.setWithCube(false);
                     groupBy.setWithRollUp(false);

                     for(SQLExpr arg : ((SQLMethodInvokeExpr)item).getArguments()) {
                        arg.setParent(item);
                     }

                     groupBy.addItem(item);
                     this.exprParser.exprList(groupBy.getItems(), groupBy);
                  }
               }

               if (this.lexer.token == Token.HAVING) {
                  this.lexer.nextToken();
                  item = this.exprParser.expr();
                  groupBy.setHaving(item);
               }

               if (this.lexer.token == Token.WITH) {
                  Lexer.SavePoint mark = this.lexer.mark();
                  this.lexer.nextToken();
                  if (this.lexer.identifierEquals(FnvHash.Constants.CUBE)) {
                     this.lexer.nextToken();
                     groupBy.setWithCube(true);
                  } else if (this.lexer.identifierEquals(FnvHash.Constants.ROLLUP)) {
                     this.lexer.nextToken();
                     groupBy.setWithRollUp(true);
                  } else if (this.lexer.identifierEquals(FnvHash.Constants.RS) && DbType.db2 == this.dbType) {
                     this.lexer.nextToken();
                  } else if (this.lexer.identifierEquals(FnvHash.Constants.RR) && DbType.db2 == this.dbType) {
                     this.lexer.nextToken();
                  } else if (this.lexer.identifierEquals(FnvHash.Constants.CS) && DbType.db2 == this.dbType) {
                     this.lexer.nextToken();
                  } else if (this.lexer.identifierEquals(FnvHash.Constants.UR) && DbType.db2 == this.dbType) {
                     this.lexer.nextToken();
                  } else {
                     this.lexer.reset(mark);
                  }
               }

               if (groupBy.getHaving() == null && this.lexer.token == Token.HAVING) {
                  this.lexer.nextToken();
                  item = this.exprParser.expr();
                  groupBy.setHaving(item);
               }

               queryBlock.setGroupBy(groupBy);
               break;
            }
         }
      } else if (this.lexer.token == Token.HAVING) {
         this.lexer.nextToken();
         SQLSelectGroupByClause groupBy = new SQLSelectGroupByClause();
         groupBy.setHaving(this.exprParser.expr());
         if (this.lexer.token == Token.GROUP) {
            this.lexer.nextToken();
            this.accept(Token.BY);

            while(true) {
               SQLExpr item = this.parseGroupByItem();
               item.setParent(groupBy);
               groupBy.addItem(item);
               if (this.lexer.token != Token.COMMA) {
                  break;
               }

               this.lexer.nextToken();
            }
         }

         if (this.lexer.token == Token.WITH) {
            this.lexer.nextToken();
            this.acceptIdentifier("ROLLUP");
            groupBy.setWithRollUp(true);
         }

         if (DbType.mysql == this.dbType && this.lexer.token == Token.DESC) {
            this.lexer.nextToken();
         }

         queryBlock.setGroupBy(groupBy);
      }

   }

   protected SQLExpr parseGroupByItem() {
      if (this.lexer.token == Token.LPAREN) {
         Lexer.SavePoint mark = this.lexer.mark();
         this.lexer.nextToken();
         if (this.lexer.token == Token.RPAREN) {
            this.lexer.nextToken();
            return new SQLListExpr();
         }

         this.lexer.reset(mark);
      }

      SQLExpr item = this.exprParser.expr();
      if (DbType.mysql == this.dbType) {
         if (this.lexer.token == Token.DESC) {
            this.lexer.nextToken();
            item = new MySqlOrderingExpr(item, SQLOrderingSpecification.DESC);
         } else if (this.lexer.token == Token.ASC) {
            this.lexer.nextToken();
            item = new MySqlOrderingExpr(item, SQLOrderingSpecification.ASC);
         }
      }

      if (this.lexer.token == Token.HINT) {
         SQLCommentHint hint = this.exprParser.parseHint();
         if (item instanceof SQLObjectImpl) {
            ((SQLExprImpl)item).setHint(hint);
         }
      }

      return item;
   }

   protected void parseSelectList(SQLSelectQueryBlock queryBlock) {
      List<SQLSelectItem> selectList = queryBlock.getSelectList();

      while(true) {
         SQLSelectItem selectItem = this.exprParser.parseSelectItem();
         selectList.add(selectItem);
         selectItem.setParent(queryBlock);
         if (this.lexer.token != Token.COMMA) {
            return;
         }

         this.lexer.nextToken();
      }
   }

   public void parseFrom(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token == Token.FROM) {
         this.lexer.nextToken();
         queryBlock.setFrom(this.parseTableSource());
      }
   }

   public SQLTableSource parseTableSource() {
      if (this.lexer.token == Token.LPAREN) {
         this.lexer.nextToken();
         SQLTableSource tableSource;
         if (this.lexer.token != Token.SELECT && this.lexer.token != Token.WITH && this.lexer.token != Token.SEL) {
            if (this.lexer.token == Token.LPAREN) {
               tableSource = this.parseTableSource();
               this.accept(Token.RPAREN);
            } else {
               tableSource = this.parseTableSource();
               this.accept(Token.RPAREN);
            }
         } else {
            SQLSelect select = this.select();
            this.accept(Token.RPAREN);
            if (select.getQuery() instanceof SQLSelectQueryBlock) {
               ((SQLSelectQueryBlock)select.getQuery()).setParenthesized(true);
            }

            SQLSelectQuery query = this.queryRest(select.getQuery(), true);
            if (query instanceof SQLUnionQuery) {
               tableSource = new SQLUnionQueryTableSource((SQLUnionQuery)query);
            } else {
               tableSource = new SQLSubqueryTableSource(select);
            }
         }

         if (this.lexer.token == Token.AS) {
            this.lexer.nextToken();
            String alias = this.tableAlias(true);
            tableSource.setAlias(alias);
            if (tableSource instanceof SQLValuesTableSource && ((SQLValuesTableSource)tableSource).getColumns().size() == 0) {
               SQLValuesTableSource values = (SQLValuesTableSource)tableSource;
               this.accept(Token.LPAREN);
               this.exprParser.names(values.getColumns(), values);
               this.accept(Token.RPAREN);
            } else if (tableSource instanceof SQLSubqueryTableSource) {
               SQLSubqueryTableSource values = (SQLSubqueryTableSource)tableSource;
               if (this.lexer.token == Token.LPAREN) {
                  this.lexer.nextToken();
                  this.exprParser.names(values.getColumns(), values);
                  this.accept(Token.RPAREN);
               }
            }
         }

         return this.parseTableSourceRest(tableSource);
      } else if (this.lexer.token() != Token.VALUES) {
         if (this.lexer.token == Token.SELECT) {
            throw new ParserException("TODO " + this.lexer.info());
         } else {
            SQLExprTableSource tableReference = new SQLExprTableSource();
            this.parseTableSourceQueryTableExpr(tableReference);
            SQLTableSource tableSrc = this.parseTableSourceRest(tableReference);
            if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
               tableSrc.addAfterComment(this.lexer.readAndResetComments());
            }

            return tableSrc;
         }
      } else {
         this.lexer.nextToken();
         SQLValuesTableSource tableSource = new SQLValuesTableSource();

         while(true) {
            this.accept(Token.LPAREN);
            SQLListExpr listExpr = new SQLListExpr();
            this.exprParser.exprList(listExpr.getItems(), listExpr);
            this.accept(Token.RPAREN);
            listExpr.setParent(tableSource);
            tableSource.getValues().add(listExpr);
            if (this.lexer.token != Token.COMMA) {
               if (this.lexer.token == Token.RPAREN) {
                  return tableSource;
               } else {
                  String alias = this.tableAlias();
                  if (alias != null) {
                     tableSource.setAlias(alias);
                  }

                  this.accept(Token.LPAREN);
                  this.exprParser.names(tableSource.getColumns(), tableSource);
                  this.accept(Token.RPAREN);
                  return tableSource;
               }
            }

            this.lexer.nextToken();
         }
      }
   }

   protected void parseTableSourceQueryTableExpr(SQLExprTableSource tableReference) {
      if (this.lexer.token != Token.LITERAL_ALIAS && !this.lexer.identifierEquals(FnvHash.Constants.IDENTIFIED) && this.lexer.token != Token.LITERAL_CHARS) {
         if (this.lexer.token == Token.HINT) {
            SQLCommentHint hint = this.exprParser.parseHint();
            tableReference.setHint(hint);
         }

         SQLExpr expr = this.expr();
         if (expr instanceof SQLBinaryOpExpr) {
            throw new ParserException("Invalid from clause : " + expr.toString().replace("\n", " "));
         } else {
            if (this.lexer.token() == Token.MONKEYS_AT) {
               this.lexer.nextToken();
               SQLExpr linkExpr = this.expr().clone();
               linkExpr.setParent(tableReference);
            }

            tableReference.setExpr(expr);
         }
      } else {
         tableReference.setExpr((SQLExpr)this.exprParser.name());
      }
   }

   protected SQLTableSource primaryTableSourceRest(SQLTableSource tableSource) {
      return tableSource;
   }

   public SQLTableSource parseTableSourceRest(SQLTableSource tableSource) {
      if (tableSource.getAlias() == null || tableSource.getAlias().length() == 0) {
         Token token = this.lexer.token;
         switch (token) {
            case LEFT:
            case RIGHT:
            case FULL:
               Lexer.SavePoint mark = this.lexer.mark();
               String strVal = this.lexer.stringVal();
               this.lexer.nextToken();
               if (this.lexer.token != Token.OUTER && this.lexer.token != Token.JOIN && !this.lexer.identifierEquals(FnvHash.Constants.ANTI) && !this.lexer.identifierEquals(FnvHash.Constants.SEMI)) {
                  tableSource.setAlias(strVal);
               } else {
                  this.lexer.reset(mark);
               }
            case OUTER:
               break;
            default:
               long hash;
               if (token != Token.IDENTIFIER || (hash = this.lexer.hash_lower()) != FnvHash.Constants.STRAIGHT_JOIN && hash != FnvHash.Constants.CROSS) {
                  String alias = this.tableAlias(false);
                  if (alias != null) {
                     if (this.isEnabled(SQLParserFeature.IgnoreNameQuotes) && alias.length() > 1) {
                        alias = StringUtils.removeNameQuotes(alias);
                     }

                     tableSource.setAlias(alias);
                     if (tableSource instanceof SQLValuesTableSource && ((SQLValuesTableSource)tableSource).getColumns().size() == 0) {
                        SQLValuesTableSource values = (SQLValuesTableSource)tableSource;
                        this.accept(Token.LPAREN);
                        this.exprParser.names(values.getColumns(), values);
                        this.accept(Token.RPAREN);
                     } else if (tableSource instanceof SQLSubqueryTableSource) {
                        SQLSubqueryTableSource values = (SQLSubqueryTableSource)tableSource;
                        if (this.lexer.token == Token.LPAREN) {
                           this.lexer.nextToken();
                           this.exprParser.names(values.getColumns(), values);
                           this.accept(Token.RPAREN);
                        }
                     }

                     if (this.lexer.token == Token.WHERE) {
                        return tableSource;
                     }

                     return this.parseTableSourceRest(tableSource);
                  }
               }
         }
      }

      SQLJoinTableSource.JoinType joinType = null;
      boolean natural = this.lexer.identifierEquals(FnvHash.Constants.NATURAL);
      if (natural) {
         this.lexer.nextToken();
      }

      boolean asof = false;
      if (this.lexer.identifierEquals(FnvHash.Constants.ASOF) && this.dbType == DbType.clickhouse) {
         this.lexer.nextToken();
         asof = true;
      }

      switch (this.lexer.token) {
         case LEFT:
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.SEMI)) {
               this.lexer.nextToken();
               joinType = SQLJoinTableSource.JoinType.LEFT_SEMI_JOIN;
            } else if (this.lexer.identifierEquals(FnvHash.Constants.ANTI)) {
               this.lexer.nextToken();
               joinType = SQLJoinTableSource.JoinType.LEFT_ANTI_JOIN;
            } else if (this.lexer.token == Token.OUTER) {
               this.lexer.nextToken();
               joinType = SQLJoinTableSource.JoinType.LEFT_OUTER_JOIN;
            } else {
               joinType = SQLJoinTableSource.JoinType.LEFT_OUTER_JOIN;
            }

            this.accept(Token.JOIN);
            break;
         case RIGHT:
            this.lexer.nextToken();
            if (this.lexer.token == Token.OUTER) {
               this.lexer.nextToken();
            }

            this.accept(Token.JOIN);
            joinType = SQLJoinTableSource.JoinType.RIGHT_OUTER_JOIN;
            break;
         case FULL:
            this.lexer.nextToken();
            if (this.lexer.token == Token.OUTER) {
               this.lexer.nextToken();
            }

            this.accept(Token.JOIN);
            joinType = SQLJoinTableSource.JoinType.FULL_OUTER_JOIN;
            break;
         case OUTER:
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.APPLY)) {
               this.lexer.nextToken();
               joinType = SQLJoinTableSource.JoinType.OUTER_APPLY;
            }
            break;
         case INNER:
            this.lexer.nextToken();
            this.accept(Token.JOIN);
            joinType = SQLJoinTableSource.JoinType.INNER_JOIN;
            break;
         case JOIN:
            this.lexer.nextToken();
            joinType = natural ? SQLJoinTableSource.JoinType.NATURAL_JOIN : SQLJoinTableSource.JoinType.JOIN;
            break;
         case COMMA:
            this.lexer.nextToken();
            joinType = SQLJoinTableSource.JoinType.COMMA;
            break;
         case STRAIGHT_JOIN:
         case IDENTIFIER:
            long hash = this.lexer.hash_lower;
            if (hash == FnvHash.Constants.STRAIGHT_JOIN) {
               this.lexer.nextToken();
               joinType = SQLJoinTableSource.JoinType.STRAIGHT_JOIN;
            } else if (hash == FnvHash.Constants.STRAIGHT) {
               this.lexer.nextToken();
               this.accept(Token.JOIN);
               joinType = SQLJoinTableSource.JoinType.STRAIGHT_JOIN;
            } else if (hash == FnvHash.Constants.CROSS) {
               this.lexer.nextToken();
               if (this.lexer.token == Token.JOIN) {
                  this.lexer.nextToken();
                  joinType = natural ? SQLJoinTableSource.JoinType.NATURAL_CROSS_JOIN : SQLJoinTableSource.JoinType.CROSS_JOIN;
               } else if (this.lexer.identifierEquals(FnvHash.Constants.APPLY)) {
                  this.lexer.nextToken();
                  joinType = SQLJoinTableSource.JoinType.CROSS_APPLY;
               }
            }
      }

      if (joinType != null) {
         SQLJoinTableSource join = new SQLJoinTableSource();
         join.setLeft(tableSource);
         join.setJoinType(joinType);
         if (asof) {
            join.setAsof(true);
         }

         boolean isBrace = false;
         if (SQLJoinTableSource.JoinType.COMMA == joinType && this.lexer.token == Token.LBRACE) {
            this.lexer.nextToken();
            this.acceptIdentifier("OJ");
            isBrace = true;
         }

         SQLTableSource rightTableSource = null;
         if (this.lexer.token == Token.LPAREN) {
            this.lexer.nextToken();
            if (this.lexer.token != Token.SELECT && (this.lexer.token != Token.FROM || this.dbType != DbType.odps && this.dbType != DbType.hive)) {
               rightTableSource = this.parseTableSource();
            } else {
               SQLSelect select = this.select();
               rightTableSource = new SQLSubqueryTableSource(select);
            }

            if (this.lexer.token == Token.UNION && rightTableSource instanceof SQLSubqueryTableSource) {
               SQLSelect select = ((SQLSubqueryTableSource)rightTableSource).getSelect();
               SQLSelectQuery query = this.queryRest(select.getQuery(), true);
               select.setQuery(query);
            }

            this.accept(Token.RPAREN);
            if (rightTableSource instanceof SQLValuesTableSource && (this.lexer.token == Token.AS || this.lexer.token == Token.IDENTIFIER) && rightTableSource.getAlias() == null && ((SQLValuesTableSource)rightTableSource).getColumns().size() == 0) {
               rightTableSource.setAlias(this.tableAlias(true));
               if (this.lexer.token == Token.LPAREN) {
                  this.lexer.nextToken();
                  this.exprParser.names(((SQLValuesTableSource)rightTableSource).getColumns(), rightTableSource);
                  this.accept(Token.RPAREN);
               }
            }
         } else if (this.lexer.token() != Token.TABLE) {
            if (this.lexer.identifierEquals(FnvHash.Constants.UNNEST)) {
               Lexer.SavePoint mark = this.lexer.mark();
               this.lexer.nextToken();
               if (this.lexer.token() == Token.LPAREN) {
                  this.lexer.nextToken();
                  SQLUnnestTableSource unnest = new SQLUnnestTableSource();
                  this.exprParser.exprList(unnest.getItems(), unnest);
                  this.accept(Token.RPAREN);
                  if (this.lexer.token() == Token.WITH) {
                     this.lexer.nextToken();
                     this.acceptIdentifier("ORDINALITY");
                     unnest.setOrdinality(true);
                  }

                  String alias = this.tableAlias();
                  unnest.setAlias(alias);
                  if (this.lexer.token() == Token.LPAREN) {
                     this.lexer.nextToken();
                     this.exprParser.names(unnest.getColumns(), unnest);
                     this.accept(Token.RPAREN);
                  }

                  SQLTableSource tableSrc = this.parseTableSourceRest(unnest);
                  rightTableSource = tableSrc;
               } else {
                  this.lexer.reset(mark);
               }
            } else if (this.lexer.token == Token.VALUES) {
               rightTableSource = this.parseValues();
            }

            if (rightTableSource == null) {
               boolean aliasToken = this.lexer.token == Token.LITERAL_ALIAS;
               SQLExpr expr = this.expr();
               if (aliasToken && expr instanceof SQLCharExpr) {
                  expr = new SQLIdentifierExpr(((SQLCharExpr)expr).getText());
               }

               SQLExprTableSource exprTableSource = new SQLExprTableSource(expr);
               if (expr instanceof SQLMethodInvokeExpr && this.lexer.token == Token.AS) {
                  this.lexer.nextToken();
                  String alias = this.tableAlias(true);
                  exprTableSource.setAlias(alias);
                  if (this.lexer.token == Token.LPAREN) {
                     this.lexer.nextToken();
                     this.exprParser.names(exprTableSource.getColumns(), exprTableSource);
                     this.accept(Token.RPAREN);
                  }
               }

               rightTableSource = exprTableSource;
            }

            rightTableSource = this.primaryTableSourceRest(rightTableSource);
         }

         if (this.lexer.token != Token.USING && !this.lexer.identifierEquals(FnvHash.Constants.USING)) {
            if (this.lexer.token != Token.STRAIGHT_JOIN && !this.lexer.identifierEquals(FnvHash.Constants.STRAIGHT_JOIN)) {
               if (rightTableSource.getAlias() == null && !(rightTableSource instanceof SQLValuesTableSource)) {
                  String tableAlias = this.tableAlias(false);
                  if (tableAlias != null) {
                     rightTableSource.setAlias(tableAlias);
                     if (this.lexer.token == Token.LPAREN) {
                        if (rightTableSource instanceof SQLSubqueryTableSource) {
                           this.lexer.nextToken();
                           List<SQLName> columns = ((SQLSubqueryTableSource)rightTableSource).getColumns();
                           this.exprParser.names(columns, rightTableSource);
                           this.accept(Token.RPAREN);
                        } else if (rightTableSource instanceof SQLExprTableSource && ((SQLExprTableSource)rightTableSource).getExpr() instanceof SQLMethodInvokeExpr) {
                           List<SQLName> columns = ((SQLExprTableSource)rightTableSource).getColumns();
                           if (columns.size() == 0) {
                              this.lexer.nextToken();
                              this.exprParser.names(columns, rightTableSource);
                              this.accept(Token.RPAREN);
                           }
                        }
                     }
                  }

                  rightTableSource = this.primaryTableSourceRest(rightTableSource);
               }
            } else {
               this.primaryTableSourceRest(rightTableSource);
            }
         } else {
            Lexer.SavePoint savePoint = this.lexer.mark();
            this.lexer.nextToken();
            if (this.lexer.token == Token.LPAREN) {
               this.lexer.nextToken();
               join.setRight(rightTableSource);
               this.exprParser.exprList(join.getUsing(), join);
               this.accept(Token.RPAREN);
            } else {
               if (this.lexer.token == Token.IDENTIFIER) {
                  this.lexer.reset(savePoint);
                  join.setRight(rightTableSource);
                  return join;
               }

               join.setAlias(this.tableAlias());
            }
         }

         if (this.lexer.token == Token.WITH) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);

            while(true) {
               SQLExpr hintExpr = this.expr();
               SQLExprHint hint = new SQLExprHint(hintExpr);
               hint.setParent(tableSource);
               rightTableSource.getHints().add(hint);
               if (this.lexer.token != Token.COMMA) {
                  this.accept(Token.RPAREN);
                  break;
               }

               this.lexer.nextToken();
            }
         }

         join.setRight(rightTableSource);
         if (!natural && !StringUtils.isEmpty(tableSource.getAlias()) && tableSource.aliasHashCode64() == FnvHash.Constants.NATURAL && DbType.mysql == this.dbType) {
            tableSource.setAlias(null);
            natural = true;
         }

         join.setNatural(natural);
         if (this.lexer.token == Token.ON) {
            this.lexer.nextToken();
            SQLExpr joinOn = this.expr();
            join.setCondition(joinOn);

            while(this.lexer.token == Token.ON) {
               this.lexer.nextToken();
               SQLExpr joinOn2 = this.expr();
               join.addCondition(joinOn2);
            }

            if (this.dbType == DbType.odps && this.lexer.identifierEquals(FnvHash.Constants.USING)) {
               SQLJoinTableSource.UDJ udj = new SQLJoinTableSource.UDJ();
               this.lexer.nextToken();
               udj.setFunction(this.lexer.stringVal());
               this.accept(Token.IDENTIFIER);
               this.accept(Token.LPAREN);
               this.exprParser.exprList(udj.getArguments(), udj);
               this.accept(Token.RPAREN);
               udj.setAlias(this.alias());
               this.accept(Token.AS);
               this.accept(Token.LPAREN);
               this.exprParser.names(udj.getColumns(), udj);
               this.accept(Token.RPAREN);
               join.setUdj(udj);
            }
         } else if (this.lexer.token == Token.USING || this.lexer.identifierEquals(FnvHash.Constants.USING)) {
            Lexer.SavePoint savePoint = this.lexer.mark();
            this.lexer.nextToken();
            if (this.lexer.token == Token.LPAREN) {
               this.lexer.nextToken();
               this.exprParser.exprList(join.getUsing(), join);
               this.accept(Token.RPAREN);
            } else {
               this.lexer.reset(savePoint);
            }
         }

         SQLTableSource tableSourceReturn = this.parseTableSourceRest(join);
         if (isBrace) {
            this.accept(Token.RBRACE);
         }

         return this.parseTableSourceRest(tableSourceReturn);
      } else if ((tableSource.aliasHashCode64() == FnvHash.Constants.LATERAL || this.lexer.token == Token.LATERAL) && this.lexer.token() == Token.VIEW) {
         return this.parseLateralView(tableSource);
      } else if (!this.lexer.identifierEquals(FnvHash.Constants.LATERAL) && this.lexer.token != Token.LATERAL) {
         return tableSource;
      } else {
         this.lexer.nextToken();
         return this.parseLateralView(tableSource);
      }
   }

   public SQLExpr expr() {
      return this.exprParser.expr();
   }

   public SQLOrderBy parseOrderBy() {
      return this.exprParser.parseOrderBy();
   }

   public void acceptKeyword(String ident) {
      if (this.lexer.token == Token.IDENTIFIER && ident.equalsIgnoreCase(this.lexer.stringVal())) {
         this.lexer.nextToken();
      } else {
         this.setErrorEndPos(this.lexer.pos());
         throw new ParserException("syntax error, expect " + ident + ", actual " + this.lexer.token + ", " + this.lexer.info());
      }
   }

   public void parseFetchClause(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token == Token.LIMIT) {
         SQLLimit limit = this.exprParser.parseLimit();
         queryBlock.setLimit(limit);
      } else {
         if (this.lexer.identifierEquals(FnvHash.Constants.OFFSET) || this.lexer.token == Token.OFFSET) {
            this.lexer.nextToken();
            SQLExpr offset = this.exprParser.expr();
            queryBlock.setOffset(offset);
            if (this.lexer.identifierEquals(FnvHash.Constants.ROW) || this.lexer.identifierEquals(FnvHash.Constants.ROWS)) {
               this.lexer.nextToken();
            }
         }

         if (this.lexer.token == Token.FETCH) {
            this.lexer.nextToken();
            if (this.lexer.token != Token.FIRST && this.lexer.token != Token.NEXT && !this.lexer.identifierEquals(FnvHash.Constants.NEXT)) {
               this.acceptIdentifier("FIRST");
            } else {
               this.lexer.nextToken();
            }

            SQLExpr first = this.exprParser.primary();
            queryBlock.setFirst(first);
            if (this.lexer.token == Token.PERCENT) {
               this.lexer.nextToken();
            }

            if (this.lexer.identifierEquals(FnvHash.Constants.ROW) || this.lexer.identifierEquals(FnvHash.Constants.ROWS)) {
               this.lexer.nextToken();
            }

            if (this.lexer.token != Token.ONLY && this.lexer.token != Token.WITH && this.lexer.token != Token.TIES) {
               this.acceptIdentifier("ONLY");
            } else {
               this.lexer.nextToken();
            }
         }

      }
   }

   protected void parseHierachical(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token == Token.CONNECT || this.lexer.identifierEquals(FnvHash.Constants.CONNECT)) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         if (this.lexer.token == Token.PRIOR || this.lexer.identifierEquals(FnvHash.Constants.PRIOR)) {
            this.lexer.nextToken();
            queryBlock.setPrior(true);
         }

         if (this.lexer.identifierEquals(FnvHash.Constants.NOCYCLE)) {
            queryBlock.setNoCycle(true);
            this.lexer.nextToken();
            if (this.lexer.token == Token.PRIOR) {
               this.lexer.nextToken();
               queryBlock.setPrior(true);
            }
         }

         queryBlock.setConnectBy(this.exprParser.expr());
      }

      if (this.lexer.token == Token.START || this.lexer.identifierEquals(FnvHash.Constants.START)) {
         this.lexer.nextToken();
         this.accept(Token.WITH);
         queryBlock.setStartWith(this.exprParser.expr());
      }

      if (this.lexer.token == Token.CONNECT || this.lexer.identifierEquals(FnvHash.Constants.CONNECT)) {
         this.lexer.nextToken();
         this.accept(Token.BY);
         if (this.lexer.token == Token.PRIOR || this.lexer.identifierEquals(FnvHash.Constants.PRIOR)) {
            this.lexer.nextToken();
            queryBlock.setPrior(true);
         }

         if (this.lexer.identifierEquals(FnvHash.Constants.NOCYCLE)) {
            queryBlock.setNoCycle(true);
            this.lexer.nextToken();
            if (this.lexer.token == Token.PRIOR || this.lexer.identifierEquals(FnvHash.Constants.PRIOR)) {
               this.lexer.nextToken();
               queryBlock.setPrior(true);
            }
         }

         queryBlock.setConnectBy(this.exprParser.expr());
      }

   }

   protected SQLTableSource parseLateralView(SQLTableSource tableSource) {
      this.accept(Token.VIEW);
      if (tableSource != null && "LATERAL".equalsIgnoreCase(tableSource.getAlias())) {
         tableSource.setAlias(null);
      }

      SQLLateralViewTableSource lateralViewTabSrc = new SQLLateralViewTableSource();
      lateralViewTabSrc.setTableSource(tableSource);
      if (this.lexer.token == Token.OUTER) {
         lateralViewTabSrc.setOuter(true);
         this.lexer.nextToken();
      }

      SQLMethodInvokeExpr udtf = (SQLMethodInvokeExpr)this.exprParser.expr();
      lateralViewTabSrc.setMethod(udtf);
      String alias = this.as();
      lateralViewTabSrc.setAlias(alias);
      if (this.lexer.token == Token.AS) {
         this.lexer.nextToken();
         this.exprParser.names(lateralViewTabSrc.getColumns());
      }

      return this.parseTableSourceRest(lateralViewTabSrc);
   }

   public SQLValuesTableSource parseValues() {
      this.accept(Token.VALUES);
      SQLValuesTableSource tableSource = new SQLValuesTableSource();

      while(true) {
         boolean isSingleValue = true;
         if (this.lexer.token == Token.ROW) {
            this.lexer.nextToken();
         }

         if (this.lexer.token() == Token.LPAREN) {
            this.accept(Token.LPAREN);
            isSingleValue = false;
         }

         SQLListExpr listExpr = new SQLListExpr();
         if (isSingleValue) {
            SQLExpr expr = this.expr();
            expr.setParent(listExpr);
            listExpr.getItems().add(expr);
         } else {
            this.exprParser.exprList(listExpr.getItems(), listExpr);
            this.accept(Token.RPAREN);
         }

         listExpr.setParent(tableSource);
         tableSource.getValues().add(listExpr);
         if (this.lexer.token() != Token.COMMA) {
            String alias = this.tableAlias();
            if (alias != null) {
               tableSource.setAlias(alias);
            }

            if (this.lexer.token() == Token.LPAREN) {
               this.lexer.nextToken();
               this.exprParser.names(tableSource.getColumns(), tableSource);
               this.accept(Token.RPAREN);
            }

            return tableSource;
         }

         this.lexer.nextToken();
      }
   }
}
