package com.alibaba.druid.sql.dialect.spark.parser;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLFlashbackExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLListExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
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.SQLValuesTableSource;
import com.alibaba.druid.sql.ast.statement.SQLWithSubqueryClause;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectQueryBlock;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectSubqueryTableSource;
import com.alibaba.druid.sql.dialect.spark.ast.expr.SparkSQLSubqueryTableSource;
import com.alibaba.druid.sql.dialect.spark.ast.expr.SparkSampleExpr;
import com.alibaba.druid.sql.dialect.spark.ast.expr.SparkSelectJoinTableSource;
import com.alibaba.druid.sql.dialect.spark.ast.expr.SparkSelectPivot;
import com.alibaba.druid.sql.dialect.spark.ast.expr.SparkSelectTableReference;
import com.alibaba.druid.sql.dialect.spark.ast.expr.SparkSelectTableSource;
import com.alibaba.druid.sql.dialect.spark.ast.expr.SparkTablePartition;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLSelectListCache;
import com.alibaba.druid.sql.parser.SQLSelectParser;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;

public class SparkSelectParser extends SQLSelectParser {
   public SparkSelectParser(String sql) {
      super((SQLExprParser)(new SparkExprParser(sql)));
      ((SparkExprParser)this.exprParser).setSparkSqlParser(this);
   }

   public SparkSelectParser(SQLExprParser exprParser) {
      super(exprParser);
      ((SparkExprParser)exprParser).setSparkSqlParser(this);
   }

   public SparkSelectParser(SQLExprParser exprParser, SQLSelectListCache selectListCache) {
      super(exprParser, selectListCache);
      ((SparkExprParser)exprParser).setSparkSqlParser(this);
   }

   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.lexer.token() == Token.LIMIT) {
         SQLLimit limit = this.exprParser.parseLimit();
         select.setLimit(limit);
      }

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

      return select;
   }

   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 {
         SQLSelectQueryBlock queryBlock = new SQLSelectQueryBlock();
         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 (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);
      }
   }

   public void parseFetchClause(OracleSelectQueryBlock 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.token() == Token.ROW || this.lexer.token() == Token.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();
            boolean percent = false;
            if (this.lexer.token() == Token.PERCENT) {
               percent = true;
               this.lexer.nextToken();
            }

            queryBlock.setFirst(first, percent);
            if (this.lexer.identifierEquals(FnvHash.Constants.ROW) || this.lexer.identifierEquals(FnvHash.Constants.ROWS) || this.lexer.token() == Token.ROW || this.lexer.token() == Token.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();
            }
         }

      }
   }

   public SQLSelectQuery queryRest(SQLSelectQuery selectQuery, boolean acceptUnion) {
      if (!acceptUnion) {
         return selectQuery;
      } else if (this.lexer.token() == Token.UNION) {
         SQLUnionQuery union;
         do {
            union = new SQLUnionQuery();
            union.setLeft(selectQuery);
            this.lexer.nextToken();
            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();
            }

            SQLSelectQuery right = this.query((SQLObject)null, false);
            union.setRight(right);
            selectQuery = union;
         } while(this.lexer.token() == Token.UNION);

         selectQuery = this.queryRest(union, true);
         return selectQuery;
      } else if (this.lexer.token() == Token.INTERSECT) {
         this.lexer.nextToken();
         SQLUnionQuery union = new SQLUnionQuery();
         union.setLeft(selectQuery);
         union.setOperator(SQLUnionOperator.INTERSECT);
         SQLSelectQuery right = this.query((SQLObject)null, false);
         union.setRight(right);
         return this.queryRest(union, true);
      } else if (this.lexer.token() == Token.MINUS) {
         this.lexer.nextToken();
         SQLUnionQuery union = new SQLUnionQuery();
         union.setLeft(selectQuery);
         if (this.lexer.token() == Token.ALL) {
            union.setOperator(SQLUnionOperator.MINUS_ALL);
            this.lexer.nextToken();
         } else if (this.lexer.token() == Token.DISTINCT) {
            union.setOperator(SQLUnionOperator.MINUS_DISTINCT);
            this.lexer.nextToken();
         } else {
            union.setOperator(SQLUnionOperator.MINUS);
         }

         SQLSelectQuery right = this.query((SQLObject)null, false);
         union.setRight(right);
         return this.queryRest(union, true);
      } else if (this.lexer.token() == Token.EXCEPT) {
         this.lexer.nextToken();
         SQLUnionQuery union = new SQLUnionQuery();
         union.setLeft(selectQuery);
         if (this.lexer.token() == Token.ALL) {
            union.setOperator(SQLUnionOperator.EXCEPT_ALL);
            this.lexer.nextToken();
         } else if (this.lexer.token() == Token.DISTINCT) {
            union.setOperator(SQLUnionOperator.EXCEPT_DISTINCT);
            this.lexer.nextToken();
         } else {
            union.setOperator(SQLUnionOperator.EXCEPT);
         }

         SQLSelectQuery right = this.query((SQLObject)null, false);
         union.setRight(right);
         return this.queryRest(union, true);
      } else {
         return selectQuery;
      }
   }

   public SQLTableSource parseTableSource() {
      if (this.lexer.token() != Token.VALUES) {
         SQLTableSource tableSource = this.parseTableSourcePrimary();
         return tableSource instanceof SparkSelectTableSource ? this.parseTableSourceRest((SparkSelectTableSource)tableSource) : this.parseTableSourceRest(tableSource);
      } 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 if (this.lexer.token() != Token.GROUP && this.lexer.token() != Token.ORDER && this.lexer.token() != Token.LEFT && this.lexer.token() != Token.JOIN && this.lexer.token() != Token.RIGHT && this.lexer.token() != Token.FULL && this.lexer.token() != Token.NATURAL && this.lexer.token() != Token.SEMI && this.lexer.token() != Token.CROSS && this.lexer.token() != Token.INNER && this.lexer.token() != Token.OUTER && this.lexer.token() != Token.EOF) {
                  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;
               } else {
                  return tableSource;
               }
            }

            this.lexer.nextToken();
         }
      }
   }

   public SQLTableSource parseTableSourcePrimary() {
      if (this.lexer.token() != Token.LPAREN) {
         if (this.lexer.token() == Token.SELECT) {
            throw new ParserException("TODO. " + this.lexer.info());
         } else {
            SparkSelectTableReference tableReference = new SparkSelectTableReference();
            if (this.lexer.identifierEquals("ONLY")) {
               this.lexer.nextToken();
               tableReference.setOnly(true);
               this.accept(Token.LPAREN);
               this.parseTableSourceQueryTableExpr(tableReference);
               this.accept(Token.RPAREN);
            } else {
               this.parseTableSourceQueryTableExpr(tableReference);
               this.parsePivot(tableReference);
            }

            return tableReference;
         }
      } else {
         this.lexer.nextToken();
         SparkSelectTableSource tableSource;
         if (this.lexer.token() != Token.SELECT && this.lexer.token() != Token.WITH) {
            if (this.lexer.token() != Token.LPAREN) {
               if (this.lexer.token() != Token.IDENTIFIER && this.lexer.token() != Token.LITERAL_ALIAS) {
                  throw new ParserException("TODO :" + this.lexer.info());
               }

               SQLTableSource identTable = this.parseTableSource();
               this.accept(Token.RPAREN);
               this.parsePivot((SparkSelectTableSource)identTable);
               return identTable;
            }

            tableSource = (SparkSelectTableSource)this.parseTableSource();
         } else {
            tableSource = new SparkSQLSubqueryTableSource(this.select());
         }

         this.accept(Token.RPAREN);
         if ((this.lexer.token() == Token.UNION || this.lexer.token() == Token.MINUS || this.lexer.token() == Token.EXCEPT) && tableSource instanceof OracleSelectSubqueryTableSource) {
            OracleSelectSubqueryTableSource selectSubqueryTableSource = (OracleSelectSubqueryTableSource)tableSource;
            SQLSelect select = selectSubqueryTableSource.getSelect();
            SQLSelectQuery selectQuery = this.queryRest(select.getQuery(), true);
            select.setQuery(selectQuery);
         }

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

   protected void parseTableSourceQueryTableExpr(SQLExprTableSource tableSource) {
      SparkSelectTableReference tableReference = (SparkSelectTableReference)tableSource;
      tableReference.setExpr(this.exprParser.expr());
      if (this.lexer.identifierEquals("TABLESAMPLE")) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         SparkSampleExpr sample = new SparkSampleExpr();
         if (this.lexer.identifierEquals("BUCKET")) {
            this.lexer.nextToken();
            sample.setBucket(this.expr());
            this.lexer.nextToken();
            this.lexer.nextToken();
            sample.setBucketOutOf(this.expr());
         } else {
            SQLExpr numExpr = this.expr();
            if (this.lexer.token() != Token.ROWS && !this.lexer.identifierEquals("ROWS")) {
               if (this.lexer.token() == Token.PERCENT || this.lexer.identifierEquals("PERCENT")) {
                  sample.setPercent(numExpr);
               }
            } else {
               sample.setRows(numExpr);
            }

            this.lexer.nextToken();
         }

         this.accept(Token.RPAREN);
         tableReference.setSample(sample);
      }

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

         SQLExpr col;
         SparkTablePartition partition;
         SQLExpr value;
         for(partition = new SparkTablePartition(); this.lexer.token() != Token.RPAREN; partition.addDef(col, value)) {
            this.lexer.nextToken();
            value = null;
            col = this.exprParser.expr();
            if (col instanceof SQLBinaryOpExpr) {
               SQLBinaryOpExpr b = (SQLBinaryOpExpr)col;
               value = b.getRight();
               col = b.getLeft();
            } else {
               this.accept(Token.EQ);
               value = this.exprParser.expr();
            }
         }

         this.lexer.nextToken();
         tableReference.setPartition(partition);
      }

      if (this.lexer.identifierEquals("VERSIONS")) {
         SQLBetweenExpr betweenExpr = new SQLBetweenExpr();
         betweenExpr.setTestExpr(new SQLIdentifierExpr("VERSIONS"));
         this.lexer.nextToken();
         this.accept(Token.BETWEEN);
         SQLFlashbackExpr start = new SQLFlashbackExpr();
         if (this.lexer.identifierEquals("SCN")) {
            this.lexer.nextToken();
            start.setType(SQLFlashbackExpr.Type.SCN);
         } else {
            this.acceptIdentifier("TIMESTAMP");
            start.setType(SQLFlashbackExpr.Type.TIMESTAMP);
         }

         SQLBinaryOpExpr binaryExpr = (SQLBinaryOpExpr)this.exprParser.expr();
         if (binaryExpr.getOperator() != SQLBinaryOperator.BooleanAnd) {
            throw new ParserException("syntax error : " + binaryExpr.getOperator() + ", " + this.lexer.info());
         }

         start.setExpr(binaryExpr.getLeft());
         betweenExpr.setBeginExpr(start);
         betweenExpr.setEndExpr(binaryExpr.getRight());
         tableReference.setFlashback(betweenExpr);
      }

   }

   private SQLExpr flashback() {
      this.accept(Token.OF);
      if (this.lexer.identifierEquals("SCN")) {
         this.lexer.nextToken();
         return new SQLFlashbackExpr(SQLFlashbackExpr.Type.SCN, this.expr());
      } else if (this.lexer.identifierEquals("SNAPSHOT")) {
         return this.expr();
      } else {
         this.lexer.nextToken();
         return new SQLFlashbackExpr(SQLFlashbackExpr.Type.TIMESTAMP, this.expr());
      }
   }

   protected SQLTableSource primaryTableSourceRest(SQLTableSource tableSource) {
      if (tableSource instanceof SparkSelectTableSource && this.lexer.token() == Token.AS) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.OF) {
            ((SparkSelectTableSource)tableSource).setFlashback(this.flashback());
         }

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

      return tableSource;
   }

   protected SQLTableSource parseTableSourceRest(SparkSelectTableSource tableSource) {
      if (this.lexer.token() == Token.AS) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.OF) {
            tableSource.setFlashback(this.flashback());
            return this.parseTableSourceRest(tableSource);
         }

         tableSource.setAlias(this.tableAlias(true));
      } else if (tableSource.getAlias() != null && tableSource.getAlias().length() != 0) {
         if (this.lexer.token() == Token.OUTER) {
         }
      } else if (this.lexer.token() == Token.IDENTIFIER && (this.lexer.identifierEquals("SEMI") || this.lexer.identifierEquals("ANTI"))) {
         this.lexer.mark();
         this.lexer.nextToken();
         if (this.lexer.token() != Token.JOIN) {
            this.lexer.reset();
            String tableAlias = this.tableAlias();
            tableSource.setAlias(tableAlias);
         } else {
            this.lexer.reset();
         }
      } else if (this.lexer.token() != Token.LEFT && this.lexer.token() != Token.INNER && this.lexer.token() != Token.RIGHT && this.lexer.token() != Token.FULL && this.lexer.token() != Token.JOIN && this.lexer.token() != Token.PIVOT) {
         String tableAlias = this.tableAlias();
         tableSource.setAlias(tableAlias);
      }

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

      SQLJoinTableSource.JoinType joinType = null;
      if (this.lexer.token() == Token.LEFT) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            this.accept(Token.JOIN);
            joinType = SQLJoinTableSource.JoinType.LEFT_OUTER_JOIN;
         } else if (this.lexer.identifierEquals("SEMI")) {
            this.lexer.nextToken();
            this.accept(Token.JOIN);
            joinType = SQLJoinTableSource.JoinType.LEFT_SEMI_JOIN;
         } else if (this.lexer.identifierEquals("ANTI")) {
            this.lexer.nextToken();
            this.accept(Token.JOIN);
            joinType = SQLJoinTableSource.JoinType.LEFT_ANTI_JOIN;
         } else {
            this.accept(Token.JOIN);
            joinType = SQLJoinTableSource.JoinType.LEFT_OUTER_JOIN;
         }
      }

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

         this.accept(Token.JOIN);
         joinType = SQLJoinTableSource.JoinType.RIGHT_OUTER_JOIN;
      }

      if (this.lexer.token() == Token.FULL) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            joinType = SQLJoinTableSource.JoinType.FULL_OUTER_JOIN;
         } else {
            joinType = SQLJoinTableSource.JoinType.FULL_JOIN;
         }

         this.accept(Token.JOIN);
      }

      boolean natural = this.lexer.token() == Token.NATURAL;
      boolean full = false;
      boolean left = false;
      boolean isright = false;
      if (natural) {
         this.lexer.nextToken();
      }

      if (this.lexer.token() == Token.FULL) {
         this.lexer.nextToken();
         if (natural) {
            full = true;
         }

         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_FULL_OUTER_JOIN;
               this.accept(Token.JOIN);
            }
         }
      }

      if (this.lexer.identifierEquals("SEMI")) {
         this.lexer.nextToken();
         if (natural) {
            joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_SEMI_JOIN;
            this.accept(Token.JOIN);
         } else {
            joinType = SQLJoinTableSource.JoinType.LEFT_SEMI_JOIN;
            this.accept(Token.JOIN);
         }
      } else if (this.lexer.identifierEquals("ANTI")) {
         this.lexer.nextToken();
         if (natural) {
            joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_ANTI_JOIN;
            this.accept(Token.JOIN);
         } else {
            joinType = SQLJoinTableSource.JoinType.LEFT_ANTI_JOIN;
            this.accept(Token.JOIN);
         }
      }

      if (this.lexer.token() == Token.LEFT) {
         this.lexer.nextToken();
         if (natural) {
            left = true;
         }

         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_OUTER_JOIN;
               this.accept(Token.JOIN);
            } else {
               joinType = SQLJoinTableSource.JoinType.LEFT_OUTER_JOIN;
               this.accept(Token.JOIN);
            }
         } else if (this.lexer.identifierEquals("SEMI")) {
            this.lexer.nextToken();
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_SEMI_JOIN;
               this.accept(Token.JOIN);
            } else {
               joinType = SQLJoinTableSource.JoinType.LEFT_SEMI_JOIN;
               this.accept(Token.JOIN);
            }
         } else if (this.lexer.identifierEquals("ANTI")) {
            this.lexer.nextToken();
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_ANTI_JOIN;
               this.accept(Token.JOIN);
            } else {
               joinType = SQLJoinTableSource.JoinType.LEFT_ANTI_JOIN;
               this.accept(Token.JOIN);
            }
         } else {
            this.accept(Token.JOIN);
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_OUTER_JOIN;
            } else {
               joinType = SQLJoinTableSource.JoinType.LEFT_OUTER_JOIN;
            }
         }
      }

      if (this.lexer.token() == Token.RIGHT) {
         this.lexer.nextToken();
         if (natural) {
            isright = true;
         }

         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_RIGHT_OUTER_JOIN;
               this.accept(Token.JOIN);
            } else {
               joinType = SQLJoinTableSource.JoinType.RIGHT_OUTER_JOIN;
               this.accept(Token.JOIN);
            }
         }
      }

      if (this.lexer.token() == Token.OUTER) {
         this.lexer.nextToken();
         if (natural) {
            joinType = SQLJoinTableSource.JoinType.NATURAL_OUTER_JOIN;
            this.accept(Token.JOIN);
         }
      }

      if (this.lexer.token() == Token.INNER) {
         this.lexer.nextToken();
         this.accept(Token.JOIN);
         if (natural) {
            joinType = SQLJoinTableSource.JoinType.NATURAL_INNER_JOIN;
         } else {
            joinType = SQLJoinTableSource.JoinType.INNER_JOIN;
         }
      }

      if (this.lexer.token() == Token.CROSS) {
         this.lexer.nextToken();
         this.accept(Token.JOIN);
         joinType = SQLJoinTableSource.JoinType.CROSS_JOIN;
      }

      if (this.lexer.token() == Token.JOIN) {
         this.lexer.nextToken();
         if (natural) {
            if (full) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_FULL_JOIN;
            } else if (left) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_JOIN;
            } else if (isright) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_RIGHT_JOIN;
            } else {
               joinType = SQLJoinTableSource.JoinType.NATURAL_JOIN;
            }
         } else {
            joinType = SQLJoinTableSource.JoinType.JOIN;
         }
      }

      if (this.lexer.token() == Token.COMMA) {
         this.lexer.nextToken();
         joinType = SQLJoinTableSource.JoinType.COMMA;
      }

      if (joinType != null) {
         SparkSelectJoinTableSource join = new SparkSelectJoinTableSource();
         join.setLeft(tableSource);
         join.setJoinType(joinType);
         SQLTableSource right = this.parseTableSourcePrimary();
         String tableAlias = this.tableAlias();
         right.setAlias(tableAlias);
         join.setRight(right);
         if (this.lexer.token() == Token.ON) {
            this.lexer.nextToken();
            join.setCondition(this.exprParser.expr());
            if (this.lexer.token() == Token.ON && tableSource instanceof SQLJoinTableSource && ((SQLJoinTableSource)tableSource).getCondition() == null) {
               this.lexer.nextToken();
               SQLExpr leftCondidition = this.exprParser.expr();
               ((SQLJoinTableSource)tableSource).setCondition(leftCondidition);
            }
         } else if (this.lexer.token() == Token.USING) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            this.exprParser.exprList(join.getUsing(), join);
            this.accept(Token.RPAREN);
         }

         this.parsePivot(join);
         return this.parseTableSourceRest(join);
      } else {
         if (this.lexer.identifierEquals(FnvHash.Constants.PIVOT)) {
            this.parsePivot(tableSource);
         }

         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);
         }
      }
   }

   private void parsePivot(SparkSelectTableSource tableSource) {
      if (this.lexer.identifierEquals(FnvHash.Constants.PIVOT)) {
         this.lexer.nextToken();
         SparkSelectPivot pivot = new SparkSelectPivot();
         if (this.lexer.identifierEquals("XML")) {
            this.lexer.nextToken();
            pivot.setXml(true);
         }

         this.accept(Token.LPAREN);

         while(true) {
            SparkSelectPivot.Item item = new SparkSelectPivot.Item();
            item.setExpr((SQLAggregateExpr)this.exprParser.expr());
            item.setAlias(this.as());
            pivot.addItem(item);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.FOR);
               if (this.lexer.token() != Token.LPAREN) {
                  pivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
                  this.lexer.nextToken();
               } else {
                  this.lexer.nextToken();

                  while(true) {
                     pivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
                     this.lexer.nextToken();
                     if (this.lexer.token() != Token.COMMA) {
                        this.accept(Token.RPAREN);
                        break;
                     }

                     this.lexer.nextToken();
                  }
               }

               this.accept(Token.IN);
               this.accept(Token.LPAREN);
               if (this.lexer.token() == Token.SELECT) {
                  SQLExpr expr = this.exprParser.expr();
                  item = new SparkSelectPivot.Item();
                  item.setExpr(expr);
                  pivot.getPivotIn().add(item);
               } else {
                  while(true) {
                     item = new SparkSelectPivot.Item();
                     item.setExpr(this.exprParser.expr());
                     item.setAlias(this.as());
                     pivot.getPivotIn().add(item);
                     if (this.lexer.token() != Token.COMMA) {
                        break;
                     }

                     this.lexer.nextToken();
                  }
               }

               this.accept(Token.RPAREN);
               this.accept(Token.RPAREN);
               tableSource.setPivot(pivot);
               break;
            }

            this.lexer.nextToken();
         }
      }

   }

   protected void parseInto(OracleSelectQueryBlock x) {
      if (this.lexer.token() == Token.INTO) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.FROM) {
            return;
         }

         SQLExpr expr = this.expr();
         if (this.lexer.token() != Token.COMMA) {
            x.setInto(expr);
            return;
         }

         SQLListExpr list = new SQLListExpr();
         list.addItem(expr);

         while(this.lexer.token() == Token.COMMA) {
            this.lexer.nextToken();
            list.addItem(this.expr());
         }

         x.setInto(list);
      }

   }

   private void parseHints(OracleSelectQueryBlock queryBlock) {
      this.exprParser.parseHints(queryBlock.getHints());
   }

   public void setDbType(DbType dbType) {
      this.dbType = dbType;
   }
}
