package com.chenyang.druid.sql.dialect.mysql.parser;

import com.chenyang.druid.sql.ast.SQLCommentHint;
import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLLimit;
import com.chenyang.druid.sql.ast.SQLName;
import com.chenyang.druid.sql.ast.SQLObject;
import com.chenyang.druid.sql.ast.SQLOrderBy;
import com.chenyang.druid.sql.ast.TDDLHint;
import com.chenyang.druid.sql.ast.expr.SQLAggregateExpr;
import com.chenyang.druid.sql.ast.expr.SQLAllColumnExpr;
import com.chenyang.druid.sql.ast.expr.SQLIdentifierExpr;
import com.chenyang.druid.sql.ast.expr.SQLListExpr;
import com.chenyang.druid.sql.ast.expr.SQLLiteralExpr;
import com.chenyang.druid.sql.ast.expr.SQLSizeExpr;
import com.chenyang.druid.sql.ast.statement.SQLExprTableSource;
import com.chenyang.druid.sql.ast.statement.SQLSelect;
import com.chenyang.druid.sql.ast.statement.SQLSelectItem;
import com.chenyang.druid.sql.ast.statement.SQLSelectQuery;
import com.chenyang.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.chenyang.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.chenyang.druid.sql.ast.statement.SQLTableSampling;
import com.chenyang.druid.sql.ast.statement.SQLTableSource;
import com.chenyang.druid.sql.ast.statement.SQLUnionQuery;
import com.chenyang.druid.sql.ast.statement.SQLUnionQueryTableSource;
import com.chenyang.druid.sql.ast.statement.SQLUnnestTableSource;
import com.chenyang.druid.sql.ast.statement.SQLUpdateSetItem;
import com.chenyang.druid.sql.ast.statement.SQLValuesTableSource;
import com.chenyang.druid.sql.dialect.mysql.ast.MySqlForceIndexHint;
import com.chenyang.druid.sql.dialect.mysql.ast.MySqlIgnoreIndexHint;
import com.chenyang.druid.sql.dialect.mysql.ast.MySqlIndexHint;
import com.chenyang.druid.sql.dialect.mysql.ast.MySqlIndexHintImpl;
import com.chenyang.druid.sql.dialect.mysql.ast.MySqlUseIndexHint;
import com.chenyang.druid.sql.dialect.mysql.ast.clause.MySqlTableTableSource;
import com.chenyang.druid.sql.dialect.mysql.ast.expr.MySqlOutFileExpr;
import com.chenyang.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.chenyang.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement;
import com.chenyang.druid.sql.dialect.mysql.ast.statement.MySqlUpdateTableSource;
import com.chenyang.druid.sql.parser.Lexer;
import com.chenyang.druid.sql.parser.ParserException;
import com.chenyang.druid.sql.parser.SQLExprParser;
import com.chenyang.druid.sql.parser.SQLParserFeature;
import com.chenyang.druid.sql.parser.SQLSelectListCache;
import com.chenyang.druid.sql.parser.SQLSelectParser;
import com.chenyang.druid.sql.parser.Token;
import com.chenyang.druid.util.FnvHash;
import java.util.ArrayList;
import java.util.List;

public class MySqlSelectParser extends SQLSelectParser {
   protected boolean returningFlag;
   protected MySqlUpdateStatement updateStmt;

   public MySqlSelectParser(SQLExprParser exprParser) {
      super(exprParser);
      this.returningFlag = false;
   }

   public MySqlSelectParser(SQLExprParser exprParser, SQLSelectListCache selectListCache) {
      super(exprParser, selectListCache);
      this.returningFlag = false;
   }

   public MySqlSelectParser(String sql) {
      this((SQLExprParser)(new MySqlExprParser(sql)));
   }

   public void parseFrom(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token() != Token.EOF && this.lexer.token() != Token.SEMI && this.lexer.token() != Token.ORDER && this.lexer.token() != Token.RPAREN && this.lexer.token() != Token.UNION) {
         Boolean tableQuery = (Boolean)queryBlock.getAttribute("$table-query");
         if (this.lexer.token() == Token.FROM || tableQuery != null && tableQuery) {
            if (tableQuery != null && tableQuery) {
               SQLSelectItem item = new SQLSelectItem();
               SQLAllColumnExpr expr = new SQLAllColumnExpr();
               item.setExpr(expr);
               queryBlock.getSelectList().add(item);
            }

            this.lexer.nextTokenIdent();

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

            if (this.lexer.token() != Token.UPDATE) {
               SQLTableSource from = this.parseTableSource(queryBlock);
               queryBlock.setFrom(from);
            } else {
               this.updateStmt = this.parseUpdateStatment();
               List<SQLExpr> returnning = this.updateStmt.getReturning();

               for(SQLSelectItem item : queryBlock.getSelectList()) {
                  SQLExpr itemExpr = item.getExpr();
                  itemExpr.setParent(this.updateStmt);
                  returnning.add(itemExpr);
               }

               this.returningFlag = true;
            }
         } else {
            for(SQLSelectItem item : queryBlock.getSelectList()) {
               SQLExpr expr = item.getExpr();
               if (expr instanceof SQLAggregateExpr) {
                  throw new ParserException("syntax error, expect " + Token.FROM + ", actual " + this.lexer.token() + ", " + this.lexer.info());
               }
            }

         }
      }
   }

   public SQLSelectQuery query(SQLObject parent, boolean acceptUnion) {
      List<SQLCommentHint> hints = null;
      if (this.lexer.token() == Token.HINT) {
         hints = this.exprParser.parseHints();
      }

      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         SQLSelectQuery select = this.query();
         select.setBracket(true);
         this.accept(Token.RPAREN);
         return this.queryRest(select, acceptUnion);
      } else if (this.lexer.token() == Token.VALUES) {
         return this.valuesQuery(acceptUnion);
      } else {
         MySqlSelectQueryBlock queryBlock = new MySqlSelectQueryBlock();
         queryBlock.setParent(parent);

         class QueryHintHandler implements Lexer.CommentHandler {
            private MySqlSelectQueryBlock queryBlock;
            private Lexer lexer;

            QueryHintHandler(MySqlSelectQueryBlock queryBlock, Lexer lexer) {
               this.queryBlock = queryBlock;
               this.lexer = lexer;
            }

            public boolean handle(Token lastToken, String comment) {
               if (this.lexer.isEnabled(SQLParserFeature.TDDLHint) && (comment.startsWith("+ TDDL") || comment.startsWith("+TDDL") || comment.startsWith("!TDDL") || comment.startsWith("TDDL"))) {
                  SQLCommentHint hint = new TDDLHint(comment);
                  if (this.lexer.getCommentCount() > 0) {
                     hint.addBeforeComment(this.lexer.getComments());
                  }

                  this.queryBlock.getHints().add(hint);
                  this.lexer.nextToken();
               }

               return false;
            }
         }

         this.lexer.setCommentHandler(new QueryHintHandler(queryBlock, this.lexer));
         if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
            queryBlock.addBeforeComment(this.lexer.readAndResetComments());
         }

         if (this.lexer.token() == Token.SELECT && this.selectListCache != null) {
            this.selectListCache.match(this.lexer, queryBlock);
         }

         if (this.lexer.token() == Token.SELECT) {
            this.lexer.nextTokenValue();

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

            while(true) {
               Token token = this.lexer.token();
               if (token == Token.DISTINCT) {
                  queryBlock.setDistionOption(2);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.DISTINCTROW)) {
                  queryBlock.setDistionOption(4);
                  this.lexer.nextToken();
               } else if (token == Token.ALL) {
                  queryBlock.setDistionOption(1);
                  this.lexer.nextToken();
               } else if (token == Token.UNIQUE) {
                  queryBlock.setDistionOption(3);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.HIGH_PRIORITY)) {
                  queryBlock.setHignPriority(true);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.STRAIGHT_JOIN)) {
                  queryBlock.setStraightJoin(true);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.SQL_SMALL_RESULT)) {
                  queryBlock.setSmallResult(true);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.SQL_BIG_RESULT)) {
                  queryBlock.setBigResult(true);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.SQL_BUFFER_RESULT)) {
                  queryBlock.setBufferResult(true);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.SQL_CACHE)) {
                  queryBlock.setCache(true);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.SQL_NO_CACHE)) {
                  queryBlock.setCache(false);
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.SQL_CALC_FOUND_ROWS)) {
                  queryBlock.setCalcFoundRows(true);
                  this.lexer.nextToken();
               } else {
                  if (!this.lexer.identifierEquals(FnvHash.Constants.TOP)) {
                     break;
                  }

                  Lexer.SavePoint mark = this.lexer.mark();
                  this.lexer.nextToken();
                  if (this.lexer.token() == Token.LITERAL_INT) {
                     SQLLimit limit = new SQLLimit(this.lexer.integerValue().intValue());
                     queryBlock.setLimit(limit);
                     this.lexer.nextToken();
                  } else if (this.lexer.token() == Token.DOT) {
                     this.lexer.reset(mark);
                     break;
                  }
               }
            }

            this.parseSelectList(queryBlock);
            if (this.lexer.identifierEquals(FnvHash.Constants.FORCE)) {
               this.lexer.nextToken();
               this.accept(Token.PARTITION);
               SQLName partition = this.exprParser.name();
               queryBlock.setForcePartition(partition);
            }

            this.parseInto(queryBlock);
         }

         if (this.lexer.token() == Token.TABLE) {
            queryBlock.putAttribute("$table-query", true);
         }

         this.parseFrom(queryBlock);
         this.parseWhere(queryBlock);
         this.parseHierachical(queryBlock);
         if (this.lexer.token() == Token.GROUP || this.lexer.token() == Token.HAVING) {
            this.parseGroupBy(queryBlock);
         }

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

         if (this.lexer.token() == Token.ORDER) {
            queryBlock.setOrderBy(this.exprParser.parseOrderBy());
         }

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

         if (this.lexer.token() == Token.FETCH) {
            Lexer.SavePoint mark = this.lexer.mark();
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.NEXT)) {
               this.lexer.nextToken();
               SQLExpr rows = this.exprParser.primary();
               queryBlock.setLimit(new SQLLimit(rows));
               this.acceptIdentifier("ROWS");
               this.acceptIdentifier("ONLY");
            } else {
               this.lexer.reset(mark);
            }
         }

         if (this.lexer.token() == Token.PROCEDURE) {
            this.lexer.nextToken();
            throw new ParserException("TODO. " + this.lexer.info());
         } else {
            if (this.lexer.token() == Token.INTO) {
               this.parseInto(queryBlock);
            }

            if (this.lexer.token() == Token.FOR) {
               this.lexer.nextToken();
               if (this.lexer.token() == Token.UPDATE) {
                  this.lexer.nextToken();
                  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);
                  }

                  if (this.lexer.identifierEquals(FnvHash.Constants.SKIP)) {
                     this.lexer.nextToken();
                     this.acceptIdentifier("LOCKED");
                     queryBlock.setSkipLocked(true);
                  }
               } else {
                  this.acceptIdentifier("SHARE");
                  queryBlock.setForShare(true);
               }
            }

            if (this.lexer.token() == Token.LOCK) {
               this.lexer.nextToken();
               this.accept(Token.IN);
               this.acceptIdentifier("SHARE");
               this.acceptIdentifier("MODE");
               queryBlock.setLockInShareMode(true);
            }

            if (hints != null) {
               queryBlock.setHints(hints);
            }

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

   public SQLTableSource parseTableSource() {
      return this.parseTableSource((SQLObject)null);
   }

   public SQLTableSource parseTableSource(SQLObject parent) {
      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         List hints = null;
         if (this.lexer.token() == Token.HINT) {
            hints = new ArrayList();
            this.exprParser.parseHints(hints);
         }

         SQLTableSource tableSource;
         if (this.lexer.token() != Token.SELECT && this.lexer.token() != Token.WITH) {
            if (this.lexer.token() == Token.LPAREN) {
               tableSource = this.parseTableSource();
               if (this.lexer.token() != Token.RPAREN && tableSource instanceof SQLSubqueryTableSource) {
                  SQLSubqueryTableSource sqlSubqueryTableSource = (SQLSubqueryTableSource)tableSource;
                  SQLSelect select = sqlSubqueryTableSource.getSelect();
                  SQLSelectQuery query = this.queryRest(select.getQuery(), true);
                  if (query instanceof SQLUnionQuery && select.getWithSubQuery() == null) {
                     select.getQuery().setBracket(true);
                     tableSource = new SQLUnionQueryTableSource((SQLUnionQuery)query);
                  } else {
                     tableSource = new SQLSubqueryTableSource(select);
                  }

                  if (hints != null) {
                     tableSource.getHints().addAll(hints);
                  }
               } else if (this.lexer.token() != Token.RPAREN && tableSource instanceof SQLUnionQueryTableSource) {
                  SQLUnionQueryTableSource unionQueryTableSource = (SQLUnionQueryTableSource)tableSource;
                  SQLUnionQuery unionQuery = unionQueryTableSource.getUnion();
                  SQLSelectQuery query = this.queryRest(unionQuery, true);
                  if (query instanceof SQLUnionQuery) {
                     unionQuery.setBracket(true);
                     tableSource = new SQLUnionQueryTableSource((SQLUnionQuery)query);
                  } else {
                     tableSource = new SQLSubqueryTableSource(unionQuery);
                  }

                  if (hints != null) {
                     tableSource.getHints().addAll(hints);
                  }
               }

               this.accept(Token.RPAREN);
            } else {
               tableSource = this.parseTableSource();
               this.accept(Token.RPAREN);
               if (this.lexer.token() == Token.AS && tableSource instanceof SQLValuesTableSource) {
                  this.lexer.nextToken();
                  String alias = this.lexer.stringVal();
                  this.lexer.nextToken();
                  tableSource.setAlias(alias);
                  this.accept(Token.LPAREN);
                  SQLValuesTableSource values = (SQLValuesTableSource)tableSource;
                  this.exprParser.names(values.getColumns(), tableSource);
                  this.accept(Token.RPAREN);
               }
            }
         } else {
            SQLSelect select = this.select();
            this.accept(Token.RPAREN);
            SQLSelectQueryBlock innerQuery = select.getQueryBlock();
            boolean noOrderByAndLimit = innerQuery instanceof SQLSelectQueryBlock && innerQuery.getOrderBy() == null && ((SQLSelectQueryBlock)select.getQuery()).getLimit() == null;
            if (this.lexer.token() == Token.LIMIT) {
               SQLLimit limit = this.exprParser.parseLimit();
               if (parent != null && parent instanceof SQLSelectQueryBlock) {
                  ((SQLSelectQueryBlock)parent).setLimit(limit);
               }

               if (parent == null && noOrderByAndLimit) {
                  innerQuery.setLimit(limit);
               }
            } else if (this.lexer.token() == Token.ORDER) {
               SQLOrderBy orderBy = this.exprParser.parseOrderBy();
               if (parent != null && parent instanceof SQLSelectQueryBlock) {
                  ((SQLSelectQueryBlock)parent).setOrderBy(orderBy);
               }

               if (parent == null && noOrderByAndLimit) {
                  innerQuery.setOrderBy(orderBy);
               }
            }

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

            if (hints != null) {
               tableSource.getHints().addAll(hints);
            }
         }

         return this.parseTableSourceRest(tableSource);
      } else if (this.lexer.token() == Token.LBRACE) {
         this.accept(Token.LBRACE);
         this.acceptIdentifier("OJ");
         SQLTableSource tableSrc = this.parseTableSource();
         this.accept(Token.RBRACE);
         tableSrc = this.parseTableSourceRest(tableSrc);
         if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
            tableSrc.addAfterComment(this.lexer.readAndResetComments());
         }

         return tableSrc;
      } else if (this.lexer.token() == Token.VALUES) {
         return this.parseValues();
      } else if (this.lexer.token() == Token.UPDATE) {
         SQLTableSource tableSource = new MySqlUpdateTableSource(this.parseUpdateStatment());
         return this.parseTableSourceRest(tableSource);
      } else if (this.lexer.token() == Token.SELECT) {
         throw new ParserException("TODO. " + this.lexer.info());
      } else {
         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);
               return tableSrc;
            }

            this.lexer.reset(mark);
         }

         Boolean isTableSource = parent == null ? null : (Boolean)parent.getAttribute("$table-query");
         SQLExprTableSource tableReference = (SQLExprTableSource)(isTableSource != null && isTableSource ? new MySqlTableTableSource() : new SQLExprTableSource());
         this.parseTableSourceQueryTableExpr(tableReference);
         SQLTableSource tableSrc = this.parseTableSourceRest(tableReference);
         if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
            tableSrc.addAfterComment(this.lexer.readAndResetComments());
         }

         return tableSrc;
      }
   }

   protected MySqlUpdateStatement parseUpdateStatment() {
      MySqlUpdateStatement update = new MySqlUpdateStatement();
      this.lexer.nextToken();
      if (this.lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
         this.lexer.nextToken();
         update.setLowPriority(true);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
         this.lexer.nextToken();
         update.setIgnore(true);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.COMMIT_ON_SUCCESS)) {
         this.lexer.nextToken();
         update.setCommitOnSuccess(true);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.ROLLBACK_ON_FAIL)) {
         this.lexer.nextToken();
         update.setRollBackOnFail(true);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.QUEUE_ON_PK)) {
         this.lexer.nextToken();
         update.setQueryOnPk(true);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.TARGET_AFFECT_ROW)) {
         this.lexer.nextToken();
         SQLExpr targetAffectRow = this.exprParser.expr();
         update.setTargetAffectRow(targetAffectRow);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.FORCE)) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.ALL) {
            this.lexer.nextToken();
            this.acceptIdentifier("PARTITIONS");
            update.setForceAllPartitions(true);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.PARTITIONS)) {
            this.lexer.nextToken();
            update.setForceAllPartitions(true);
         } else {
            if (this.lexer.token() != Token.PARTITION) {
               throw new ParserException("TODO. " + this.lexer.info());
            }

            this.lexer.nextToken();
            SQLName partition = this.exprParser.name();
            update.setForcePartition(partition);
         }
      }

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

      SQLSelectParser selectParser = this.exprParser.createSelectParser();
      SQLTableSource updateTableSource = selectParser.parseTableSource();
      update.setTableSource(updateTableSource);
      this.accept(Token.SET);

      while(true) {
         SQLUpdateSetItem item = this.exprParser.parseUpdateSetItem();
         update.addItem(item);
         if (this.lexer.token() != Token.COMMA) {
            if (this.lexer.token() == Token.WHERE) {
               this.lexer.nextToken();
               update.setWhere(this.exprParser.expr());
            }

            update.setOrderBy(this.exprParser.parseOrderBy());
            update.setLimit(this.exprParser.parseLimit());
            return update;
         }

         this.lexer.nextToken();
      }
   }

   protected void parseInto(SQLSelectQueryBlock queryBlock) {
      if (this.lexer.token() == Token.INTO) {
         this.lexer.nextToken();
         if (this.lexer.identifierEquals(FnvHash.Constants.OUTFILE)) {
            this.lexer.nextToken();
            MySqlOutFileExpr outFile = new MySqlOutFileExpr();
            outFile.setFile(this.expr());
            queryBlock.setInto((SQLExpr)outFile);
            if (this.lexer.identifierEquals(FnvHash.Constants.FIELDS) || this.lexer.identifierEquals(FnvHash.Constants.COLUMNS)) {
               this.lexer.nextToken();
               if (this.lexer.identifierEquals(FnvHash.Constants.TERMINATED)) {
                  this.lexer.nextToken();
                  this.accept(Token.BY);
               }

               outFile.setColumnsTerminatedBy(this.expr());
               if (this.lexer.identifierEquals(FnvHash.Constants.OPTIONALLY)) {
                  this.lexer.nextToken();
                  outFile.setColumnsEnclosedOptionally(true);
               }

               if (this.lexer.identifierEquals(FnvHash.Constants.ENCLOSED)) {
                  this.lexer.nextToken();
                  this.accept(Token.BY);
                  outFile.setColumnsEnclosedBy((SQLLiteralExpr)this.expr());
               }

               if (this.lexer.identifierEquals(FnvHash.Constants.ESCAPED)) {
                  this.lexer.nextToken();
                  this.accept(Token.BY);
                  outFile.setColumnsEscaped((SQLLiteralExpr)this.expr());
               }
            }

            if (this.lexer.identifierEquals(FnvHash.Constants.LINES)) {
               this.lexer.nextToken();
               if (this.lexer.identifierEquals(FnvHash.Constants.STARTING)) {
                  this.lexer.nextToken();
                  this.accept(Token.BY);
                  outFile.setLinesStartingBy((SQLLiteralExpr)this.expr());
               } else {
                  if (this.lexer.identifierEquals(FnvHash.Constants.TERMINATED)) {
                     this.lexer.nextToken();
                  }

                  this.accept(Token.BY);
                  outFile.setLinesTerminatedBy((SQLLiteralExpr)this.expr());
               }
            }
         } else {
            SQLExpr intoExpr = this.exprParser.name();
            if (this.lexer.token() == Token.COMMA) {
               SQLListExpr list = new SQLListExpr();
               list.addItem(intoExpr);

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

               intoExpr = list;
            }

            queryBlock.setInto(intoExpr);
         }

      }
   }

   protected SQLTableSource primaryTableSourceRest(SQLTableSource tableSource) {
      if (this.lexer.token() == Token.USE) {
         this.lexer.nextToken();
         MySqlUseIndexHint hint = new MySqlUseIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
         this.lexer.nextToken();
         MySqlIgnoreIndexHint hint = new MySqlIgnoreIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.FORCE)) {
         this.lexer.nextToken();
         MySqlForceIndexHint hint = new MySqlForceIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
      }

      if (this.lexer.token() == Token.PARTITION) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.ON) {
            tableSource.setAlias("partition");
         } else {
            this.accept(Token.LPAREN);
            this.exprParser.names(((SQLExprTableSource)tableSource).getPartitions(), tableSource);
            this.accept(Token.RPAREN);
         }
      }

      return tableSource;
   }

   public SQLTableSource parseTableSourceRest(SQLTableSource tableSource) {
      if (this.lexer.identifierEquals(FnvHash.Constants.TABLESAMPLE) && tableSource instanceof SQLExprTableSource) {
         Lexer.SavePoint mark = this.lexer.mark();
         this.lexer.nextToken();
         SQLTableSampling sampling = new SQLTableSampling();
         if (this.lexer.identifierEquals(FnvHash.Constants.BERNOULLI)) {
            this.lexer.nextToken();
            sampling.setBernoulli(true);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.SYSTEM)) {
            this.lexer.nextToken();
            sampling.setSystem(true);
         }

         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.BUCKET)) {
               this.lexer.nextToken();
               SQLExpr bucket = this.exprParser.primary();
               sampling.setBucket(bucket);
               if (this.lexer.token() == Token.OUT) {
                  this.lexer.nextToken();
                  this.accept(Token.OF);
                  SQLExpr outOf = this.exprParser.primary();
                  sampling.setOutOf(outOf);
               }

               if (this.lexer.token() == Token.ON) {
                  this.lexer.nextToken();
                  SQLExpr on = this.exprParser.expr();
                  sampling.setOn(on);
               }
            }

            if (this.lexer.token() == Token.LITERAL_INT || this.lexer.token() == Token.LITERAL_FLOAT) {
               SQLExpr val = this.exprParser.primary();
               if (this.lexer.identifierEquals(FnvHash.Constants.ROWS)) {
                  this.lexer.nextToken();
                  sampling.setRows(val);
               } else if (this.lexer.token() == Token.RPAREN) {
                  sampling.setRows(val);
               } else {
                  this.acceptIdentifier("PERCENT");
                  sampling.setPercent(val);
               }
            }

            if (this.lexer.token() == Token.IDENTIFIER) {
               String strVal = this.lexer.stringVal();
               char first = strVal.charAt(0);
               char last = strVal.charAt(strVal.length() - 1);
               if (last >= 'a' && last <= 'z') {
                  last = (char)(last - 32);
               }

               boolean match = false;
               if (first == '.' || first >= '0' && first <= '9') {
                  switch (last) {
                     case 'B':
                     case 'G':
                     case 'K':
                     case 'M':
                     case 'P':
                     case 'T':
                        match = true;
                     case 'C':
                     case 'D':
                     case 'E':
                     case 'F':
                     case 'H':
                     case 'I':
                     case 'J':
                     case 'L':
                     case 'N':
                     case 'O':
                     case 'Q':
                     case 'R':
                     case 'S':
                  }
               }

               SQLSizeExpr size = new SQLSizeExpr(strVal.substring(0, strVal.length() - 2), last);
               sampling.setByteLength(size);
               this.lexer.nextToken();
            }

            SQLExprTableSource table = (SQLExprTableSource)tableSource;
            table.setSampling(sampling);
            this.accept(Token.RPAREN);
         } else {
            this.lexer.reset(mark);
         }
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.USING)) {
         return tableSource;
      } else {
         this.parseIndexHintList(tableSource);
         if (this.lexer.token() == Token.PARTITION) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            this.exprParser.names(((SQLExprTableSource)tableSource).getPartitions(), tableSource);
            this.accept(Token.RPAREN);
         }

         return super.parseTableSourceRest(tableSource);
      }
   }

   private void parseIndexHintList(SQLTableSource tableSource) {
      if (this.lexer.token() == Token.USE) {
         this.lexer.nextToken();
         MySqlUseIndexHint hint = new MySqlUseIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
         this.parseIndexHintList(tableSource);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
         this.lexer.nextToken();
         MySqlIgnoreIndexHint hint = new MySqlIgnoreIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
         this.parseIndexHintList(tableSource);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.FORCE)) {
         this.lexer.nextToken();
         MySqlForceIndexHint hint = new MySqlForceIndexHint();
         this.parseIndexHint(hint);
         tableSource.getHints().add(hint);
         this.parseIndexHintList(tableSource);
      }

   }

   private void parseIndexHint(MySqlIndexHintImpl hint) {
      if (this.lexer.token() == Token.INDEX) {
         this.lexer.nextToken();
      } else {
         this.accept(Token.KEY);
      }

      if (this.lexer.token() == Token.FOR) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.JOIN) {
            this.lexer.nextToken();
            hint.setOption(MySqlIndexHint.Option.JOIN);
         } else if (this.lexer.token() == Token.ORDER) {
            this.lexer.nextToken();
            this.accept(Token.BY);
            hint.setOption(MySqlIndexHint.Option.ORDER_BY);
         } else {
            this.accept(Token.GROUP);
            this.accept(Token.BY);
            hint.setOption(MySqlIndexHint.Option.GROUP_BY);
         }
      }

      this.accept(Token.LPAREN);

      while(this.lexer.token() != Token.RPAREN && this.lexer.token() != Token.EOF) {
         if (this.lexer.token() == Token.PRIMARY) {
            this.lexer.nextToken();
            hint.getIndexList().add(new SQLIdentifierExpr("PRIMARY"));
         } else {
            SQLName name = this.exprParser.name();
            name.setParent(hint);
            hint.getIndexList().add(name);
         }

         if (this.lexer.token() != Token.COMMA) {
            break;
         }

         this.lexer.nextToken();
      }

      this.accept(Token.RPAREN);
   }

   public SQLUnionQuery unionRest(SQLUnionQuery union) {
      if (this.lexer.token() == Token.LIMIT) {
         union.setLimit(this.exprParser.parseLimit());
      }

      return super.unionRest(union);
   }

   public MySqlExprParser getExprParser() {
      return (MySqlExprParser)this.exprParser;
   }
}
