content: /*
   * Copyright 1999-2017 Alibaba Group Holding Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.alibaba.druid.sql.dialect.mysql.parser;
  
  import com.alibaba.druid.sql.ast.SQLExpr;
  import com.alibaba.druid.sql.ast.SQLObject;
  import com.alibaba.druid.sql.ast.SQLSetQuantifier;
  import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
  import com.alibaba.druid.sql.ast.expr.SQLLiteralExpr;
  import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
  import com.alibaba.druid.sql.ast.statement.*;
  import com.alibaba.druid.sql.dialect.mysql.ast.*;
  import com.alibaba.druid.sql.dialect.mysql.ast.clause.MySqlSelectIntoStatement;
  import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOutFileExpr;
  import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
  import com.alibaba.druid.sql.parser.ParserException;
  import com.alibaba.druid.sql.parser.SQLExprParser;
  import com.alibaba.druid.sql.parser.SQLSelectParser;
  import com.alibaba.druid.sql.parser.Token;
  import com.alibaba.druid.util.FnvHash;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * @author zz [455910092@qq.com]
   */
  public class MySqlSelectIntoParser extends SQLSelectParser {
      private List<SQLExpr> argsList;
  
      public MySqlSelectIntoParser(SQLExprParser exprParser) {
          super(exprParser);
      }
  
      public MySqlSelectIntoParser(String sql) {
          this(new MySqlExprParser(sql));
      }
  
      public MySqlSelectIntoStatement parseSelectInto() {
          SQLSelect select = select();
          MySqlSelectIntoStatement stmt = new MySqlSelectIntoStatement();
          stmt.setSelect(select);
          stmt.setVarList(argsList);
          return stmt;
  
      }
  
      @Override
      public SQLSelectQuery query(SQLObject parent, boolean acceptUnion) {
          if (lexer.token() == (Token.LPAREN)) {
              lexer.nextToken();
  
              SQLSelectQuery select = query();
              accept(Token.RPAREN);
  
              return queryRest(select, acceptUnion);
          }
  
          MySqlSelectQueryBlock queryBlock = new MySqlSelectQueryBlock();
  
          if (lexer.token() == Token.SELECT) {
              lexer.nextToken();
  
              if (lexer.token() == Token.HINT) {
                  this.exprParser.parseHints(queryBlock.getHints());
              }
  
              if (lexer.token() == Token.COMMENT) {
                  lexer.nextToken();
              }
  
              if (lexer.token() == (Token.DISTINCT)) {
                  queryBlock.setDistionOption(SQLSetQuantifier.DISTINCT);
                  lexer.nextToken();
              } else if (lexer.identifierEquals("DISTINCTROW")) {
                  queryBlock.setDistionOption(SQLSetQuantifier.DISTINCTROW);
                  lexer.nextToken();
              } else if (lexer.token() == (Token.ALL)) {
                  queryBlock.setDistionOption(SQLSetQuantifier.ALL);
                  lexer.nextToken();
              }
  
              if (lexer.identifierEquals("HIGH_PRIORITY")) {
                  queryBlock.setHignPriority(true);
                  lexer.nextToken();
              }
  
              if (lexer.identifierEquals("STRAIGHT_JOIN")) {
                  queryBlock.setStraightJoin(true);
                  lexer.nextToken();
              }
  
              if (lexer.identifierEquals("SQL_SMALL_RESULT")) {
                  queryBlock.setSmallResult(true);
                  lexer.nextToken();
              }
  
              if (lexer.identifierEquals("SQL_BIG_RESULT")) {
                  queryBlock.setBigResult(true);
                  lexer.nextToken();
              }
  
              if (lexer.identifierEquals("SQL_BUFFER_RESULT")) {
                  queryBlock.setBufferResult(true);
                  lexer.nextToken();
              }
  
              if (lexer.identifierEquals("SQL_CACHE")) {
                  queryBlock.setCache(true);
                  lexer.nextToken();
              }
  
              if (lexer.identifierEquals("SQL_NO_CACHE")) {
                  queryBlock.setCache(false);
                  lexer.nextToken();
              }
  
              if (lexer.identifierEquals("SQL_CALC_FOUND_ROWS")) {
                  queryBlock.setCalcFoundRows(true);
                  lexer.nextToken();
              }
  
              parseSelectList(queryBlock);
  
              argsList = parseIntoArgs();
          }
  
          parseFrom(queryBlock);
  
          parseWhere(queryBlock);
  
          parseGroupBy(queryBlock);
  
          queryBlock.setOrderBy(this.exprParser.parseOrderBy());
  
          if (lexer.token() == Token.LIMIT) {
              queryBlock.setLimit(this.exprParser.parseLimit());
          }
  
          if (lexer.token() == Token.PROCEDURE) {
              lexer.nextToken();
              throw new ParserException("TODO. " + lexer.info());
          }
  
          parseInto(queryBlock);
  
          if (lexer.token() == Token.FOR) {
              lexer.nextToken();
              accept(Token.UPDATE);
  
              queryBlock.setForUpdate(true);
          }
  
          if (lexer.token() == Token.LOCK) {
              lexer.nextToken();
              accept(Token.IN);
              acceptIdentifier("SHARE");
              acceptIdentifier("MODE");
              queryBlock.setLockInShareMode(true);
          }
  
          return queryRest(queryBlock, acceptUnion);
      }
  
      /**
       * Parses the arguments for the INTO clause and returns a list of SQL expressions.
       *
       * @return a list of SQL expressions representing the arguments passed to the INTO clause
       */
      protected List<SQLExpr> parseIntoArgs() {
          List<SQLExpr> args = new ArrayList<SQLExpr>();
          if (lexer.token() == (Token.INTO)) {
              accept(Token.INTO);
              //lexer.nextToken();
              for (; ; ) {
                  SQLExpr var = exprParser.primary();
                  if (var instanceof SQLIdentifierExpr) {
                      var = new SQLVariantRefExpr(
                              ((SQLIdentifierExpr) var).getName());
                  }
                  args.add(var);
                  if (lexer.token() == Token.COMMA) {
                      accept(Token.COMMA);
                      continue;
                  } else {
                      break;
                  }
              }
          }
          return args;
      }
  
      protected void parseInto(SQLSelectQueryBlock queryBlock) {
          if (lexer.token() == (Token.INTO)) {
              lexer.nextToken();
  
              if (lexer.identifierEquals("OUTFILE")) {
                  lexer.nextToken();
  
                  MySqlOutFileExpr outFile = new MySqlOutFileExpr();
                  outFile.setFile(expr());
  
                  queryBlock.setInto(outFile);
  
                  if (lexer.identifierEquals("FIELDS") || lexer.identifierEquals("COLUMNS")) {
                      lexer.nextToken();
  
                      if (lexer.identifierEquals("TERMINATED")) {
                          lexer.nextToken();
                          accept(Token.BY);
                      }
                      outFile.setColumnsTerminatedBy((SQLLiteralExpr) expr());
  
                      if (lexer.identifierEquals("OPTIONALLY")) {
                          lexer.nextToken();
                          outFile.setColumnsEnclosedOptionally(true);
                      }
  
                      if (lexer.identifierEquals("ENCLOSED")) {
                          lexer.nextToken();
                          accept(Token.BY);
                          outFile.setColumnsEnclosedBy((SQLLiteralExpr) expr());
                      }
  
                      if (lexer.identifierEquals("ESCAPED")) {
                          lexer.nextToken();
                          accept(Token.BY);
                          outFile.setColumnsEscaped((SQLLiteralExpr) expr());
                      }
                  }
  
                  if (lexer.identifierEquals("LINES")) {
                      lexer.nextToken();
  
                      if (lexer.identifierEquals("STARTING")) {
                          lexer.nextToken();
                          accept(Token.BY);
                          outFile.setLinesStartingBy((SQLLiteralExpr) expr());
                      } else {
                          lexer.identifierEquals("TERMINATED");
                          lexer.nextToken();
                          accept(Token.BY);
                          outFile.setLinesTerminatedBy((SQLLiteralExpr) expr());
                      }
                  }
              } else {
                  queryBlock.setInto(this.exprParser.name());
              }
          }
      }
  
      public SQLTableSource parseTableSourceRest(SQLTableSource tableSource) {
          if (lexer.identifierEquals("USING")) {
              return tableSource;
          }
  
          parseIndexHintList(tableSource);
  
          return super.parseTableSourceRest(tableSource);
      }
  
      private void parseIndexHintList(SQLTableSource tableSource) {
          if (lexer.token() == Token.USE) {
              lexer.nextToken();
              MySqlUseIndexHint hint = new MySqlUseIndexHint();
              parseIndexHint(hint);
              tableSource.getHints().add(hint);
              parseIndexHintList(tableSource);
          }
  
          if (lexer.identifierEquals("IGNORE")) {
              lexer.nextToken();
              MySqlIgnoreIndexHint hint = new MySqlIgnoreIndexHint();
              parseIndexHint(hint);
              tableSource.getHints().add(hint);
              parseIndexHintList(tableSource);
          }
  
          if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
              lexer.nextToken();
              MySqlForceIndexHint hint = new MySqlForceIndexHint();
              parseIndexHint(hint);
              tableSource.getHints().add(hint);
              parseIndexHintList(tableSource);
          }
      }
  
      private void parseIndexHint(MySqlIndexHintImpl hint) {
          if (lexer.token() == Token.INDEX) {
              lexer.nextToken();
          } else {
              accept(Token.KEY);
          }
  
          if (lexer.token() == Token.FOR) {
              lexer.nextToken();
  
              if (lexer.token() == Token.JOIN) {
                  lexer.nextToken();
                  hint.setOption(MySqlIndexHint.Option.JOIN);
              } else if (lexer.token() == Token.ORDER) {
                  lexer.nextToken();
                  accept(Token.BY);
                  hint.setOption(MySqlIndexHint.Option.ORDER_BY);
              } else {
                  accept(Token.GROUP);
                  accept(Token.BY);
                  hint.setOption(MySqlIndexHint.Option.GROUP_BY);
              }
          }
  
          accept(Token.LPAREN);
          if (lexer.token() == Token.PRIMARY) {
              lexer.nextToken();
              hint.getIndexList().add(new SQLIdentifierExpr("PRIMARY"));
          } else {
              this.exprParser.names(hint.getIndexList());
          }
          accept(Token.RPAREN);
      }
  
      public SQLUnionQuery unionRest(SQLUnionQuery union) {
          if (lexer.token() == Token.LIMIT) {
              union.setLimit(this.exprParser.parseLimit());
          }
          return super.unionRest(union);
      }
  
      public MySqlExprParser getExprParser() {
          return (MySqlExprParser) exprParser;
      }
  }
  