/*
 * 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.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.*;
import com.alibaba.druid.sql.ast.expr.*;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.dialect.mysql.ast.*;
import com.alibaba.druid.sql.dialect.mysql.ast.clause.*;
import com.alibaba.druid.sql.dialect.mysql.ast.clause.ConditionValue.ConditionType;
import com.alibaba.druid.sql.dialect.mysql.ast.clause.MySqlCaseStatement.MySqlWhenStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlUserName;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.*;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlLockTableStatement.LockType;
import com.alibaba.druid.sql.parser.*;
import com.alibaba.druid.sql.repository.SchemaObject;
import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor;
import com.alibaba.druid.util.FnvHash;
import com.alibaba.druid.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

import static com.alibaba.druid.sql.parser.Token.*;
import static com.alibaba.druid.sql.parser.Token.ALTER;
import static com.alibaba.druid.sql.parser.Token.FALSE;
import static com.alibaba.druid.sql.parser.Token.GROUP;
import static com.alibaba.druid.sql.parser.Token.INDEX;
import static com.alibaba.druid.sql.parser.Token.SEMI;
import static com.alibaba.druid.sql.parser.Token.TABLE;
import static com.alibaba.druid.sql.parser.Token.TRUE;
import static com.alibaba.druid.sql.parser.Token.WITH;
import static com.alibaba.druid.util.FnvHash.Constants.*;

public class MySqlStatementParser extends SQLStatementParser {
    private static final String AUTO_INCREMENT = "AUTO_INCREMENT";
    private static final String AVG_ROW_LENGTH = "AVG_ROW_LENGTH";
    private static final String CHECKSUM2 = "CHECKSUM";
    private static final String DELAY_KEY_WRITE = "DELAY_KEY_WRITE";
    private static final String ENCRYPTION2 = "ENCRYPTION";
    private static final String INSERT_METHOD = "INSERT_METHOD";
    private static final String KEY_BLOCK_SIZE2 = "KEY_BLOCK_SIZE";
    private static final String MAX_ROWS2 = "MAX_ROWS";
    private static final String MIN_ROWS2 = "MIN_ROWS";
    private static final String PASSWORD2 = "PASSWORD";
    private static final String STATS_AUTO_RECALC = "STATS_AUTO_RECALC";
    private static final String STATS_PERSISTENT = "STATS_PERSISTENT";
    private static final String STATS_SAMPLE_PAGES = "STATS_SAMPLE_PAGES";
    private static final String TABLESPACE2 = "TABLESPACE";
    private static final String CHAIN = "CHAIN";
    private static final String ENGINES = "ENGINES";
    private static final String ENGINE = "ENGINE";
    private static final String BINLOG = "BINLOG";
    private static final String EVENTS = "EVENTS";
    private static final String GLOBAL = "GLOBAL";
    private static final String VARIABLES = "VARIABLES";
    private static final String STATUS = "STATUS";
    private static final String DBLOCK = "DBLOCK";
    private static final String RESET = "RESET";
    private static final String DESCRIBE = "DESCRIBE";
    private static final String WRITE = "WRITE";
    private static final String READ = "READ";
    private static final String LOCAL = "LOCAL";
    private static final String TABLES = "TABLES";
    private static final String CONNECTION = "CONNECTION";

    private int maxIntoClause = -1;

    public MySqlStatementParser(String sql) {
        super(new MySqlExprParser(sql));
    }

    public MySqlStatementParser(String sql, SQLParserFeature... features) {
        super(new MySqlExprParser(sql, features));
    }

    public MySqlStatementParser(String sql, boolean keepComments) {
        super(new MySqlExprParser(sql, keepComments));
    }

    public MySqlStatementParser(String sql, boolean skipComment, boolean keepComments) {
        super(new MySqlExprParser(sql, skipComment, keepComments));
    }

    public MySqlStatementParser(Lexer lexer) {
        super(new MySqlExprParser(lexer));
    }

    public int getMaxIntoClause() {
        return maxIntoClause;
    }

    public void setMaxIntoClause(int maxIntoClause) {
        this.maxIntoClause = maxIntoClause;
    }

    public SQLCreateTableStatement parseCreateTable() {
        MySqlCreateTableParser parser = new MySqlCreateTableParser(this.exprParser);
        return parser.parseCreateTable();
    }

    public SQLStatement parseSelect() {
        MySqlSelectParser selectParser = createSQLSelectParser();

        SQLSelect select = selectParser.select();

        if (selectParser.returningFlag) {
            return selectParser.updateStmt;
        }

        return new SQLSelectStatement(select, DbType.mysql);
    }

    public SQLUpdateStatement parseUpdateStatement() {
        return new MySqlSelectParser(this.exprParser, selectListCache).parseUpdateStatment();
    }

    protected MySqlUpdateStatement createUpdateStatement() {
        return new MySqlUpdateStatement();
    }

    public MySqlDeleteStatement parseDeleteStatement() {
        MySqlDeleteStatement deleteStatement = new MySqlDeleteStatement();

        if (lexer.isKeepComments() && lexer.hasComment()) {
            List<String> comments = lexer.readAndResetComments();

            if (comments != null) {
                deleteStatement.addBeforeComment(comments);
            }
        }

        if (lexer.token() == Token.DELETE) {
            lexer.nextToken();

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

            if (lexer.token() == Token.HINT) {
                this.getExprParser().parseHints(deleteStatement.getHints());
            }

            if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
                deleteStatement.setLowPriority(true);
                lexer.nextToken();
            }

            if (lexer.identifierEquals("QUICK")) {
                deleteStatement.setQuick(true);
                lexer.nextToken();
            }

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

            if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
                Lexer.SavePoint savePoint = lexer.mark();
                lexer.nextToken();

                if (lexer.token() == Token.ALL) {
                    lexer.nextToken();
                    acceptIdentifier("PARTITIONS");
                    deleteStatement.setForceAllPartitions(true);
                } else if (lexer.identifierEquals(FnvHash.Constants.PARTITIONS)) {
                    lexer.nextToken();
                    deleteStatement.setForceAllPartitions(true);
                } else if (lexer.token() == Token.PARTITION) {
                    lexer.nextToken();
                    SQLName partition = this.exprParser.name();
                    deleteStatement.setForcePartition(partition);
                } else {
                    lexer.reset(savePoint);
                }
            }

            if (lexer.token() == Token.IDENTIFIER) {
                deleteStatement.setTableSource(createSQLSelectParser().parseTableSource());

                if (lexer.token() == Token.FROM) {
                    lexer.nextToken();
                    SQLTableSource tableSource = createSQLSelectParser().parseTableSource();
                    deleteStatement.setFrom(tableSource);
                }
            } else if (lexer.token() == Token.FROM) {
                lexer.nextToken();

                if (lexer.token() == Token.FULLTEXT) {
                    lexer.nextToken();
                    if (lexer.identifierEquals(FnvHash.Constants.DICTIONARY)) {
                        lexer.nextToken();
                        deleteStatement.setFulltextDictionary(true);
                    }
                }

                deleteStatement.setTableSource(createSQLSelectParser().parseTableSource());
            } else {
                throw new ParserException("syntax error. " + lexer.info());
            }

            if (lexer.identifierEquals(FnvHash.Constants.USING)) {
                lexer.nextToken();

                SQLTableSource tableSource = createSQLSelectParser().parseTableSource();
                deleteStatement.setUsing(tableSource);
            }
        }

        if (lexer.token() == (Token.WHERE)) {
            lexer.nextToken();
            SQLExpr where = this.exprParser.expr();
            deleteStatement.setWhere(where);
        }

        if (lexer.token() == (Token.ORDER)) {
            SQLOrderBy orderBy = exprParser.parseOrderBy();
            deleteStatement.setOrderBy(orderBy);
        }

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

        if (lexer.token() != Token.EOF && lexer.token() != SEMI) {
            throw new ParserException("syntax error. " + lexer.info());
        }

        return deleteStatement;
    }

    protected void createOptionSkip() {
        super.createOptionSkip();

        lexer.nextIf(HINT);

        if (lexer.identifierEquals(FnvHash.Constants.PHYSICAL)) {
            lexer.nextToken();
        }

        if (lexer.token() == GROUP) {
            lexer.nextToken();
        } else if (lexer.identifierEquals(FnvHash.Constants.SIMPLE)) {
            lexer.nextToken();
            if (lexer.token() == WITH) {
                lexer.nextToken();
                accept(Token.CACHE);
            }
        }
    }

    protected SQLStatement createTableRest(Lexer.SavePoint mark) {
        if (lexer.identifierEquals(FnvHash.Constants.TIME)) {
            lexer.nextToken();
            if (lexer.token() == Token.SEQUENCE) {
                lexer.reset(mark);
                return parseCreateSequence();
            }
        }

        if (lexer.identifierEquals(ALGORITHM)) {
            lexer.reset(mark);
            return parseCreateView();
        }

        if (lexer.nextIfIdentifier(DEFINER)) {
            accept(Token.EQ);
            this.getExprParser().userName();

            if (lexer.identifierEquals(FnvHash.Constants.SQL)) {
                lexer.nextToken();
                acceptIdentifier("SECURITY");
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                lexer.nextToken();
            }
            if (lexer.identifierEquals(FnvHash.Constants.EVENT)) {
                lexer.reset(mark);
                return parseCreateEvent();
            } else if (lexer.token() == Token.TRIGGER) {
                lexer.reset(mark);
                return parseCreateTrigger();
            } else if (lexer.token() == Token.VIEW) {
                lexer.reset(mark);
                return parseCreateView();
            } else if (lexer.token() == Token.FUNCTION) {
                lexer.reset(mark);
                return parseCreateFunction();
            } else {
                lexer.reset(mark);
                return parseCreateProcedure();
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.CLUSTERED)) {
            lexer.reset(mark);
            return parseCreateIndex();
        }

        if (lexer.token() == Token.FULLTEXT) {
            lexer.reset(mark);
            return parseCreateFullTextStatement();
        }

        if (lexer.token() == Token.UNIQUE
                || lexer.token() == INDEX
                || lexer.identifierEquals(FnvHash.Constants.SPATIAL)
                || lexer.identifierEquals(FnvHash.Constants.ANN)
                || lexer.identifierEquals(FnvHash.Constants.GLOBAL)
                || lexer.identifierEquals(FnvHash.Constants.LOCAL)) {
            lexer.reset(mark);
            return parseCreateIndex();
        }

        if (lexer.identifierEquals(FnvHash.Constants.LOGFILE)) {
            lexer.reset(mark);
            return parseCreateLogFileGroup();
        }

        if (lexer.identifierEquals(FnvHash.Constants.DIMENSION) || lexer.identifierEquals("SHADOW")) {
            lexer.reset(mark);
            return parseCreateTable();
        }
        return null;
    }

    public SQLStatement parseCreateFullTextStatement() {
        Lexer.SavePoint mark = lexer.markOut();

        accept(Token.CREATE);
        accept(Token.FULLTEXT);

        if (lexer.identifierEquals(FnvHash.Constants.CHARFILTER)) {
            lexer.nextToken();
            return parseFullTextCharFilter();
        } else if (lexer.identifierEquals(FnvHash.Constants.TOKENIZER)) {
            lexer.nextToken();
            return parseFullTextTokenizer();
        } else if (lexer.identifierEquals(FnvHash.Constants.TOKENFILTER)) {
            lexer.nextToken();
            return parseFullTextTokenFilter();
        } else if (lexer.identifierEquals(FnvHash.Constants.ANALYZER)) {
            lexer.nextToken();
            return parseFullTextAnalyzer();
        } else if (lexer.token() == INDEX) {
            lexer.reset(mark);
            return parseCreateIndex();
        } else if (lexer.identifierEquals(FnvHash.Constants.DICTIONARY)) {
            lexer.nextToken();
            MysqlCreateFullTextDictionaryStatement stmt = new MysqlCreateFullTextDictionaryStatement();
            SQLName name = this.exprParser.name();
            stmt.setName(name);

            accept(Token.LPAREN);

            SQLColumnDefinition col = new SQLColumnDefinition();

            col.setName(this.exprParser.name());

            acceptIdentifier("varchar");

            col.setDataType(new SQLDataTypeImpl("varchar"));

            if (lexer.token() == Token.COMMENT) {
                accept(Token.COMMENT);
                col.setComment(this.exprParser.name());
            }

            stmt.setColumn(col);
            accept(Token.RPAREN);

            if (lexer.token() == Token.COMMENT) {
                accept(Token.COMMENT);
                stmt.setComment(this.exprParser.name().getSimpleName());
            }
            return stmt;
        }
        throw new ParserException("TODO " + lexer.info());
    }

    private SQLStatement parseFullTextAnalyzer() {
        MysqlCreateFullTextAnalyzerStatement stmt = new MysqlCreateFullTextAnalyzerStatement();

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        accept(Token.LPAREN);
        for (; ; ) {
            String key = "";
            if (lexer.token() == Token.LITERAL_ALIAS || lexer.token() == Token.LITERAL_CHARS) {
                key = StringUtils.removeNameQuotes(lexer.stringVal());

                if (key.equalsIgnoreCase("charfilter")) {
                    lexer.nextToken();
                    accept(Token.EQ);

                    accept(Token.LBRACKET);
                    for (; ; ) {
                        String c = SQLUtils.normalize(this.exprParser.name().getSimpleName());
                        stmt.getCharfilters().add(c);
                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    accept(Token.RBRACKET);
                } else if (key.equalsIgnoreCase("tokenfilter")) {
                    lexer.nextToken();
                    accept(Token.EQ);

                    accept(Token.LBRACKET);
                    for (; ; ) {
                        String c = SQLUtils.normalize(this.exprParser.name().getSimpleName());
                        stmt.getTokenizers().add(c);
                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    accept(Token.RBRACKET);
                } else if (key.equalsIgnoreCase("tokenizer")) {
                    lexer.nextToken();
                    accept(Token.EQ);

                    stmt.setTokenizer(SQLUtils.normalize(this.exprParser.name().getSimpleName()));
                }
            }

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }

            break;
        }

        accept(Token.RPAREN);

        String tokenizer = stmt.getTokenizer();
        if (tokenizer == null || StringUtils.isEmpty(tokenizer)) {
            throw new ParserException("tokenizer is require.");
        }

        return stmt;
    }

    private SQLStatement parseFullTextTokenizer() {
        MysqlCreateFullTextTokenizerStatement stmt = new MysqlCreateFullTextTokenizerStatement();

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        accept(Token.LPAREN);
        for (; ; ) {
            SQLAssignItem assignItem = this.exprParser.parseAssignItem();
            assignItem.setParent(stmt);

            SQLExpr target = assignItem.getTarget();
            if ("type".equalsIgnoreCase(((SQLTextLiteralExpr) target).getText())) {
                stmt.setTypeName((SQLTextLiteralExpr) assignItem.getValue());
            } else if ("user_defined_dict".equalsIgnoreCase(((SQLTextLiteralExpr) target).getText())) {
                stmt.setUserDefinedDict((SQLTextLiteralExpr) assignItem.getValue());
            } else {
                stmt.getOptions().add(assignItem);
            }

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }

        accept(Token.RPAREN);

        SQLTextLiteralExpr typeName = stmt.getTypeName();
        if (typeName == null || StringUtils.isEmpty(typeName.getText())) {
            throw new ParserException("type is require.");
        }

        return stmt;
    }

    private SQLStatement parseFullTextCharFilter() {
        MysqlCreateFullTextCharFilterStatement stmt = new MysqlCreateFullTextCharFilterStatement();

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        accept(Token.LPAREN);
        for (; ; ) {
            SQLAssignItem assignItem = this.exprParser.parseAssignItem();
            assignItem.setParent(stmt);

            if ("type".equalsIgnoreCase(((SQLTextLiteralExpr) assignItem.getTarget()).getText())) {
                stmt.setTypeName((SQLTextLiteralExpr) assignItem.getValue());
            } else {
                stmt.getOptions().add(assignItem);
            }

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }

        accept(Token.RPAREN);

        SQLTextLiteralExpr typeName = stmt.getTypeName();
        if (typeName == null || StringUtils.isEmpty(typeName.getText())) {
            throw new ParserException("type is require.");
        }

        return stmt;
    }

    private SQLStatement parseFullTextTokenFilter() {
        MysqlCreateFullTextTokenFilterStatement stmt = new MysqlCreateFullTextTokenFilterStatement();

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        accept(Token.LPAREN);
        for (; ; ) {
            SQLAssignItem assignItem = this.exprParser.parseAssignItem();
            assignItem.setParent(stmt);

            if ("type".equalsIgnoreCase(((SQLTextLiteralExpr) assignItem.getTarget()).getText())) {
                stmt.setTypeName((SQLTextLiteralExpr) assignItem.getValue());
            } else {
                stmt.getOptions().add(assignItem);
            }

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }

        accept(Token.RPAREN);

        SQLTextLiteralExpr typeName = stmt.getTypeName();
        if (typeName == null || StringUtils.isEmpty(typeName.getText())) {
            throw new ParserException("type is require.");
        }

        return stmt;
    }

    public SQLStatement parseCreateOutline() {
        accept(Token.CREATE);
        acceptIdentifier("OUTLINE");

        SQLCreateOutlineStatement stmt = new SQLCreateOutlineStatement();
        stmt.setDbType(dbType);

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        if (lexer.token() == Token.WHERE) {
            lexer.nextToken();
            stmt.setWhere(this.exprParser.expr());
        }

        accept(Token.ON);

        expectedNextToken = Token.TO;
        try {
            SQLStatement on = this.parseStatement();
            stmt.setOn(on);
        } finally {
            expectedNextToken = null;
        }

        accept(Token.TO);

        SQLStatement to = this.parseStatement();
        stmt.setTo(to);

        return stmt;
    }

    public SQLStatement parseCreateTableSpace() {
        if (lexer.token() == Token.CREATE) {
            accept(Token.CREATE);
        }

        MySqlCreateTableSpaceStatement stmt = new MySqlCreateTableSpaceStatement();

        accept(Token.TABLESPACE);
        stmt.setName(this.exprParser.name());

        if (lexer.identifierEquals(FnvHash.Constants.ADD)) {
            lexer.nextToken();
            acceptIdentifier("DATAFILE");
            SQLExpr file = this.exprParser.primary();
            stmt.setAddDataFile(file);
        }

        for (; ; ) {
            if (lexer.identifierEquals(FnvHash.Constants.INITIAL_SIZE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr initialSize = this.exprParser.expr();
                stmt.setInitialSize(initialSize);
            } else if (lexer.identifierEquals(FnvHash.Constants.FILE_BLOCK_SIZE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr fileBlockSize = this.exprParser.expr();
                stmt.setFileBlockSize(fileBlockSize);
            } else if (lexer.identifierEquals(FnvHash.Constants.EXTENT_SIZE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr extentSize = this.exprParser.expr();
                stmt.setExtentSize(extentSize);
            } else if (lexer.identifierEquals(FnvHash.Constants.AUTOEXTEND_SIZE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr extentSize = this.exprParser.expr();
                stmt.setAutoExtentSize(extentSize);
            } else if (lexer.identifierEquals(FnvHash.Constants.MAX_SIZE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr size = this.exprParser.expr();
                stmt.setMaxSize(size);
            } else if (lexer.identifierEquals(FnvHash.Constants.NODEGROUP)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr size = this.exprParser.expr();
                stmt.setNodeGroup(size);
            } else if (lexer.identifierEquals(FnvHash.Constants.WAIT)) {
                lexer.nextToken();
                stmt.setWait(true);
            } else if (lexer.identifierEquals(FnvHash.Constants.ENGINE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr engine = this.exprParser.expr();
                stmt.setEngine(engine);
            } else if (lexer.token() == Token.COMMENT) {
                lexer.nextToken();
                SQLExpr comment = this.exprParser.expr();
                stmt.setComment(comment);
            } else if (lexer.token() == Token.USE) {
                lexer.nextToken();
                acceptIdentifier("LOGFILE");
                accept(GROUP);

                SQLExpr logFileGroup = this.exprParser.expr();
                stmt.setFileBlockSize(logFileGroup);
            } else {
                break;
            }
        }
        return stmt;
    }

    public SQLStatement parseCreateServer() {
        if (lexer.token() == Token.CREATE) {
            accept(Token.CREATE);
        }

        MySqlCreateServerStatement stmt = new MySqlCreateServerStatement();

        acceptIdentifier("SERVER");
        stmt.setName(this.exprParser.name());

        accept(Token.FOREIGN);
        acceptIdentifier("DATA");
        acceptIdentifier("WRAPPER");
        stmt.setForeignDataWrapper(this.exprParser.name());

        acceptIdentifier("OPTIONS");
        accept(Token.LPAREN);
        for (; ; ) {
            if (lexer.identifierEquals(FnvHash.Constants.HOST)) {
                lexer.nextToken();
                SQLExpr host = this.exprParser.expr();
                stmt.setHost(host);
            } else if (lexer.token() == Token.USER) {
                lexer.nextToken();
                SQLExpr user = this.exprParser.expr();
                stmt.setUser(user);
            } else if (lexer.token() == Token.DATABASE) {
                lexer.nextToken();
                SQLExpr db = this.exprParser.expr();
                stmt.setDatabase(db);
            } else if (lexer.identifierEquals(FnvHash.Constants.PASSWORD)) {
                lexer.nextToken();
                SQLExpr pwd = this.exprParser.expr();
                stmt.setPassword(pwd);
            } else if (lexer.identifierEquals(FnvHash.Constants.SOCKET)) {
                lexer.nextToken();
                SQLExpr sock = this.exprParser.expr();
                stmt.setSocket(sock);
            } else if (lexer.identifierEquals(FnvHash.Constants.OWNER)) {
                lexer.nextToken();
                SQLExpr owner = this.exprParser.expr();
                stmt.setOwner(owner);
            } else if (lexer.identifierEquals(FnvHash.Constants.PORT)) {
                lexer.nextToken();
                SQLExpr port = this.exprParser.expr();
                stmt.setPort(port);
            }

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
            } else {
                break;
            }
        }
        accept(Token.RPAREN);
        return stmt;
    }

    public SQLCreateIndexStatement parseCreateIndex() {
        accept(Token.CREATE);

        SQLCreateIndexStatement stmt = new SQLCreateIndexStatement();

        this.exprParser.parseIndex(stmt.getIndexDefinition());

        return stmt;
    }

    private void parseCreateIndexUsing(SQLCreateIndexStatement stmt) {
        if (lexer.identifierEquals(FnvHash.Constants.USING)) {
            lexer.nextToken();

            if (lexer.identifierEquals(FnvHash.Constants.BTREE)) {
                stmt.setUsing("BTREE");
                lexer.nextToken();
            } else if (lexer.identifierEquals(FnvHash.Constants.HASH)) {
                stmt.setUsing("HASH");
                lexer.nextToken();
            } else {
                throw new ParserException("TODO " + lexer.info());
            }
        }
    }

    public SQLStatement parseCreateUser() {
        if (lexer.token() == Token.CREATE) {
            lexer.nextToken();
        }

        accept(Token.USER);

        MySqlCreateUserStatement stmt = new MySqlCreateUserStatement();

        if (lexer.token() == Token.IF) {
            lexer.nextToken();
            accept(Token.NOT);
            accept(Token.EXISTS);
            stmt.setIfNotExists(true);
        }

        for (; ; ) {
            MySqlCreateUserStatement.UserSpecification userSpec = new MySqlCreateUserStatement.UserSpecification();

            if (lexer.token() == Token.IF) {
                lexer.nextToken();
                accept(Token.NOT);
                accept(Token.EXISTS);
                stmt.setIfNotExists(true);
            }

            MySqlUserName mySqlUserName = new MySqlUserName();
            mySqlUserName.setUserName(trimQuotesBeginAndEnd(lexer.stringVal()));
            lexer.nextToken();
            String maybeHost = lexer.stringVal();
            if ("@".equals(maybeHost)) {
                lexer.nextToken();
                mySqlUserName.setHost(trimQuotesBeginAndEnd(lexer.stringVal()));
                lexer.nextToken();
            } else if (maybeHost.startsWith("@")) { // eg: @localhost
                maybeHost = maybeHost.substring(1);
                mySqlUserName.setHost(trimQuotesBeginAndEnd(maybeHost));
                lexer.nextToken();
            }

            userSpec.setUser(mySqlUserName);

            if (lexer.identifierEquals(FnvHash.Constants.IDENTIFIED)) {
                lexer.nextToken();
                if (lexer.token() == Token.BY) {
                    lexer.nextToken();
                    if (lexer.identifierEquals("RANDOM")) {
                        lexer.nextToken();
                        acceptIdentifier("PASSWORD");
                        userSpec.setRandomPassword(true);
                    } else {
                        if (lexer.identifierEquals("PASSWORD")) {
                            lexer.nextToken();
                            userSpec.setPasswordHash(true);
                        }
                        SQLExpr password = this.exprParser.expr();
                        if (password instanceof SQLIdentifierExpr || password instanceof SQLCharExpr) {
                            userSpec.setPassword(password);
                        } else {
                            throw new ParserException("syntax error. invalid " + password + " expression.");
                        }
                    }

                } else if (lexer.token() == WITH) {
                    lexer.nextToken();
                    userSpec.setAuthPlugin(this.exprParser.expr());

                    // IDENTIFIED WITH auth_plugin BY 'auth_string'
                    // IDENTIFIED WITH auth_plugin AS 'auth_string'
                    if (lexer.token() == Token.BY ||
                            lexer.token() == Token.AS) {
                        userSpec.setPluginAs(lexer.token() == Token.AS);
                        lexer.nextToken();

                        if (userSpec.isPluginAs()) {
                            // Remove ' because lexer don't remove it when token after as.
                            String psw = lexer.stringVal();
                            userSpec.setPassword(new SQLCharExpr(trimQuotesBeginAndEnd(psw)));
                            lexer.nextToken();
                        } else {
                            userSpec.setPassword(this.exprParser.charExpr());
                        }
                    }
                }
            }

            stmt.addUser(userSpec);

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }

            break;
        }

        return stmt;
    }

    static String trimQuotesBeginAndEnd(String str) {
        if (str == null || str.length() < 2) {
            return str;
        }
        char beginChar = str.charAt(0);
        char endChar = str.charAt(str.length() - 1);
        if ((beginChar == '\'' && endChar == '\'') || (beginChar == '\"' && endChar == '\"')) {
            return str.substring(1, str.length() - 1);
        }
        return str;
    }
    public SQLStatement parseKill() {
        accept(Token.KILL);

        MySqlKillStatement stmt = new MySqlKillStatement();

        if (lexer.identifierEquals("CONNECTION")) {
            stmt.setType(MySqlKillStatement.Type.CONNECTION);
            lexer.nextToken();
        } else if (lexer.identifierEquals(FnvHash.Constants.QUERY)
                || lexer.identifierEquals(FnvHash.Constants.PROCESS)) {
            stmt.setType(MySqlKillStatement.Type.QUERY);
            lexer.nextToken();
        } else if (lexer.token() == Token.LITERAL_INT
                || lexer.token() == Token.LITERAL_CHARS
        ) {
            // skip
        } else if (lexer.token() == Token.ALL) {
            SQLIdentifierExpr all = new SQLIdentifierExpr(lexer.stringVal());
            all.setParent(stmt);
            stmt.getThreadIds().add(all);
            lexer.nextToken();
        } else {
            throw new ParserException("not support kill type " + lexer.token() + ". " + lexer.info());
        }

        this.exprParser.exprList(stmt.getThreadIds(), stmt);

        if (lexer.token() == SEMI) {
            lexer.nextToken();
            stmt.setAfterSemi(true);
        }
        return stmt;
    }

    public SQLStatement parseBinlog() {
        acceptIdentifier("binlog");

        MySqlBinlogStatement stmt = new MySqlBinlogStatement();

        SQLExpr expr = this.exprParser.expr();
        stmt.setExpr(expr);

        return stmt;
    }

    public MySqlAnalyzeStatement parseAnalyze() {
        accept(Token.ANALYZE);
        MySqlAnalyzeStatement stmt = new MySqlAnalyzeStatement();

        if (lexer.token() == TABLE) {
            accept(TABLE);
            List<SQLName> names = new ArrayList<SQLName>();
            this.exprParser.names(names, stmt);

            for (SQLName name : names) {
                stmt.addTableSource(new SQLExprTableSource(name));
            }
            if (lexer.token() == WHERE) {
                accept(WHERE);
                SQLExpr where = this.exprParser.expr();
                stmt.setAdbWhere(where);
            }
        } else if (lexer.token() == DATABASE) {
            accept(DATABASE);
            SQLName name = this.exprParser.name();
            stmt.setAdbSchema((SQLIdentifierExpr) name);
        } else if (lexer.token() == COLUMN) {
            accept(COLUMN);
            SQLName table = this.exprParser.name();
            stmt.setTable(table);

            accept(LPAREN);

            for (; ; ) {
                SQLName name = this.exprParser.name();
                stmt.getAdbColumns().add((SQLIdentifierExpr) name);
                if (lexer.token() == COMMA) {
                    accept(COMMA);
                    continue;
                }
                break;
            }
            accept(RPAREN);
            if (lexer.token() == WHERE) {
                accept(WHERE);
                SQLExpr where = this.exprParser.expr();
                stmt.setAdbWhere(where);
            }
        } else if (lexer.identifierEquals("columns")) {
            lexer.nextToken();
            accept(GROUP);

            SQLName table = this.exprParser.name();
            stmt.setTable(table);

            accept(LPAREN);

            for (; ; ) {
                SQLName name = this.exprParser.name();
                stmt.getAdbColumnsGroup().add((SQLIdentifierExpr) name);
                if (lexer.token() == COMMA) {
                    accept(COMMA);
                    continue;
                }
                break;
            }
            accept(RPAREN);
            if (lexer.token() == WHERE) {
                accept(WHERE);
                SQLExpr where = this.exprParser.expr();
                stmt.setAdbWhere(where);
            }
        }

        if (lexer.token() == Token.PARTITION) {
            stmt.setPartition(
                    parsePartitionRef()
            );
        }

        if (lexer.token() == Token.COMPUTE) {
            lexer.nextToken();
            acceptIdentifier("STATISTICS");
            stmt.setComputeStatistics(true);
        }

        if (lexer.token() == Token.FOR) {
            lexer.nextToken();
            acceptIdentifier("COLUMNS");
            stmt.setForColums(true);
        }

        if (lexer.identifierEquals(FnvHash.Constants.CACHE)) {
            lexer.nextToken();
            acceptIdentifier("METADATA");
            stmt.setCacheMetadata(true);
        }

        if (lexer.identifierEquals(FnvHash.Constants.NOSCAN)) {
            lexer.nextToken();
            stmt.setNoscan(true);
        }

        return stmt;
    }

    public MySqlOptimizeStatement parseOptimize() {
        accept(Token.OPTIMIZE);
        accept(TABLE);

        MySqlOptimizeStatement stmt = new MySqlOptimizeStatement();
        List<SQLName> names = new ArrayList<SQLName>();
        this.exprParser.names(names, stmt);

        for (SQLName name : names) {
            stmt.addTableSource(new SQLExprTableSource(name));
        }
        return stmt;
    }

    public SQLStatement parseReset() {
        acceptIdentifier(RESET);

        MySqlResetStatement stmt = new MySqlResetStatement();

        for (; ; ) {
            if (lexer.token() == Token.IDENTIFIER) {
                if (lexer.identifierEquals("QUERY")) {
                    lexer.nextToken();
                    accept(Token.CACHE);
                    stmt.getOptions().add("QUERY CACHE");
                } else {
                    stmt.getOptions().add(lexer.stringVal());
                    lexer.nextToken();
                }

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
            }
            break;
        }

        return stmt;
    }

    public boolean parseStatementListDialect(List<SQLStatement> statementList) {
        if (lexer.identifierEquals("PREPARE")) {
            MySqlPrepareStatement stmt = parsePrepare();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("EXECUTE")) {
            acceptIdentifier("EXECUTE");

            if (lexer.identifierEquals("RESTART") || lexer.identifierEquals("UPDATE")) {
                MySqlExecuteForAdsStatement stmt = parseExecuteForAds();
                statementList.add(stmt);
            } else {
                MySqlExecuteStatement stmt = parseExecute();
                statementList.add(stmt);
            }
            return true;
        }

        if (lexer.identifierEquals("DEALLOCATE")) {
            MysqlDeallocatePrepareStatement stmt = parseDeallocatePrepare();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("LOAD")) {
            SQLStatement stmt = parseLoad();
            statementList.add(stmt);
            return true;
        }

        if (lexer.token() == Token.REPLACE) {
            SQLReplaceStatement stmt = parseReplace();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("START")) {
            SQLStartTransactionStatement stmt = parseStart();
            statementList.add(stmt);
            return true;
        }

        if (lexer.token() == Token.SHOW) {
            SQLStatement stmt = parseShow();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("CLEAR")) {
            lexer.nextToken();

            if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("DDL")) {
                // CLEAR DDL CACHE { ALL | <job_id> [ , <job_id> ] ... }
                lexer.nextToken();
                accept(Token.CACHE);

                DrdsClearDDLJobCache stmt = new DrdsClearDDLJobCache();
                if (Token.ALL == lexer.token()) {
                    lexer.nextToken();
                    stmt.setAllJobs(true);
                    statementList.add(stmt);
                    return true;
                } else {
                    while (true) {
                        stmt.addJobId(lexer.integerValue().longValue());
                        accept(Token.LITERAL_INT);
                        if (Token.COMMA == lexer.token()) {
                            lexer.nextToken();
                        } else if (lexer.token() == Token.EOF || lexer.token() == SEMI) {
                            break;
                        } else {
                            throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                        }
                    }
                    statementList.add(stmt);
                    return true;
                }
            }

            acceptIdentifier("PLANCACHE");

            statementList.add(new MySqlClearPlanCacheStatement());
            return true;
        }

        if (lexer.identifierEquals("DISABLED")) {
            lexer.nextToken();
            acceptIdentifier("PLANCACHE");

            statementList.add(new MySqlDisabledPlanCacheStatement());
            return true;
        }

        if (lexer.identifierEquals("XA")) {
            lexer.nextToken();
            MySqlXAStatement stmt = new MySqlXAStatement();
            String typeStr = lexer.stringVal();
            stmt.setType(
                    MySqlXAStatement.XAType.of(typeStr)
            );
            lexer.nextToken();

            if (lexer.token() != EOF && lexer.token() != SEMI) {
                SQLExpr xid = exprParser.expr();
                stmt.setId(xid);
            }

            statementList.add(stmt);
            return true;
        }

        if (lexer.token() == Token.EXPLAIN) {
            SQLStatement stmt = this.parseExplain();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(BINLOG)) {
            SQLStatement stmt = parseBinlog();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(RESET)) {
            SQLStatement stmt = parseReset();
            statementList.add(stmt);
            return true;
        }

        if (lexer.token() == Token.ANALYZE) {
            SQLStatement stmt = parseAnalyze();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.ARCHIVE)) {
            SQLStatement stmt = parseArchive();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.BACKUP)) {
            SQLStatement stmt = parseBackup();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.RESTORE)) {
            SQLStatement stmt = parseRestore();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("BUILD")) {
            SQLStatement stmt = parseBuildTable();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("CANCEL")) {
            SQLStatement stmt = parseCancelJob();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.EXPORT)) {
            lexer.nextToken();
            if (lexer.token() == TABLE) {
                SQLStatement stmt = parseExportTable();
                statementList.add(stmt);
            } else if (lexer.token() == Token.DATABASE) {
                SQLStatement stmt = parseExportDB();
                statementList.add(stmt);
            }
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.IMPORT)) {
            lexer.nextToken();
            if (lexer.token() == TABLE) {
                SQLStatement stmt = parseImportTable();
                statementList.add(stmt);
            } else if (lexer.token() == Token.DATABASE) {
                SQLStatement stmt = parseImportDB();
                statementList.add(stmt);
            }
            return true;
        }

        if (lexer.identifierEquals("SUBMIT")) {
            lexer.nextToken();
            acceptIdentifier("JOB");

            SQLStatement stmt = parseSubmitJob();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.MIGRATE)) {
            SQLStatement stmt = parseMigrate();
            statementList.add(stmt);
            return true;
        }

        if (lexer.token() == Token.OPTIMIZE) {
            SQLStatement stmt = parseOptimize();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("HELP")) {
            lexer.nextToken();
            MySqlHelpStatement stmt = new MySqlHelpStatement();
            stmt.setContent(this.exprParser.primary());
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("FLUSH")) {
            SQLStatement stmt = parseFlush();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.SYNC)) {
            SQLStatement stmt = parseSync();
            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.INIT)) {
            statementList.add(
                    new SQLExprStatement(
                            this.exprParser.expr()));
            return true;
        }

        // DRDS async DDL.
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals(FnvHash.Constants.RECOVER)) {
            // RECOVER DDL {ALL | <job_id> [, <job_id>] ...}
            lexer.nextToken();
            acceptIdentifier("DDL");
            DrdsRecoverDDLJob stmt = new DrdsRecoverDDLJob();
            if (Token.ALL == lexer.token()) {
                lexer.nextToken();
                stmt.setAllJobs(true);
                statementList.add(stmt);
                return true;
            } else {
                while (true) {
                    stmt.addJobId(lexer.integerValue().longValue());
                    accept(Token.LITERAL_INT);
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                    } else if (lexer.token() == Token.EOF || lexer.token() == SEMI) {
                        break;
                    } else {
                        throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                    }
                }
                statementList.add(stmt);
                return true;
            }
        }

        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals(FnvHash.Constants.REMOVE)) {
            // REMOVE DDL { ALL { COMPLETED | PENDING } | <job_id> [, <job_id>] ...}
            lexer.nextToken();
            acceptIdentifier("DDL");
            DrdsRemoveDDLJob stmt = new DrdsRemoveDDLJob();
            if (Token.ALL == lexer.token()) {
                lexer.nextToken();
                if (lexer.identifierEquals("COMPLETED")) {
                    lexer.nextToken();
                    stmt.setAllCompleted(true);
                } else if (lexer.identifierEquals("PENDING")) {
                    lexer.nextToken();
                    stmt.setAllPending(true);
                } else {
                    throw new ParserException("syntax error, expect COMPLETED or PENDING, actual " + lexer.token() + ", " + lexer.info());
                }
            } else {
                while (true) {
                    stmt.addJobId(lexer.integerValue().longValue());
                    accept(Token.LITERAL_INT);
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                    } else if (lexer.token() == Token.EOF || lexer.token() == SEMI) {
                        break;
                    } else {
                        throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                    }
                }
            }
            statementList.add(stmt);
            return true;
        }

        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("INSPECT")) {
            // INSPECT DDL CACHE
            lexer.nextToken();
            acceptIdentifier("DDL");
            accept(Token.CACHE);
            statementList.add(new DrdsInspectDDLJobCache());
            return true;
        }

        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals(FnvHash.Constants.CHANGE)) {
            // CHANGE DDL <job_id> { SKIP | ADD } <group_and_table_name> [ , <group_and_table_name> ] ...
            Lexer.SavePoint mark = lexer.mark();
            lexer.nextToken();
            if (lexer.identifierEquals("DDL")) {
                lexer.nextToken();

                DrdsChangeDDLJob stmt = new DrdsChangeDDLJob();

                stmt.setJobId(lexer.integerValue().longValue());
                accept(Token.LITERAL_INT);

                if (lexer.identifierEquals("SKIP")) {
                    lexer.nextToken();
                    stmt.setSkip(true);
                } else if (lexer.identifierEquals("ADD")) {
                    lexer.nextToken();
                    stmt.setAdd(true);
                } else {
                    throw new ParserException("syntax error, expect SKIP or ADD, actual " + lexer.token() + ", " + lexer.info());
                }

                StringBuilder builder = new StringBuilder();
                while (true) {
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                        stmt.addGroupAndTableNameList(builder.toString());
                        builder = new StringBuilder();
                    } else if (lexer.token() == Token.EOF || lexer.token() == SEMI) {
                        stmt.addGroupAndTableNameList(builder.toString());
                        break;
                    } else if (lexer.token() == Token.COLON) {
                        builder.append(':');
                        lexer.nextToken();
                    } else if (lexer.token() == Token.DOT) {
                        builder.append('.');
                        lexer.nextToken();
                    } else {
                        builder.append(lexer.stringVal());
                        lexer.nextToken();
                    }
                }

                statementList.add(stmt);
                return true;
            }
            lexer.reset(mark);
        }

        if (isEnabled(SQLParserFeature.DRDSBaseline) && lexer.identifierEquals("BASELINE")) {
            lexer.nextToken();

            DrdsBaselineStatement stmt = new DrdsBaselineStatement();

            if (Token.EOF == lexer.token() || SEMI == lexer.token() ||
                    lexer.stringVal().isEmpty() || lexer.stringVal().equalsIgnoreCase("BASELINE")) {
                throw new ParserException("syntax error, expect baseline operation, actual " + lexer.token() + ", " + lexer.info());
            }

            stmt.setOperation(lexer.stringVal());

            lexer.setToken(Token.COMMA); // Hack here: Set previous comma to deal with negative number.
            lexer.nextToken();

            if (lexer.identifierEquals(FnvHash.Constants.SQL)) {
                // Parse select.
                lexer.nextToken();

                if (lexer.token() == Token.HINT) {
                    stmt.setHeadHints(this.exprParser.parseHints());
                }

                MySqlSelectParser selectParser = createSQLSelectParser();
                stmt.setSelect(selectParser.select());
            } else {
                // Parse id list.
                while (lexer.token() != Token.EOF && lexer.token() != SEMI) {
                    stmt.addBaselineId(lexer.integerValue().longValue());
                    accept(Token.LITERAL_INT);
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                    }
                }
            }

            statementList.add(stmt);
            return true;
        }

        if (lexer.token() == Token.DESC || lexer.identifierEquals(DESCRIBE)) {
            SQLStatement stmt = parseDescribe();
            statementList.add(stmt);
            return true;
        }

        if (lexer.token() == Token.LOCK) {
            lexer.nextToken();
            String val = lexer.stringVal();
            boolean isLockTables = TABLES.equalsIgnoreCase(val) && lexer.token() == Token.IDENTIFIER;
            boolean isLockTable = "TABLE".equalsIgnoreCase(val) && lexer.token() == TABLE;
            if (isLockTables || isLockTable) {
                lexer.nextToken();
            } else {
                setErrorEndPos(lexer.pos());
                throw new ParserException("syntax error, expect TABLES or TABLE, actual " + lexer.token() + ", " + lexer.info());
            }

            MySqlLockTableStatement stmt = new MySqlLockTableStatement();

            for (; ; ) {
                MySqlLockTableStatement.Item item = new MySqlLockTableStatement.Item();

                SQLExprTableSource tableSource = null;
                SQLName tableName = this.exprParser.name();

                if (lexer.token() == Token.AS) {
                    lexer.nextToken();
                    String as = lexer.stringVal();
                    tableSource = new SQLExprTableSource(tableName, as);
                    lexer.nextToken();
                } else {
                    tableSource = new SQLExprTableSource(tableName);
                }
                item.setTableSource(tableSource);
                stmt.getItems().add(item);

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }

                if (lexer.identifierEquals(READ)) {
                    lexer.nextToken();
                    if (lexer.identifierEquals(LOCAL)) {
                        lexer.nextToken();
                        item.setLockType(LockType.READ_LOCAL);
                    } else {
                        item.setLockType(LockType.READ);
                    }
                } else if (lexer.identifierEquals(WRITE)) {
                    lexer.nextToken();
                    item.setLockType(LockType.WRITE);
                } else if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
                    lexer.nextToken();
                    acceptIdentifier(WRITE);
                    lexer.nextToken();
                    item.setLockType(LockType.LOW_PRIORITY_WRITE);
                } else {
                    throw new ParserException(
                            "syntax error, expect READ or WRITE OR AS, actual " + lexer.token() + ", " + lexer.info());
                }

                if (lexer.token() == Token.HINT) {
                    item.setHints(this.exprParser.parseHints());
                }

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }

            statementList.add(stmt);
            return true;
        }

        if (lexer.identifierEquals("UNLOCK")) {
            lexer.nextToken();
            String val = lexer.stringVal();
            boolean isUnLockTables = TABLES.equalsIgnoreCase(val) && lexer.token() == Token.IDENTIFIER;
            boolean isUnLockTable = "TABLE".equalsIgnoreCase(val) && lexer.token() == TABLE;
            statementList.add(new MySqlUnlockTablesStatement());
            if (isUnLockTables || isUnLockTable) {
                lexer.nextToken();
            } else {
                setErrorEndPos(lexer.pos());
                throw new ParserException("syntax error, expect TABLES or TABLE, actual " + lexer.token() + ", " + lexer.info());
            }
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.CHECKSUM)) {
            statementList.add(this.parseChecksum());
            return true;
        }

        if (lexer.token() == Token.HINT) {
            List<SQLCommentHint> hints = this.exprParser.parseHints();

            boolean tddlHints = false;
            boolean accept = false;

            boolean acceptHint = false;
            switch (lexer.token()) {
                case SELECT:
                case WITH:
                case DELETE:
                case UPDATE:
                case INSERT:
                case SHOW:
                case REPLACE:
                case TRUNCATE:
                case DROP:
                case ALTER:
                case CREATE:
                case CHECK:
                case SET:
                case DESC:
                case OPTIMIZE:
                case ANALYZE:
                case KILL:
                case EXPLAIN:
                case LPAREN:
                    acceptHint = true;
                    break;
                case IDENTIFIER:
                    acceptHint = lexer.hashLCase() == FnvHash.Constants.DUMP
                            || lexer.hashLCase() == FnvHash.Constants.RENAME
                            || lexer.hashLCase() == FnvHash.Constants.DESCRIBE;
                    break;
                default:
                    break;
            }
            if (hints.size() >= 1
                    && statementList.isEmpty()
                    && acceptHint) {
                SQLCommentHint hint = hints.get(0);
                String hintText = hint.getText().toUpperCase();
                if (hintText.startsWith("+TDDL")
                        || hintText.startsWith("+ TDDL")
                        || hintText.startsWith("TDDL")
                        || hintText.startsWith("!TDDL")) {
                    tddlHints = true;
                } else if (hintText.startsWith("+")) {
                    accept = true;
                }
            }

            if (tddlHints) {
                SQLStatementImpl stmt = (SQLStatementImpl) this.parseStatement();
                stmt.setHeadHints(hints);
                statementList.add(stmt);
                return true;
            } else if (accept) {
                SQLStatementImpl stmt = (SQLStatementImpl) this.parseStatement();
                stmt.setHeadHints(hints);
                statementList.add(stmt);
                return true;
            }

            MySqlHintStatement stmt = new MySqlHintStatement();
            stmt.setHints(hints);

            statementList.add(stmt);
            return true;
        }

        if (lexer.token() == Token.BEGIN) {
            Lexer.SavePoint mark = lexer.mark();
            lexer.nextToken();
            if (lexer.token() == SEMI || lexer.token() == Token.EOF || lexer.token() == Token.IDENTIFIER) {
                lexer.reset(mark);
                statementList.add(this.parseTiDBBeginStatment());
                return true;
            } else {
                lexer.reset(mark);
            }
            statementList.add(this.parseBlock());
            return true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.ADD)) {
            statementList.add(parseAddManageInstanceGroup());
            return true;
        }

        if (lexer.token() == Token.IDENTIFIER) {
            String label = lexer.stringVal();
            Lexer.SavePoint savePoint = lexer.markOut();
            lexer.nextToken();
            if (lexer.token() == Token.VARIANT && lexer.stringVal().equals(":")) {
                lexer.nextToken();
                if (lexer.token() == Token.LOOP) {
                    // parse loop statement
                    statementList.add(this.parseLoop(label));
                } else if (lexer.token() == Token.WHILE) {
                    // parse while statement with label
                    statementList.add(this.parseWhile(label));
                } else if (lexer.token() == Token.BEGIN) {
                    // parse begin-end statement with label
                    SQLBlockStatement block = this.parseBlock(label);
                    statementList.add(block);
                } else if (lexer.token() == Token.REPEAT) {
                    // parse repeat statement with label
                    statementList.add(this.parseRepeat(label));
                }
                return true;
            } else {
                lexer.reset(savePoint);
            }

        }

        if (lexer.token() == Token.CHECK) {
            final Lexer.SavePoint mark = lexer.mark();
            lexer.nextToken();

            if (lexer.token() == TABLE) {
                lexer.nextToken();

                MySqlCheckTableStatement stmt = new MySqlCheckTableStatement();
                for (; ; ) {
                    SQLName table = this.exprParser.name();
                    stmt.addTable(new SQLExprTableSource(table));

                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }

                    break;
                }
                statementList.add(stmt);
            }
            return true;
        }
        String strVal = lexer.stringVal();
        if (strVal.equalsIgnoreCase("SPLIT")) {
            TidbSplitTableStatement stmt = this.parseTiDBSplitTableStatement();
            statementList.add(stmt);
            return true;
        }

        return false;
    }

    private TidbSplitTableStatement parseTiDBSplitTableStatement() {
        TidbSplitTableStatement stmt = new TidbSplitTableStatement();
        lexer.nextToken();
        Lexer.SavePoint mark = lexer.mark();
        String strVal = lexer.stringVal();
        if (lexer.token() == PARTITION) {
            accept(PARTITION);
            stmt.setSplitSyntaxOptionPartition(true);
        } else if (strVal.equalsIgnoreCase("REGION")) {
            acceptIdentifier("REGION");
            accept(FOR);
            stmt.setSplitSyntaxOptionRegionFor(true);
        } else {
            lexer.reset(mark);
        }
        strVal = lexer.stringVal();
        accept(TABLE);
        SQLName tableNameTmp = this.exprParser.name();
        SQLExprTableSource sqlExprTableSource = new SQLExprTableSource(tableNameTmp);
        stmt.setTableName(sqlExprTableSource);
        strVal = lexer.stringVal();
        if (lexer.token() == INDEX) {
            accept(INDEX);
            SQLName indexName = this.exprParser.name();
            stmt.setIndexName(indexName);
        } else if (lexer.token() == PARTITION) {
            accept(PARTITION);
            accept(LPAREN);
            this.exprParser.exprList(stmt.getPartitionNameListOptions(), stmt);
            accept(RPAREN);
            if (lexer.token() == INDEX) {
                accept(INDEX);
                SQLName indexName = this.exprParser.name();
                stmt.setIndexName(indexName);
            }
        }
        if (lexer.token() == BETWEEN) {
            accept(BETWEEN);
            accept(LPAREN);
            List<SQLExpr> sqlExprBetweens = new ArrayList<>();
            this.exprParser.exprList(sqlExprBetweens, stmt);
            stmt.setSplitOptionBetween(sqlExprBetweens);
            accept(RPAREN);
            accept(AND);
            accept(LPAREN);
            List<SQLExpr> sqlExprAnds = new ArrayList<>();
            this.exprParser.exprList(sqlExprAnds, stmt);
            stmt.setSplitOptionAnd(sqlExprAnds);
            accept(RPAREN);
            acceptIdentifier("REGIONS");
            SQLIntegerExpr num = this.exprParser.integerExpr();
            stmt.setSplitOptionRegions(num.getNumber().longValue());
        } else if (lexer.token() == BY) {
            accept(BY);
            accept(LPAREN);
            List<SQLExpr> byItems = new ArrayList<>();
            this.exprParser.exprList(byItems, stmt);
            accept(RPAREN);
            stmt.getSplitOptionBys().add(byItems);
            while (lexer.token() == COMMA) {
                accept(COMMA);
                accept(LPAREN);
                byItems = new ArrayList<>();
                this.exprParser.exprList(byItems, stmt);
                accept(RPAREN);
                stmt.getSplitOptionBys().add(byItems);
            }

        } else if (lexer.token() == PARTITION) {
            accept(PARTITION);
        }
        return stmt;
    }
    private SQLStatement parseArchive() {
        lexer.nextToken();
        accept(TABLE);
        SQLArchiveTableStatement stmt = new SQLArchiveTableStatement();

        SQLName tableName = this.exprParser.name();
        stmt.setTable(tableName);
        stmt.setType(new SQLIdentifierExpr("UPLOAD"));

        if (lexer.token() == Token.LITERAL_INT) {
            for (; ; ) {
                stmt.getSpIdList().add(this.exprParser.integerExpr());
                String pidStr = lexer.stringVal();
                accept(Token.VARIANT);
                String s = pidStr.replaceAll(":", "");
                if (StringUtils.isEmpty(s)) {
                    stmt.getpIdList().add(exprParser.integerExpr());
                } else {
                    stmt.getpIdList().add(new SQLIntegerExpr(Integer.valueOf(s)));
                }

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }

                break;
            }
        }
        return stmt;
    }

    private SQLStatement parseBackup() {
        lexer.nextToken();
        SQLBackupStatement stmt = new SQLBackupStatement();

        String type = "BACKUP_DATA";
        String action = "BACKUP";

        if (lexer.identifierEquals(FnvHash.Constants.DATA)) {
            lexer.nextToken();
            accept(Token.INTO);
            type = "BACKUP_DATA";

            for (; ; ) {
                stmt.getProperties().add(new SQLCharExpr(lexer.stringVal()));
                accept(Token.LITERAL_CHARS);
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
        } else if (lexer.identifierEquals(FnvHash.Constants.LOG)) {
            type = "BACKUP_LOG";
            lexer.nextToken();

            if (lexer.identifierEquals("LIST_LOGS")) {
                lexer.nextToken();
                action = "LIST_LOG";

            } else if (lexer.identifierEquals(FnvHash.Constants.STATUS)) {
                lexer.nextToken();
                action = "STATUS";
            } else if (lexer.token() == Token.INTO) {
                lexer.nextToken();
                for (; ; ) {
                    stmt.getProperties().add(new SQLCharExpr(lexer.stringVal()));
                    accept(Token.LITERAL_CHARS);
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
            }

        } else if (lexer.identifierEquals("CANCEL")) {
            lexer.nextToken();
            type = "BACKUP_DATA";
            action = "BACKUP_CANCEL";
            stmt.getProperties().add(new SQLCharExpr(lexer.stringVal()));
            accept(Token.LITERAL_CHARS);
        }

        stmt.setType(new SQLIdentifierExpr(type));
        stmt.setAction(new SQLIdentifierExpr(action));

        return stmt;
    }

    private SQLStatement parseRestore() {
        lexer.nextToken();

        String type = "DATA";

        SQLRestoreStatement stmt = new SQLRestoreStatement();

        if (lexer.identifierEquals(FnvHash.Constants.DATA)) {
            lexer.nextToken();
            type = "DATA";
        } else if (lexer.identifierEquals(FnvHash.Constants.LOG)) {
            lexer.nextToken();
            type = "LOG";
        }

        stmt.setType(new SQLIdentifierExpr(type));

        accept(Token.FROM);
        for (; ; ) {
            stmt.getProperties().add(new SQLCharExpr(lexer.stringVal()));
            accept(Token.LITERAL_CHARS);
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }
        return stmt;
    }

    private SQLStatement parseBuildTable() {
        lexer.nextToken();

        SQLBuildTableStatement stmt = new SQLBuildTableStatement();

        accept(TABLE);

        stmt.setTable(this.exprParser.name());

        if (lexer.identifierEquals(FnvHash.Constants.VERSION)) {
            lexer.nextToken();
            accept(Token.EQ);
            stmt.setVersion(this.exprParser.integerExpr());
        }

        if (lexer.token() == WITH) {
            lexer.nextToken();
            acceptIdentifier("SPLIT");

            stmt.setWithSplit(true);
        }

        if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
            lexer.nextToken();

            accept(EQ);

            if (lexer.token() == TRUE) {
                lexer.nextToken();
                stmt.setForce(true);
            } else if (lexer.token() == FALSE) {
                lexer.nextToken();
                stmt.setForce(false);
            }
        }

        return stmt;
    }

    private SQLStatement parseCancelJob() {
        lexer.nextToken();

        // DRDS async DDL.
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("DDL")) {
            // CANCEL DDL <job_id> [, <job_id>] ...
            lexer.nextToken();
            DrdsCancelDDLJob cancelDDLJob = new DrdsCancelDDLJob();
            while (true) {
                cancelDDLJob.addJobId(lexer.integerValue().longValue());
                accept(Token.LITERAL_INT);
                if (Token.COMMA == lexer.token()) {
                    lexer.nextToken();
                } else if (lexer.token() == Token.EOF || lexer.token() == SEMI) {
                    break;
                } else {
                    throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                }
            }
            return cancelDDLJob;
        }

        SQLCancelJobStatement stmt = new SQLCancelJobStatement();

        if (lexer.identifierEquals("JOB")) {
            lexer.nextToken();
        } else if (lexer.identifierEquals("LOAD_JOB")) {
            lexer.nextToken();
        } else if (lexer.identifierEquals("SYNC_JOB")) {
            lexer.nextToken();
            stmt.setImport(true);
        }

        stmt.setJobName(this.exprParser.name());

        return stmt;

    }

    protected SQLStatement parseExportTable() {
        accept(TABLE);
        SQLExportTableStatement stmt = new SQLExportTableStatement();
        stmt.setTable(
                new SQLExprTableSource(
                        this.exprParser.name()));
        return stmt;
    }

    protected SQLStatement parseExportDB() {
        accept(Token.DATABASE);
        SQLExportDatabaseStatement stmt = new SQLExportDatabaseStatement();
        stmt.setDb(this.exprParser.name());

        if (lexer.identifierEquals("REALTIME")) {
            lexer.nextToken();
            accept(Token.EQ);
            if ("y".equalsIgnoreCase(lexer.stringVal())) {
                lexer.nextToken();
                stmt.setRealtime(true);
            } else if ("n".equalsIgnoreCase(lexer.stringVal())) {
                lexer.nextToken();
                stmt.setRealtime(false);
            } else {
                throw new ParserException("Invalid 'realtime' option, should be 'Y' or 'N'. ");
            }
        }

        return stmt;
    }

    protected SQLStatement parseRaftLeaderTransfer() {
        acceptIdentifier("RAFT_LEADER_TRANSFER");
        MySqlRaftLeaderTransferStatement stmt = new MySqlRaftLeaderTransferStatement();

        acceptIdentifier("SHARD");
        accept(Token.EQ);

        stmt.setShard(exprParser.charExpr());

        accept(Token.FROM);
        accept(Token.EQ);
        stmt.setFrom(exprParser.charExpr());

        accept(Token.TO);
        accept(Token.EQ);
        stmt.setTo(exprParser.charExpr());

        acceptIdentifier("TIMEOUT");
        accept(Token.EQ);
        stmt.setTimeout(exprParser.integerExpr());

        return stmt;
    }

    protected SQLStatement parseRaftMemeberChange() {
        acceptIdentifier("RAFT_MEMBER_CHANGE");
        MySqlRaftMemberChangeStatement stmt = new MySqlRaftMemberChangeStatement();

        if (lexer.identifierEquals("NOLEADER")) {
            lexer.nextToken();
            stmt.setNoLeader(true);
        }

        acceptIdentifier("SHARD");
        accept(Token.EQ);

        stmt.setShard(exprParser.charExpr());

        acceptIdentifier("HOST");
        accept(Token.EQ);
        stmt.setHost(exprParser.charExpr());

        acceptIdentifier("STATUS");
        accept(Token.EQ);
        stmt.setStatus(exprParser.charExpr());

        if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
            lexer.nextToken();
            stmt.setForce(true);
        }

        return stmt;
    }

    protected SQLStatement parseMigrate() {
        MySqlMigrateStatement stmt = new MySqlMigrateStatement();
        acceptIdentifier("MIGRATE");
        accept(Token.DATABASE);

        stmt.setSchema(exprParser.name());

        acceptIdentifier("SHARDS");
        accept(Token.EQ);
        stmt.setShardNames(exprParser.charExpr());

        if (lexer.token() == GROUP) {
            lexer.nextToken();
            stmt.setMigrateType(new SQLIntegerExpr(0));
        } else if (lexer.identifierEquals(FnvHash.Constants.HOST)) {
            lexer.nextToken();
            stmt.setMigrateType(new SQLIntegerExpr(1));
        }

        accept(Token.FROM);
        stmt.setFromInsId(exprParser.charExpr());

        if (lexer.token() == Token.VARIANT) {
            lexer.nextToken();
            stmt.setFromInsIp(exprParser.charExpr());

            String variant = lexer.stringVal();
            Integer number = Integer.valueOf(variant.substring(1, variant.length()));
            stmt.setFromInsPort(new SQLIntegerExpr(number));
            accept(Token.VARIANT);

            accept(Token.VARIANT);
            stmt.setFromInsStatus(exprParser.charExpr());
        }

        accept(Token.TO);
        stmt.setToInsId(exprParser.charExpr());

        if (lexer.token() == Token.VARIANT) {
            lexer.nextToken();
            stmt.setToInsIp(exprParser.charExpr());

            String variant = lexer.stringVal();
            Integer number = Integer.valueOf(variant.substring(1, variant.length()));
            stmt.setToInsPort(new SQLIntegerExpr(number));
            accept(Token.VARIANT);

            accept(Token.VARIANT);
            stmt.setToInsStatus(exprParser.charExpr());
        }

        return stmt;
    }

    protected SQLStatement parseImportDB() {
        accept(Token.DATABASE);
        SQLImportDatabaseStatement stmt = new SQLImportDatabaseStatement();

        stmt.setDb(this.exprParser.name());

        if (lexer.identifierEquals(FnvHash.Constants.STATUS)) {
            lexer.nextToken();
            accept(Token.EQ);
            stmt.setStatus(this.exprParser.name());
        }

        return stmt;
    }

    protected SQLStatement parseImportTable() {
        SQLImportTableStatement stmt = new SQLImportTableStatement();
        if (lexer.identifierEquals(FnvHash.Constants.EXTERNAL)) {
            lexer.nextToken();
            stmt.setExtenal(true);
        }

        accept(TABLE);
        stmt.setTable(
                new SQLExprTableSource(
                        this.exprParser.name()));

        acceptIdentifier("VERSION");
        accept(Token.EQ);
        stmt.setVersion(this.exprParser.integerExpr());

        if (lexer.identifierEquals("BUILD")) {
            lexer.nextToken();
            accept(Token.EQ);
            if ("Y".equalsIgnoreCase(lexer.stringVal())) {
                lexer.nextToken();
                stmt.setUsingBuild(true);
            } else if ("N".equalsIgnoreCase(lexer.stringVal())) {
                lexer.nextToken();
                stmt.setUsingBuild(false);
            } else {
                throw new ParserException("Invalid 'build' option, should be 'Y' or 'N'. ");
            }
        }

        return stmt;
    }

    protected SQLStatement parseSubmitJob() {
        SQLSubmitJobStatement stmt = new SQLSubmitJobStatement();
        if (lexer.identifierEquals("AWAIT")) {
            lexer.nextToken();
            stmt.setAwait(true);
        }

        stmt.setStatment(this.parseStatement());

        return stmt;
    }

    public SQLStatement parseSync() {
        lexer.nextToken();

        if (lexer.identifierEquals("RAFT_LEADER_TRANSFER")) {
            return parseRaftLeaderTransfer();
        } else if (lexer.identifierEquals("RAFT_MEMBER_CHANGE")) {
            return parseRaftMemeberChange();
        } else {
            acceptIdentifier("META");
            acceptIdentifier("TABLES");

            SQLSyncMetaStatement stmt = new SQLSyncMetaStatement();

            if (lexer.token() == Token.FROM) {
                lexer.nextToken();
                stmt.setFrom(
                        this.exprParser.name()
                );
            }

            if (lexer.token() == Token.LIKE) {
                lexer.nextToken();
                stmt.setLike(
                        this.exprParser.expr()
                );
            }
            return stmt;
        }

    }

    public SQLStatement parseFlush() {
        acceptIdentifier("FLUSH");
        MySqlFlushStatement stmt = new MySqlFlushStatement();

        if (lexer.identifierEquals("NO_WRITE_TO_BINLOG")) {
            lexer.nextToken();
            stmt.setNoWriteToBinlog(true);
        }

        if (lexer.identifierEquals("LOCAL")) {
            lexer.nextToken();
            stmt.setLocal(true);
        }

        for (; ; ) {
            if (lexer.token() == Token.BINARY || lexer.identifierEquals("BINARY")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setBinaryLogs(true);
            } else if (lexer.identifierEquals("DES_KEY_FILE")) {
                lexer.nextToken();
                stmt.setDesKeyFile(true);
            } else if (lexer.identifierEquals("ENGINE")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setEngineLogs(true);
            } else if (lexer.identifierEquals("ERROR")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setErrorLogs(true);
            } else if (lexer.identifierEquals("GENERAL")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setGeneralLogs(true);
            } else if (lexer.identifierEquals("HOSTS")) {
                lexer.nextToken();
                stmt.setHots(true);
            } else if (lexer.identifierEquals("LOGS")) {
                lexer.nextToken();
                stmt.setLogs(true);
            } else if (lexer.identifierEquals("PRIVILEGES")) {
                lexer.nextToken();
                stmt.setPrivileges(true);
            } else if (lexer.identifierEquals("OPTIMIZER_COSTS")) {
                lexer.nextToken();
                stmt.setOptimizerCosts(true);
            } else if (lexer.identifierEquals("QUERY")) {
                lexer.nextToken();
                accept(Token.CACHE);
                stmt.setQueryCache(true);
            } else if (lexer.identifierEquals("RELAY")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setRelayLogs(true);
                if (lexer.token() == Token.FOR) {
                    lexer.nextToken();
                    acceptIdentifier("CHANNEL");
                    stmt.setRelayLogsForChannel(this.exprParser.primary());
                }
            } else if (lexer.identifierEquals("SLOW")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setSlowLogs(true);
            } else if (lexer.identifierEquals(STATUS)) {
                lexer.nextToken();
                stmt.setStatus(true);
            } else if (lexer.identifierEquals("USER_RESOURCES")) {
                lexer.nextToken();
                stmt.setUserResources(true);
            } else if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            } else {
                break;
            }
        }

        if (lexer.identifierEquals("TABLES") || lexer.token() == TABLE) {
            lexer.nextToken();

            stmt.setTableOption(true);

            if (lexer.token() == WITH) {
                lexer.nextToken();
                acceptIdentifier("READ");
                accept(Token.LOCK);
                stmt.setWithReadLock(true);
            }
            for (; ; ) {
                if (lexer.token() == Token.IDENTIFIER) {
                    for (; ; ) {
                        SQLName name = this.exprParser.name();
                        stmt.addTable(name);

                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    break;
                }
                break;
            }

            if (!stmt.getTables().isEmpty()) {
                if (lexer.token() == Token.FOR) {
                    lexer.nextToken();
                    acceptIdentifier("EXPORT");
                    stmt.setForExport(true);
                } else if (lexer.token() == WITH) {
                    lexer.nextToken();
                    acceptIdentifier("READ");
                    accept(Token.LOCK);
                    stmt.setWithReadLock(true);
                } else if (lexer.identifierEquals(FnvHash.Constants.VERSION)) {
                    lexer.nextToken();
                    accept(Token.EQ);
                    stmt.setVersion(this.exprParser.integerExpr());
                }
            }

        }

        return stmt;
    }

    public SQLBeginStatement parseTiDBBeginStatment() {
        SQLBeginStatement tidbBegin = new SQLBeginStatement();
        tidbBegin.setDbType(dbType);
        accept(Token.BEGIN);
        if (lexer.token() == Token.IDENTIFIER) {
            tidbBegin.setTidbTxnMode(this.exprParser.name());
        }
        return tidbBegin;
    }

    public SQLBlockStatement parseBlock() {
        SQLBlockStatement block = new SQLBlockStatement();
        block.setDbType(dbType);

        accept(Token.BEGIN);
        List<SQLStatement> statementList = block.getStatementList();
        this.parseStatementList(statementList, -1, block);

        if (lexer.token() != Token.END
                && statementList.size() > 0
                && (statementList.get(statementList.size() - 1) instanceof SQLCommitStatement
                || statementList.get(statementList.size() - 1) instanceof SQLRollbackStatement)) {
            block.setEndOfCommit(true);
            return block;
        }
        accept(Token.END);

        return block;
    }

    public MySqlExplainStatement parseDescribe() {
        // see https://dev.mysql.com/doc/refman/5.7/en/explain.html
        MySqlExplainStatement describe = new MySqlExplainStatement();

        // {DESCRIBE | DESC}
        if (lexer.token() == Token.DESC || lexer.identifierEquals(DESCRIBE)) {
            lexer.nextToken();
            describe.setDescribe(true);
        } else {
            throw new ParserException("expect one of {DESCRIBE | DESC} , actual " + lexer.token() + ", " + lexer.info());
        }

        return parseExplain(describe);
    }

    public MySqlExplainStatement parseExplain() {
        // see https://dev.mysql.com/doc/refman/5.7/en/explain.html
        MySqlExplainStatement explain = new MySqlExplainStatement();
        lexer.computeRowAndColumn(explain);

        // {EXPLAIN}
        if (lexer.token() == Token.EXPLAIN) {
            lexer.nextToken();
        } else {
            throw new ParserException("expect EXPLAIN , actual " + lexer.token() + ", " + lexer.info());
        }

        return parseExplain(explain);
    }

    private MySqlExplainStatement parseExplain(MySqlExplainStatement explain) {
        if (lexer.identifierEquals(FnvHash.Constants.PLAN)) {
            Lexer.SavePoint mark = lexer.mark();
            lexer.nextToken();
            if (lexer.token() == Token.FOR) {
                lexer.nextToken();
            } else {
                lexer.reset(mark);
            }
        }

        if (lexer.token() == Token.ANALYZE) {
            lexer.nextToken();
            explain.setType("ANALYZE");
        }

        if (lexer.token() == Token.HINT) {
            List<SQLCommentHint> hints = this.exprParser.parseHints();
            explain.setHints(hints);
        }
        // see https://dev.mysql.com/doc/refman/5.7/en/explain.html

        switch (dbType) {
            case mysql:
            case ads:
            case presto:
            case trino:
                Lexer.SavePoint mark = lexer.mark();

                if (lexer.token() == Token.LPAREN) {
                    lexer.nextToken();
                    if (lexer.token() == Token.SELECT) {
                        lexer.reset(mark);
                        break;
                    }

                    for (; ; ) {
                        if (lexer.identifierEquals("FORMAT")) {
                            lexer.nextToken();
                            String format = lexer.stringVal();
                            explain.setFormat(format);
                            lexer.nextToken();
                        } else if (lexer.identifierEquals("TYPE")) {
                            lexer.nextToken();
                            String type = lexer.stringVal();
                            explain.setType(type);
                            lexer.nextToken();
                        } else {
                            break;
                        }

                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        } else {
                            break;
                        }
                    }
                    accept(Token.RPAREN);
                    explain.setParenthesis(true);
                }
                break;
            default:
                break;
        }

        boolean table = false;
        if (lexer.token() == Token.IDENTIFIER) {
            final long hash = lexer.hashLCase();
            String stringVal = lexer.stringVal();

            if (hash == FnvHash.Constants.EXTENDED) {
                explain.setExtended(true);
                lexer.nextToken();
            } else if (hash == FnvHash.Constants.PARTITIONS) {
                explain.setType(stringVal);
                lexer.nextToken();
            } else if (hash == FnvHash.Constants.OPTIMIZER) {
                explain.setOptimizer(true);
                lexer.nextToken();
            } else if (hash == FnvHash.Constants.FORMAT) {
                lexer.nextToken();
                accept(Token.EQ);

                String format = lexer.stringVal();
                explain.setFormat(format);
                accept(Token.IDENTIFIER);
            } else {
                explain.setTableName(exprParser.name());
                if (lexer.token() == Token.IDENTIFIER) {
                    explain.setColumnName(exprParser.name());
                } else if (lexer.token() == Token.LITERAL_CHARS) {
                    explain.setWild(exprParser.expr());
                }
                table = true;
            }
        }

        if (lexer.token() == Token.DISTRIBUTE) {
            lexer.nextToken();
            acceptIdentifier("INFO");
            explain.setDistributeInfo(true);
        }

        if (lexer.token() == Token.FOR) {
            lexer.nextToken();
            acceptIdentifier(CONNECTION);
            explain.setConnectionId(exprParser.expr());
        } else if (!table) {
            explain.setStatement(this.parseStatement());
        }

        return explain;
    }

    public SQLStatement parseShow() {
        accept(Token.SHOW);

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

        boolean isPhysical = false;

        if (lexer.identifierEquals(FnvHash.Constants.PHYSICAL)) {
            lexer.nextToken();
            isPhysical = true;
        }

        boolean full = false;
        if (lexer.token() == Token.FULL) {
            lexer.nextToken();
            full = true;
        } else if (lexer.token() == Token.HINT) {
            String hints = lexer.stringVal().toLowerCase();
            if (hints.endsWith(" full")
                    && hints.length() <= 11
                    && hints.charAt(0) == '!'
                    && hints.charAt(1) == '5') {
                lexer.nextToken();
                full = true;
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.STATS)) {
            lexer.nextToken();
            SQLShowStatisticStmt showStats = new SQLShowStatisticStmt();
            showStats.setDbType(DbType.mysql);
            if (full) {
                showStats.setFull(true);
            }
            return showStats;
        }

        if (lexer.identifierEquals(FnvHash.Constants.PROCESSLIST)) {
            lexer.nextToken();
            MySqlShowProcessListStatement stmt = new MySqlShowProcessListStatement();
            stmt.setFull(full);

            if (!full) {
                if (lexer.identifierEquals(FnvHash.Constants.MPP)) {
                    lexer.nextToken();
                    stmt.setMpp(true);
                }
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(
                        this.exprParser.expr());
            }

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

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

            return stmt;
        }

        if (lexer.identifierEquals("COLUMNS") || lexer.identifierEquals("FIELDS")) {
            lexer.nextToken();

            SQLShowColumnsStatement stmt = parseShowColumns();
            stmt.setFull(full);

            return stmt;
        }

        if (lexer.identifierEquals("COLUMNS")) {
            lexer.nextToken();

            SQLShowColumnsStatement stmt = parseShowColumns();

            return stmt;
        }

        if (lexer.identifierEquals(TABLES)) {
            lexer.nextToken();

            SQLShowTablesStatement stmt = parseShowTables();
            stmt.setFull(full);

            return stmt;
        }

        if (lexer.identifierEquals("DATABASES")) {
            lexer.nextToken();

            SQLShowDatabasesStatement stmt = parseShowDatabases(isPhysical);

            if (full) {
                stmt.setFull(true);
            }

            return stmt;
        }

        if (lexer.identifierEquals("WARNINGS")) {
            lexer.nextToken();

            MySqlShowWarningsStatement stmt = parseShowWarnings();

            return stmt;
        }

        if (lexer.identifierEquals("COUNT")) {
            lexer.nextToken();
            accept(Token.LPAREN);
            accept(Token.STAR);
            accept(Token.RPAREN);

            if (lexer.identifierEquals(FnvHash.Constants.ERRORS)) {
                lexer.nextToken();

                MySqlShowErrorsStatement stmt = new MySqlShowErrorsStatement();
                stmt.setCount(true);

                return stmt;
            } else {
                acceptIdentifier("WARNINGS");

                MySqlShowWarningsStatement stmt = new MySqlShowWarningsStatement();
                stmt.setCount(true);

                return stmt;
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.ERRORS)) {
            lexer.nextToken();

            MySqlShowErrorsStatement stmt = new MySqlShowErrorsStatement();
            stmt.setLimit(this.exprParser.parseLimit());

            return stmt;
        }

        if (lexer.identifierEquals(STATUS)) {
            lexer.nextToken();

            MySqlShowStatusStatement stmt = parseShowStatus();

            return stmt;
        }

        if (lexer.identifierEquals(DBLOCK)) {
            lexer.nextToken();

            return new MysqlShowDbLockStatement();
        }

        if (lexer.identifierEquals(VARIABLES)) {
            lexer.nextToken();

            SQLShowVariantsStatement stmt = parseShowVariants();

            return stmt;
        }

        if (lexer.identifierEquals(GLOBAL)) {
            lexer.nextToken();

            if (lexer.identifierEquals(STATUS)) {
                lexer.nextToken();
                MySqlShowStatusStatement stmt = parseShowStatus();
                stmt.setGlobal(true);
                return stmt;
            }

            if (lexer.identifierEquals(VARIABLES)) {
                lexer.nextToken();
                SQLShowVariantsStatement stmt = parseShowVariants();
                stmt.setGlobal(true);
                return stmt;
            }

            // DRDS GSI syntax.
            if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && (INDEX == lexer.token() || lexer.identifierEquals("INDEXES"))) {
                lexer.nextToken();

                DrdsShowGlobalIndex stmt = new DrdsShowGlobalIndex();
                if (Token.FROM == lexer.token()) {
                    lexer.nextToken();
                    stmt.setTableName(this.exprParser.name());
                }
                return stmt;
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.SESSION)) {
            lexer.nextToken();

            if (lexer.identifierEquals(STATUS)) {
                lexer.nextToken();
                MySqlShowStatusStatement stmt = parseShowStatus();
                stmt.setSession(true);
                return stmt;
            }

            if (lexer.identifierEquals(VARIABLES)) {
                lexer.nextToken();
                SQLShowVariantsStatement stmt = parseShowVariants();
                stmt.setSession(true);
                return stmt;
            }

            SQLShowSessionStatement stmt = new SQLShowSessionStatement();
            if (lexer.token() == Token.LIKE) {
                lexer.nextToken();
                SQLExpr like = this.exprParser.expr();
                stmt.setLike(like);
            }
            return stmt;
        }

        if (lexer.identifierEquals("COBAR_STATUS")) {
            lexer.nextToken();
            return new CobarShowStatus();
        }

        if (lexer.identifierEquals("AUTHORS")) {
            lexer.nextToken();
            return new MySqlShowAuthorsStatement();
        }

        if (lexer.token() == Token.BINARY) {
            lexer.nextToken();
            acceptIdentifier("LOGS");
            return new MySqlShowBinaryLogsStatement();
        }

        if (lexer.identifierEquals("MASTER")) {
            lexer.nextToken();
            if (lexer.identifierEquals("LOGS")) {
                lexer.nextToken();
                return new MySqlShowMasterLogsStatement();
            }
            acceptIdentifier(STATUS);
            return new MySqlShowMasterStatusStatement();
        }

        if (lexer.identifierEquals("CLUSTER")) {
            lexer.nextToken();
            acceptIdentifier("NAME");
            return new MySqlShowClusterNameStatement();
        }

        if (lexer.identifierEquals("SYNC_JOB")) {
            lexer.nextToken();
            acceptIdentifier(STATUS);
            MySqlShowJobStatusStatement stmt = new MySqlShowJobStatusStatement();
            stmt.setSync(true);
            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }
            return stmt;
        }
        if (lexer.identifierEquals("JOB")) {
            lexer.nextToken();
            acceptIdentifier(STATUS);

            MySqlShowJobStatusStatement stmt = new MySqlShowJobStatusStatement();
            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }
            return stmt;
        }
        if (lexer.identifierEquals("MIGRATE")) {
            lexer.nextToken();

            acceptIdentifier("TASK");
            acceptIdentifier(STATUS);

            MySqlShowMigrateTaskStatusStatement stmt = new MySqlShowMigrateTaskStatusStatement();
            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }
            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.CHARSET)) {
            lexer.nextToken();

            MySqlShowCharacterSetStatement stmt = new MySqlShowCharacterSetStatement();

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                stmt.setPattern(this.exprParser.expr());
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
            lexer.nextToken();
            accept(Token.SET);
            MySqlShowCharacterSetStatement stmt = new MySqlShowCharacterSetStatement();

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                stmt.setPattern(this.exprParser.expr());
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }

            return stmt;
        }

        if (lexer.identifierEquals("COLLATION")) {
            lexer.nextToken();
            MySqlShowCollationStatement stmt = new MySqlShowCollationStatement();

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                stmt.setPattern(this.exprParser.expr());
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }

            return stmt;
        }

        if (lexer.identifierEquals(BINLOG)) {
            lexer.nextToken();
            acceptIdentifier(EVENTS);
            MySqlShowBinLogEventsStatement stmt = new MySqlShowBinLogEventsStatement();

            if (lexer.token() == Token.IN) {
                lexer.nextToken();
                stmt.setIn(this.exprParser.expr());
            }

            if (lexer.token() == Token.FROM) {
                lexer.nextToken();
                stmt.setFrom(this.exprParser.expr());
            }

            stmt.setLimit(this.exprParser.parseLimit());

            return stmt;
        }

        if (lexer.identifierEquals("CONTRIBUTORS")) {
            lexer.nextToken();
            return new MySqlShowContributorsStatement();
        }

        if (lexer.token() == Token.ALL) {
            lexer.nextToken();
            accept(Token.CREATE);
            accept(TABLE);

            SQLShowCreateTableStatement stmt = new SQLShowCreateTableStatement();
            stmt.setAll(true);
            stmt.setName(this.exprParser.name());
            return stmt;
        }

        if (lexer.token() == Token.CREATE) {
            lexer.nextToken();

            if (lexer.token() == Token.DATABASE || lexer.token() == Token.SCHEMA) {
                lexer.nextToken();

                MySqlShowCreateDatabaseStatement stmt = new MySqlShowCreateDatabaseStatement();
                if (lexer.token() == Token.IF) {
                    lexer.nextToken();
                    accept(Token.NOT);
                    accept(Token.EXISTS);
                    stmt.setIfNotExists(true);
                }
                stmt.setDatabase(this.exprParser.name());
                return stmt;
            }

            if (lexer.identifierEquals("EVENT")) {
                lexer.nextToken();

                MySqlShowCreateEventStatement stmt = new MySqlShowCreateEventStatement();
                stmt.setEventName(this.exprParser.name());
                return stmt;
            }

            if (lexer.token() == Token.FUNCTION) {
                lexer.nextToken();

                MySqlShowCreateFunctionStatement stmt = new MySqlShowCreateFunctionStatement();
                stmt.setName(this.exprParser.name());
                return stmt;
            }

            if (lexer.token() == Token.PROCEDURE) {
                lexer.nextToken();

                MySqlShowCreateProcedureStatement stmt = new MySqlShowCreateProcedureStatement();
                stmt.setName(this.exprParser.name());
                return stmt;
            }
            if (lexer.token() == TABLE) {
                lexer.nextToken();
                SQLShowCreateTableStatement stmt = new SQLShowCreateTableStatement();

                if (lexer.token() != Token.LIKE) {
                    stmt.setName(this.exprParser.name());
                }

                if (lexer.token() == Token.LIKE) {
                    lexer.nextToken();
                    if (lexer.identifierEquals(FnvHash.Constants.MAPPING)) {
                        lexer.nextToken();
                        accept(Token.LPAREN);
                        SQLName name = this.exprParser.name();
                        stmt.setLikeMapping(name);
                        accept(Token.RPAREN);
                    }
                }

                return stmt;
            }

            if (lexer.token() == Token.VIEW) {
                lexer.nextToken();

                SQLShowCreateViewStatement stmt = new SQLShowCreateViewStatement();
                stmt.setName(this.exprParser.name());
                return stmt;
            }

            if (lexer.token() == Token.TRIGGER) {
                lexer.nextToken();

                MySqlShowCreateTriggerStatement stmt = new MySqlShowCreateTriggerStatement();
                stmt.setName(this.exprParser.name());
                return stmt;
            }

            if (lexer.token() == Token.FULLTEXT) {
                lexer.nextToken();
                MysqlShowCreateFullTextStatement stmt = new MysqlShowCreateFullTextStatement();

                stmt.setType(parseFullTextType());

                stmt.setName(this.exprParser.name());
                return stmt;
            }

            if (lexer.identifierEquals("MATERIALIZED")) {
                lexer.nextToken();
                SQLShowCreateMaterializedViewStatement stmt = new SQLShowCreateMaterializedViewStatement();
                accept(VIEW);

                stmt.setName(this.exprParser.name());
                return stmt;
            }

            throw new ParserException("TODO " + lexer.info());
        }

        if (lexer.identifierEquals(ENGINE)) {
            lexer.nextToken();
            MySqlShowEngineStatement stmt = new MySqlShowEngineStatement();
            stmt.setName(this.exprParser.name());
            stmt.setOption(MySqlShowEngineStatement.Option.valueOf(lexer.stringVal().toUpperCase()));
            lexer.nextToken();
            return stmt;
        }

        if (lexer.token() == Token.DATABASE || lexer.identifierEquals(FnvHash.Constants.DB)) {
            lexer.nextToken();

            MySqlShowDatabaseStatusStatement stmt = new MySqlShowDatabaseStatusStatement();

            if (full) {
                stmt.setFull(true);
            }

            if (lexer.identifierEquals("STATUS")) {
                lexer.nextToken();

                if (lexer.token() == Token.LIKE) {
                    lexer.nextTokenValue();
                    stmt.setName(this.exprParser.name());
                } else {
                    if (lexer.token() == Token.WHERE) {
                        lexer.nextToken();
                        SQLExpr where = exprParser.expr();
                        stmt.setWhere(where);
                    }

                    if (lexer.token() == Token.ORDER) {
                        SQLOrderBy orderBy = exprParser.parseOrderBy();
                        stmt.setOrderBy(orderBy);
                    }

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

        if (lexer.identifierEquals("STORAGE")) {
            lexer.nextToken();
            acceptIdentifier(ENGINES);
            MySqlShowEnginesStatement stmt = new MySqlShowEnginesStatement();
            stmt.setStorage(true);
            return stmt;
        }

        if (lexer.identifierEquals(ENGINES)) {
            lexer.nextToken();
            MySqlShowEnginesStatement stmt = new MySqlShowEnginesStatement();
            return stmt;
        }

        if (lexer.identifierEquals(EVENTS)) {
            lexer.nextToken();
            MySqlShowEventsStatement stmt = new MySqlShowEventsStatement();

            if (lexer.token() == Token.FROM || lexer.token() == Token.IN) {
                lexer.nextToken();
                stmt.setSchema(this.exprParser.name());
            }

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                stmt.setLike(this.exprParser.expr());
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }
            return stmt;
        }

        if (lexer.token() == Token.FUNCTION) {
            lexer.nextToken();

            if (lexer.identifierEquals("CODE")) {
                lexer.nextToken();
                MySqlShowFunctionCodeStatement stmt = new MySqlShowFunctionCodeStatement();
                stmt.setName(this.exprParser.name());
                return stmt;
            }

            acceptIdentifier(STATUS);
            MySqlShowFunctionStatusStatement stmt = new MySqlShowFunctionStatusStatement();

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                stmt.setLike(this.exprParser.expr());
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }
            return stmt;
        }

        // MySqlShowFunctionStatusStatement

        if (lexer.identifierEquals(ENGINE)) {
            lexer.nextToken();
            MySqlShowEngineStatement stmt = new MySqlShowEngineStatement();
            stmt.setName(this.exprParser.name());
            stmt.setOption(MySqlShowEngineStatement.Option.valueOf(lexer.stringVal().toUpperCase()));
            lexer.nextToken();
            return stmt;
        }

        if (lexer.identifierEquals("STORAGE")) {
            lexer.nextToken();
            accept(Token.EQ);
            accept(Token.DEFAULT);
            MySqlShowEnginesStatement stmt = new MySqlShowEnginesStatement();
            stmt.setStorage(true);
            return stmt;
        }

//        if (lexer.identifierEquals("KEYS") || lexer.token() == Token.INDEX) {
//            lexer.nextToken();
//            accept(Token.FROM);
//            MySqlShowKeysStatement stmt = new MySqlShowKeysStatement();
//            stmt.setTable(exprParser.name());
//            return stmt;
//        }

        if (lexer.identifierEquals("GRANTS")) {
            lexer.nextToken();
            MySqlShowGrantsStatement stmt = new MySqlShowGrantsStatement();

            if (lexer.token() == Token.FOR) {
                lexer.nextToken();
                stmt.setUser(this.exprParser.expr());
            }

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

            return stmt;
        }

        if (lexer.token() == INDEX || lexer.identifierEquals("INDEXES") || lexer.identifierEquals("KEYS")) {
            SQLShowIndexesStatement stmt = new SQLShowIndexesStatement();

            stmt.setType(lexer.stringVal());
            lexer.nextToken();

            if (lexer.token() == Token.FROM || lexer.token() == Token.IN) {
                lexer.nextToken();
                SQLName table = exprParser.name();
                stmt.setTable(table);

                if (lexer.token() == Token.FROM || lexer.token() == Token.IN) {
                    lexer.nextToken();
                    SQLName database = exprParser.name();
                    stmt.setDatabase(database.toString());
                }

                if (lexer.token() == Token.WHERE) {
                    lexer.nextToken();
                    SQLExpr where = exprParser.expr();
                    stmt.setWhere(where);
                }
            }

            if (lexer.token() == Token.HINT) {
                stmt.setHints(this.exprParser.parseHints());
            }

            return stmt;
        }

        if (lexer.token() == Token.OPEN || lexer.identifierEquals("OPEN")) {
            lexer.nextToken();
            acceptIdentifier(TABLES);
            MySqlShowOpenTablesStatement stmt = new MySqlShowOpenTablesStatement();

            if (lexer.token() == Token.FROM || lexer.token() == Token.IN) {
                lexer.nextToken();
                stmt.setDatabase(this.exprParser.name());
            }

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                stmt.setLike(this.exprParser.expr());
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }
            return stmt;
        }

        if (lexer.identifierEquals("PLUGINS")) {
            lexer.nextToken();
            MySqlShowPluginsStatement stmt = new MySqlShowPluginsStatement();
            return stmt;
        }

        if (lexer.identifierEquals("HTC")) {
            lexer.nextToken();
            MysqlShowHtcStatement stmt = new MysqlShowHtcStatement();
            stmt.setFull(false);
            return stmt;
        }

        if (lexer.identifierEquals("HMSMETA")) {
            lexer.nextToken();
            SQLName name = this.exprParser.name();

            MySqlShowHMSMetaStatement stmt = new MySqlShowHMSMetaStatement();
            stmt.setName(name);
            return stmt;
        }

        if (lexer.identifierEquals("STC")) {
            lexer.nextToken();
            MysqlShowStcStatement stmt = new MysqlShowStcStatement();
            if (lexer.identifierEquals("HIS")) {
                lexer.nextToken();
                stmt.setHis(true);
            } else {
                stmt.setHis(false);
            }
            return stmt;
        }

        if (lexer.identifierEquals("PRIVILEGES")) {
            lexer.nextToken();
            MySqlShowPrivilegesStatement stmt = new MySqlShowPrivilegesStatement();
            return stmt;
        }

        if (lexer.token() == Token.PROCEDURE) {
            lexer.nextToken();

            if (lexer.identifierEquals("CODE")) {
                lexer.nextToken();
                MySqlShowProcedureCodeStatement stmt = new MySqlShowProcedureCodeStatement();
                stmt.setName(this.exprParser.name());
                return stmt;
            }

            acceptIdentifier(STATUS);
            MySqlShowProcedureStatusStatement stmt = new MySqlShowProcedureStatusStatement();

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                stmt.setLike(this.exprParser.expr());
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }
            return stmt;
        }

        if (lexer.identifierEquals("PROFILES")) {
            lexer.nextToken();
            MySqlShowProfilesStatement stmt = new MySqlShowProfilesStatement();
            return stmt;
        }

        if (lexer.identifierEquals("PROFILE")) {
            lexer.nextToken();
            MySqlShowProfileStatement stmt = new MySqlShowProfileStatement();

            for (; ; ) {
                if (lexer.token() == Token.ALL) {
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.ALL);
                    lexer.nextToken();
                } else if (lexer.identifierEquals("BLOCK")) {
                    lexer.nextToken();
                    acceptIdentifier("IO");
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.BLOCK_IO);
                } else if (lexer.identifierEquals("CONTEXT")) {
                    lexer.nextToken();
                    acceptIdentifier("SWITCHES");
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.CONTEXT_SWITCHES);
                } else if (lexer.identifierEquals("CPU")) {
                    lexer.nextToken();
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.CPU);
                } else if (lexer.identifierEquals("IPC")) {
                    lexer.nextToken();
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.IPC);
                } else if (lexer.identifierEquals("MEMORY")) {
                    lexer.nextToken();
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.MEMORY);
                } else if (lexer.identifierEquals("PAGE")) {
                    lexer.nextToken();
                    acceptIdentifier("FAULTS");
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.PAGE_FAULTS);
                } else if (lexer.identifierEquals("SOURCE")) {
                    lexer.nextToken();
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.SOURCE);
                } else if (lexer.identifierEquals("SWAPS")) {
                    lexer.nextToken();
                    stmt.getTypes().add(MySqlShowProfileStatement.Type.SWAPS);
                } else {
                    break;
                }

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }

            if (lexer.token() == Token.FOR) {
                lexer.nextToken();
                acceptIdentifier("QUERY");
                stmt.setForQuery(this.exprParser.primary());
            }

            stmt.setLimit(this.exprParser.parseLimit());

            return stmt;
        }

        if (lexer.identifierEquals("RELAYLOG")) {
            lexer.nextToken();
            acceptIdentifier(EVENTS);
            MySqlShowRelayLogEventsStatement stmt = new MySqlShowRelayLogEventsStatement();

            if (lexer.token() == Token.IN) {
                lexer.nextToken();
                stmt.setLogName(this.exprParser.primary());
            }

            if (lexer.token() == Token.FROM) {
                lexer.nextToken();
                stmt.setFrom(this.exprParser.primary());
            }

            stmt.setLimit(this.exprParser.parseLimit());

            return stmt;
        }

        if (lexer.identifierEquals("RELAYLOG")) {
            lexer.nextToken();
            acceptIdentifier(EVENTS);
            MySqlShowRelayLogEventsStatement stmt = new MySqlShowRelayLogEventsStatement();

            if (lexer.token() == Token.IN) {
                lexer.nextToken();
                stmt.setLogName(this.exprParser.primary());
            }

            if (lexer.token() == Token.FROM) {
                lexer.nextToken();
                stmt.setFrom(this.exprParser.primary());
            }

            stmt.setLimit(this.exprParser.parseLimit());

            return stmt;
        }

        if (lexer.identifierEquals("SLAVE")) {
            lexer.nextToken();
            if (lexer.identifierEquals(STATUS)) {
                lexer.nextToken();
                return new MySqlShowSlaveStatusStatement();
            } else {
                acceptIdentifier("HOSTS");
                MySqlShowSlaveHostsStatement stmt = new MySqlShowSlaveHostsStatement();
                return stmt;
            }
        }

        if (lexer.token() == TABLE) {
            lexer.nextToken();
            acceptIdentifier(STATUS);
            MySqlShowTableStatusStatement stmt = new MySqlShowTableStatusStatement();
            if (lexer.token() == Token.FROM || lexer.token() == Token.IN) {
                lexer.nextToken();

                SQLName name = this.exprParser.name();
                if (name instanceof SQLPropertyExpr) {
                    stmt.setDatabase((SQLIdentifierExpr) ((SQLPropertyExpr) name).getOwner());
                    stmt.setTableGroup(new SQLIdentifierExpr(((SQLPropertyExpr) name).getName()));
                } else {
                    stmt.setDatabase(name);
                }
            }

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                stmt.setLike(this.exprParser.expr());
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(this.exprParser.expr());
            }

            return stmt;
        }

        if (lexer.identifierEquals("TRIGGERS")) {
            lexer.nextToken();
            MySqlShowTriggersStatement stmt = new MySqlShowTriggersStatement();

            if (lexer.token() == Token.FROM || lexer.token() == Token.IN) {
                lexer.nextToken();
                SQLName database = exprParser.name();
                stmt.setDatabase(database);
            }

            if (lexer.token() == Token.LIKE) {
                lexer.nextTokenValue();
                SQLExpr like = exprParser.expr();
                stmt.setLike(like);
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.BROADCASTS)) {
            lexer.nextToken();
            MySqlShowBroadcastsStatement stmt = new MySqlShowBroadcastsStatement();

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.DATASOURCES)) {
            lexer.nextToken();
            MySqlShowDatasourcesStatement stmt = new MySqlShowDatasourcesStatement();

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.NODE)) {
            lexer.nextToken();
            MySqlShowNodeStatement stmt = new MySqlShowNodeStatement();

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.HELP)) {
            lexer.nextToken();
            MySqlShowHelpStatement stmt = new MySqlShowHelpStatement();

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            return stmt;
        }

        if (lexer.token() == Token.SEQUENCE || lexer.identifierEquals(FnvHash.Constants.SEQUENCES)) {
            lexer.nextToken();
            MySqlShowSequencesStatement stmt = new MySqlShowSequencesStatement();

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            return stmt;
        }

        if (lexer.identifierEquals("PARTITIONS")) {
            lexer.nextToken();
            SQLShowPartitionsStmt stmt = new SQLShowPartitionsStmt();

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

            SQLName name = exprParser.name();
            stmt.setTableSource(name);

            if (lexer.token() == Token.PARTITION) {
                lexer.nextToken();
                accept(Token.LPAREN);
                parseAssignItems(stmt.getPartition(), stmt, false);
                accept(Token.RPAREN);
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                stmt.setWhere(
                        this.exprParser.expr()
                );
            }

            return stmt;
        }

        if (lexer.identifierEquals("RULE")) {
            lexer.nextToken();

            boolean version = false;
            if (lexer.identifierEquals(FnvHash.Constants.VERSION)) {
                version = true;
                lexer.nextToken();
            } else if (lexer.identifierEquals(FnvHash.Constants.FULL) || lexer.token() == Token.FULL) {
                full = true;
                lexer.nextToken();
            }

            if (lexer.identifierEquals(FnvHash.Constants.STATUS)) {
                lexer.nextToken();
                MySqlShowRuleStatusStatement stmt = new MySqlShowRuleStatusStatement();

                if (full) {
                    stmt.setFull(full);
                }

                if (version) {
                    stmt.setVersion(true);
                }

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

                if (full) {
                    stmt.setLite(false);
                }

                if (lexer.token() == Token.WHERE) {
                    lexer.nextToken();
                    SQLExpr where = exprParser.expr();
                    stmt.setWhere(where);
                }

                if (lexer.token() == Token.ORDER) {
                    SQLOrderBy orderBy = exprParser.parseOrderBy();
                    stmt.setOrderBy(orderBy);
                }

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

                return stmt;
            } else {
                MySqlShowRuleStatement stmt = new MySqlShowRuleStatement();

                if (full) {
                    stmt.setFull(full);
                }

                if (version) {
                    stmt.setVersion(true);
                }

                if (lexer.identifierEquals(FnvHash.Constants.VERSION)) {
                    lexer.nextToken();
                    stmt.setVersion(true);
                }

                if (lexer.token() == Token.EOF || lexer.token() == SEMI) {
                    return stmt;
                }

                if (lexer.token() == Token.FROM) {
                    lexer.nextToken();

                    stmt.setName(exprParser.name());
                }

                if (lexer.token() == Token.WHERE) {
                    lexer.nextToken();
                    SQLExpr where = exprParser.expr();
                    stmt.setWhere(where);
                }

                if (lexer.token() == Token.ORDER) {
                    SQLOrderBy orderBy = exprParser.parseOrderBy();
                    stmt.setOrderBy(orderBy);
                }

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

        }

        if (lexer.identifierEquals("DS")) {
            lexer.nextToken();
            MySqlShowDsStatement stmt = new MySqlShowDsStatement();

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            return stmt;
        }

        if (lexer.identifierEquals("DDL")) {
            lexer.nextToken();

            // DRDS async DDL.
            if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && !lexer.identifierEquals("STATUS")) {
                // SHOW [FULL] DDL.
                DrdsShowDDLJobs showDDLJobs = new DrdsShowDDLJobs();
                showDDLJobs.setFull(full);

                while (lexer.token() != Token.EOF && lexer.token() != SEMI) {
                    showDDLJobs.addJobId(lexer.integerValue().longValue());
                    accept(Token.LITERAL_INT);
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                    } else if (lexer.token() == Token.EOF || lexer.token() == SEMI) {
                        break;
                    } else {
                        throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                    }
                }
                return showDDLJobs;
            }

            acceptIdentifier("STATUS");
            MySqlShowDdlStatusStatement stmt = new MySqlShowDdlStatusStatement();

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            return stmt;
        }

        if (lexer.identifierEquals("TRACE")) {
            lexer.nextToken();
            MySqlShowTraceStatement stmt = new MySqlShowTraceStatement();

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            return stmt;
        }

        if (lexer.identifierEquals("TOPOLOGY")) {
            lexer.nextToken();
            MySqlShowTopologyStatement stmt = new MySqlShowTopologyStatement();

            if (lexer.token() == Token.FROM) {
                lexer.nextToken();
            }
            stmt.setName(exprParser.name());

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = exprParser.expr();
                stmt.setWhere(where);
            }

            if (lexer.token() == Token.ORDER) {
                SQLOrderBy orderBy = exprParser.parseOrderBy();
                stmt.setOrderBy(orderBy);
            }

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

            stmt.setFull(full);

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.PLANCACHE)) {
            lexer.nextToken();

            if (lexer.identifierEquals(FnvHash.Constants.STATUS)) {
                lexer.nextToken();
                return new MySqlShowPlanCacheStatusStatement();
            } else if (lexer.identifierEquals(FnvHash.Constants.PLAN)) {
                lexer.nextToken();
                SQLSelect select = this.createSQLSelectParser().select();
                return new MySqlShowPlanCacheStatement(select);
            } else {
                throw new ParserException("TODO " + lexer.info());
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.SLOW)) {
            MySqlShowSlowStatement stmt = parserShowSlow();
            stmt.setPhysical(false);
            stmt.setFull(full);
            return stmt;
        }

        if (lexer.identifierEquals("PHYSICAL_SLOW")) {
            MySqlShowSlowStatement stmt = parserShowSlow();
            stmt.setPhysical(true);
            stmt.setFull(full);
            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.QUERY_TASK)) {
            lexer.nextToken();
            SQLShowQueryTaskStatement stmt = new SQLShowQueryTaskStatement();
            stmt.setDbType(dbType);

            if (lexer.token() == Token.FOR) {
                lexer.nextToken();
                stmt.setUser(
                        exprParser.expr()
                );
            }

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = this.exprParser.expr();
                stmt.setWhere(where);
            }

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

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

            if (full) {
                stmt.setFull(true);
            }

            return stmt;
        }
        if (lexer.identifierEquals(FnvHash.Constants.OUTLINES)) {
            lexer.nextToken();
            SQLShowOutlinesStatement stmt = new SQLShowOutlinesStatement();
            stmt.setDbType(dbType);

            if (lexer.token() == Token.WHERE) {
                lexer.nextToken();
                SQLExpr where = this.exprParser.expr();
                stmt.setWhere(where);
            }

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

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

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.RECYCLEBIN)) {
            lexer.nextToken();

            SQLShowRecylebinStatement stmt = new SQLShowRecylebinStatement();
            stmt.setDbType(dbType);
            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.TABLEGROUPS)) {
            lexer.nextToken();

            SQLShowTableGroupsStatement stmt = parseShowTableGroups();

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.CATALOGS)) {
            lexer.nextToken();

            SQLShowCatalogsStatement stmt = new SQLShowCatalogsStatement();
            if (lexer.token() == Token.LIKE) {
                lexer.nextToken();
                SQLExpr like = this.exprParser.expr();
                stmt.setLike(like);
            }

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.FUNCTIONS)) {
            lexer.nextToken();

            SQLShowFunctionsStatement stmt = new SQLShowFunctionsStatement();
            if (lexer.token() == Token.LIKE) {
                lexer.nextToken();
                SQLExpr like = this.exprParser.expr();
                stmt.setLike(like);
            }

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.SCHEMAS)) {
            lexer.nextToken();

            SQLShowDatabasesStatement stmt = new SQLShowDatabasesStatement();
            stmt.setPhysical(isPhysical);
            if (lexer.token() == Token.IN || lexer.token() == Token.FROM) {
                lexer.nextToken();
                SQLName db = this.exprParser.name();
                stmt.setDatabase(db);
            }

            if (full) {
                stmt.setFull(true);
            }

            if (lexer.token() == Token.LIKE) {
                lexer.nextToken();
                SQLExpr like = this.exprParser.expr();
                stmt.setLike(like);
            }

            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.CONFIG)) {
            lexer.nextToken();
            SQLName name = this.exprParser.name();
            MySqlShowConfigStatement stmt = new MySqlShowConfigStatement();
            stmt.setName(name);
            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.USERS)) {
            lexer.nextToken();
            SQLShowUsersStatement stmt = new SQLShowUsersStatement();
            stmt.setDbType(dbType);
            return stmt;
        }

        if (lexer.identifierEquals(FnvHash.Constants.PHYSICAL_PROCESSLIST)) {
            lexer.nextToken();

            MySqlShowPhysicalProcesslistStatement stmt = new MySqlShowPhysicalProcesslistStatement();
            if (full) {
                stmt.setFull(full);
            }
            return stmt;
        }

        if (lexer.identifierEquals("MATERIALIZED")) {
            return parseShowMaterializedView();
        }

        if (lexer.token() == Token.FULLTEXT) {
            lexer.nextToken();

            MysqlShowFullTextStatement stmt = new MysqlShowFullTextStatement();

            if (lexer.identifierEquals(FnvHash.Constants.CHARFILTERS)) {
                stmt.setType(FullTextType.CHARFILTER);
            } else if (lexer.identifierEquals(FnvHash.Constants.TOKENIZERS)) {
                stmt.setType(FullTextType.TOKENIZER);
            } else if (lexer.identifierEquals(FnvHash.Constants.TOKENFILTERS)) {
                stmt.setType(FullTextType.TOKENFILTER);
            } else if (lexer.identifierEquals(FnvHash.Constants.ANALYZERS)) {
                stmt.setType(FullTextType.ANALYZER);
            } else if (lexer.identifierEquals(FnvHash.Constants.DICTIONARIES)) {
                stmt.setType(FullTextType.DICTIONARY);
            } else {
                throw new ParserException("type of full text must be [CHARFILTERS/TOKENIZERS/TOKENFILTERS/ANALYZERS/DICTIONARYS] .");
            }

            lexer.nextToken();

            return stmt;
        }

        // DRDS GSI management syntax.
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("METADATA")) {
            lexer.nextToken();

            if (Token.LOCK == lexer.token() || lexer.identifierEquals("LOCKS")) {
                lexer.nextToken();

                DrdsShowMetadataLock stmt = new DrdsShowMetadataLock();
                if (SEMI == lexer.token() || Token.EOF == lexer.token()) {
                    return stmt;
                } else {
                    stmt.setSchemaName(this.exprParser.name());
                    return stmt;
                }
            } else {
                throw new ParserException("syntax error, expect LOCK/LOCKS, actual " + lexer.token() + ", " + lexer.info());
            }
        }

        // MySqlShowSlaveHostsStatement

        throw new ParserException("TODO " + lexer.info());
    }

    private MySqlShowStatusStatement parseShowStatus() {
        MySqlShowStatusStatement stmt = new MySqlShowStatusStatement();

        if (lexer.token() == Token.LIKE) {
            lexer.nextToken();
            SQLExpr like = exprParser.expr();
            stmt.setLike(like);
        }

        if (lexer.token() == Token.WHERE) {
            lexer.nextToken();
            SQLExpr where = exprParser.expr();
            stmt.setWhere(where);
        }

        return stmt;
    }

    public MySqlShowSlowStatement parserShowSlow() {
        lexer.nextToken();
        MySqlShowSlowStatement stmt = new MySqlShowSlowStatement();

        if (lexer.token() == Token.WHERE) {
            lexer.nextToken();
            SQLExpr where = exprParser.expr();
            stmt.setWhere(where);
        }

        if (lexer.token() == Token.ORDER) {
            SQLOrderBy orderBy = exprParser.parseOrderBy();
            stmt.setOrderBy(orderBy);
        }

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

        return stmt;
    }

    private MySqlShowWarningsStatement parseShowWarnings() {
        MySqlShowWarningsStatement stmt = new MySqlShowWarningsStatement();

        stmt.setLimit(this.exprParser.parseLimit());

        return stmt;
    }

    public SQLStartTransactionStatement parseStart() {
        acceptIdentifier("START");
        acceptIdentifier("TRANSACTION");

        SQLStartTransactionStatement stmt = new SQLStartTransactionStatement(dbType);

        if (lexer.token() == WITH) {
            lexer.nextToken();
            acceptIdentifier("CONSISTENT");
            acceptIdentifier("SNAPSHOT");
            stmt.setConsistentSnapshot(true);
        }

        if (lexer.token() == Token.BEGIN) {
            lexer.nextToken();
            stmt.setBegin(true);
            if (lexer.identifierEquals("WORK")) {
                lexer.nextToken();
                stmt.setWork(true);
            }
        }

        if (lexer.token() == Token.HINT) {
            stmt.setHints(this.exprParser.parseHints());
        }

        if (lexer.identifierEquals(FnvHash.Constants.ISOLATION)) {
            lexer.nextToken();
            acceptIdentifier("LEVEL");

            if (lexer.identifierEquals(FnvHash.Constants.READ)) {
                lexer.nextToken();
                if (lexer.identifierEquals(FnvHash.Constants.UNCOMMITTED)) {
                    lexer.nextToken();
                    stmt.setIsolationLevel(SQLStartTransactionStatement.IsolationLevel.READ_UNCOMMITTED);
                } else if (lexer.identifierEquals(FnvHash.Constants.COMMITTED)) {
                    lexer.nextToken();
                    stmt.setIsolationLevel(SQLStartTransactionStatement.IsolationLevel.READ_COMMITTED);
                } else {
                    throw new ParserException(lexer.info());
                }
            } else if (lexer.identifierEquals(FnvHash.Constants.REPEATABLE)) {
                lexer.nextToken();
                acceptIdentifier("READ");
                stmt.setIsolationLevel(SQLStartTransactionStatement.IsolationLevel.REPEATABLE_READ);
            } else if (lexer.identifierEquals(FnvHash.Constants.SERIALIZABLE)) {
                lexer.nextToken();
                stmt.setIsolationLevel(SQLStartTransactionStatement.IsolationLevel.SERIALIZABLE);
            } else {
                throw new ParserException(lexer.info());
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.READ)) {
            lexer.nextToken();
            acceptIdentifier("ONLY");
            stmt.setReadOnly(true);
        }

        return stmt;
    }

    @Override
    public SQLStatement parseRollback() {
        acceptIdentifier("ROLLBACK");

        // DRDS async DDL.
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("DDL")) {
            // ROLLBACK DDL <job_id> [, <job_id>] ...
            lexer.nextToken();
            DrdsRollbackDDLJob stmt = new DrdsRollbackDDLJob();
            while (true) {
                stmt.addJobId(lexer.integerValue().longValue());
                accept(Token.LITERAL_INT);
                if (Token.COMMA == lexer.token()) {
                    lexer.nextToken();
                } else if (lexer.token() == Token.EOF || lexer.token() == SEMI) {
                    break;
                } else {
                    throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                }
            }
            return stmt;
        }

        SQLRollbackStatement stmt = new SQLRollbackStatement();

        if (lexer.identifierEquals("WORK")) {
            lexer.nextToken();
        }

        if (lexer.token() == Token.AND) {
            lexer.nextToken();
            if (lexer.token() == Token.NOT) {
                lexer.nextToken();
                acceptIdentifier(CHAIN);
                stmt.setChain(Boolean.FALSE);
            } else {
                acceptIdentifier(CHAIN);
                stmt.setChain(Boolean.TRUE);
            }
        }

        if (lexer.token() == Token.TO) {
            lexer.nextToken();

            if (lexer.identifierEquals("SAVEPOINT")) {
                lexer.nextToken();
            }

            stmt.setTo(this.exprParser.name());
        }

        return stmt;
    }

    public SQLStatement parseCommit() {
        acceptIdentifier("COMMIT");

        SQLCommitStatement stmt = new SQLCommitStatement();

        if (lexer.identifierEquals("WORK")) {
            lexer.nextToken();
            stmt.setWork(true);
        }

        if (lexer.token() == Token.AND) {
            lexer.nextToken();
            if (lexer.token() == Token.NOT) {
                lexer.nextToken();
                acceptIdentifier(CHAIN);
                stmt.setChain(Boolean.FALSE);
            } else {
                acceptIdentifier(CHAIN);
                stmt.setChain(Boolean.TRUE);
            }
        }

        return stmt;
    }

    public SQLReplaceStatement parseReplace() {
        SQLReplaceStatement stmt = new SQLReplaceStatement();
        stmt.setDbType(DbType.mysql);

        List<SQLCommentHint> list = new ArrayList<SQLCommentHint>();

        while (lexer.token() == Token.HINT) {
            this.exprParser.parseHints(list);
        }
        stmt.setHeadHints(list);

        accept(Token.REPLACE);
        while (lexer.token() == Token.HINT) {
            this.exprParser.parseHints(stmt.getHints());
        }
        if (lexer.token() == Token.COMMENT) {
            lexer.nextToken();
        }

        if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
            stmt.setLowPriority(true);
            lexer.nextToken();
        }

        if (lexer.identifierEquals(FnvHash.Constants.DELAYED)) {
            stmt.setDelayed(true);
            lexer.nextToken();
        }

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

        SQLName tableName = exprParser.name();
        stmt.setTableName(tableName);

        if (lexer.token() == Token.PARTITION) {
            lexer.nextToken();
            accept(Token.LPAREN);
            for (; ; ) {
                SQLAssignItem ptExpr = new SQLAssignItem();
                ptExpr.setTarget(this.exprParser.name());
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                    SQLExpr ptValue = this.exprParser.expr();
                    ptExpr.setValue(ptValue);
                }
                stmt.addPartition(ptExpr);
                if (lexer.token() != Token.COMMA) {
                    break;
                } else {
                    lexer.nextToken();
                }
            }
            accept(Token.RPAREN);
        }

        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            if (lexer.token() == Token.SELECT) {
                SQLQueryExpr queryExpr = (SQLQueryExpr) this.exprParser.expr();
                stmt.setQuery(queryExpr);
            } else {
                this.exprParser.exprList(stmt.getColumns(), stmt);
            }
            accept(Token.RPAREN);
        }

        if (lexer.token() == Token.VALUES || lexer.identifierEquals("VALUE")) {
            lexer.nextToken();

            parseValueClause(stmt.getValuesList(), null, 0, stmt);
        } else if (lexer.token() == Token.SELECT) {
            SQLQueryExpr queryExpr = (SQLQueryExpr) this.exprParser.expr();
            stmt.setQuery(queryExpr);
        } else if (lexer.token() == Token.SET) {
            lexer.nextToken();

            SQLInsertStatement.ValuesClause values = new SQLInsertStatement.ValuesClause();
            values.setParent(stmt);
            stmt.getValuesList().add(values);
            for (; ; ) {
                stmt.addColumn(this.exprParser.name());
                if (lexer.token() == Token.COLONEQ) {
                    lexer.nextToken();
                } else {
                    accept(Token.EQ);
                }
                values.addValue(this.exprParser.expr());

                if (lexer.token() == (Token.COMMA)) {
                    lexer.nextToken();
                    continue;
                }

                break;
            }
        } else if (lexer.token() == Token.LPAREN) {
            SQLSelect select = this.createSQLSelectParser().select();
            SQLQueryExpr queryExpr = new SQLQueryExpr(select);
            stmt.setQuery(queryExpr);
        }

        return stmt;
    }

    protected SQLStatement parseLoad() {
        acceptIdentifier("LOAD");

        if (lexer.identifierEquals("DATA")) {
            SQLStatement stmt = parseLoadDataInFile();
            return stmt;
        }

        if (lexer.identifierEquals("XML")) {
            SQLStatement stmt = parseLoadXml();
            return stmt;
        }

        throw new ParserException("TODO. " + lexer.info());
    }

    protected MySqlLoadXmlStatement parseLoadXml() {
        acceptIdentifier("XML");

        MySqlLoadXmlStatement stmt = new MySqlLoadXmlStatement();

        if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
            stmt.setLowPriority(true);
            lexer.nextToken();
        }

        if (lexer.identifierEquals("CONCURRENT")) {
            stmt.setConcurrent(true);
            lexer.nextToken();
        }

        if (lexer.identifierEquals(LOCAL)) {
            stmt.setLocal(true);
            lexer.nextToken();
        }

        acceptIdentifier("INFILE");

        SQLLiteralExpr fileName = (SQLLiteralExpr) exprParser.expr();
        stmt.setFileName(fileName);

        if (lexer.token() == Token.REPLACE) {
            stmt.setReplicate(true);
            lexer.nextToken();
        }

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

        accept(Token.INTO);
        accept(TABLE);

        SQLName tableName = exprParser.name();
        stmt.setTableName(tableName);

        if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
            lexer.nextToken();
            accept(Token.SET);

            if (lexer.token() != Token.LITERAL_CHARS) {
                throw new ParserException("syntax error, illegal charset. " + lexer.info());
            }

            String charset = lexer.stringVal();
            lexer.nextToken();
            stmt.setCharset(charset);
        }

        if (lexer.identifierEquals("ROWS")) {
            lexer.nextToken();
            acceptIdentifier("IDENTIFIED");
            accept(Token.BY);
            SQLExpr rowsIdentifiedBy = exprParser.expr();
            stmt.setRowsIdentifiedBy(rowsIdentifiedBy);
        }

        if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
            throw new ParserException("TODO. " + lexer.info());
        }

        if (lexer.token() == Token.SET) {
            throw new ParserException("TODO. " + lexer.info());
        }

        return stmt;
    }

    protected MySqlLoadDataInFileStatement parseLoadDataInFile() {
        acceptIdentifier("DATA");

        MySqlLoadDataInFileStatement stmt = new MySqlLoadDataInFileStatement();

        if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
            stmt.setLowPriority(true);
            lexer.nextToken();
        }

        if (lexer.identifierEquals("CONCURRENT")) {
            stmt.setConcurrent(true);
            lexer.nextToken();
        }

        if (lexer.identifierEquals(LOCAL)) {
            stmt.setLocal(true);
            lexer.nextToken();
        }

        acceptIdentifier("INFILE");

        SQLLiteralExpr fileName = (SQLLiteralExpr) exprParser.expr();
        stmt.setFileName(fileName);

        if (lexer.token() == Token.REPLACE) {
            stmt.setReplicate(true);
            lexer.nextToken();
        }

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

        accept(Token.INTO);
        accept(TABLE);

        SQLName tableName = exprParser.name();
        stmt.setTableName(tableName);

        if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
            lexer.nextToken();
            accept(Token.SET);

            if (lexer.token() != Token.LITERAL_CHARS) {
                throw new ParserException("syntax error, illegal charset. " + lexer.info());
            }

            String charset = lexer.stringVal();
            lexer.nextToken();
            stmt.setCharset(charset);
        }

        if (lexer.identifierEquals("FIELDS") || lexer.identifierEquals("COLUMNS")) {
            lexer.nextToken();
            if (lexer.identifierEquals("TERMINATED")) {
                lexer.nextToken();
                accept(Token.BY);

                if (lexer.token() == Token.LITERAL_CHARS) {
                    stmt.setColumnsTerminatedBy(new SQLCharExpr(lexer.stringVal()));
                    lexer.nextToken();
                } else {
                    SQLExpr primary = this.exprParser.primary();
                    if (primary instanceof SQLHexExpr) {
                        stmt.setColumnsTerminatedBy((SQLHexExpr) primary);
                    } else {
                        throw new ParserException("invalid expr for columns terminated : " + primary);
                    }
                }
            }

            if (lexer.identifierEquals("OPTIONALLY")) {
                stmt.setColumnsEnclosedOptionally(true);
                lexer.nextToken();
            }

            if (lexer.identifierEquals("ENCLOSED")) {
                lexer.nextToken();
                accept(Token.BY);
                stmt.setColumnsEnclosedBy(new SQLCharExpr(lexer.stringVal()));
                lexer.nextToken();
            }

            if (lexer.identifierEquals("ESCAPED")) {
                lexer.nextToken();
                accept(Token.BY);
                stmt.setColumnsEscaped(new SQLCharExpr(lexer.stringVal()));
                lexer.nextToken();
            }
        }

        if (lexer.identifierEquals("LINES")) {
            lexer.nextToken();
            if (lexer.identifierEquals("STARTING")) {
                lexer.nextToken();
                accept(Token.BY);

                if (lexer.token() == Token.LITERAL_CHARS) {
                    stmt.setLinesStartingBy(new SQLCharExpr(lexer.stringVal()));
                    lexer.nextToken();
                } else {
                    SQLExpr primary = this.exprParser.primary();
                    if (primary instanceof SQLHexExpr) {
                        stmt.setLinesStartingBy((SQLHexExpr) primary);
                    } else {
                        throw new ParserException("invalid expr for lines starting : " + primary);
                    }
                }
            }

            if (lexer.identifierEquals("TERMINATED")) {
                lexer.nextToken();
                accept(Token.BY);
                if (lexer.token() == Token.LITERAL_CHARS) {
                    stmt.setLinesTerminatedBy(new SQLCharExpr(lexer.stringVal()));
                    lexer.nextToken();
                } else {
                    SQLExpr primary = this.exprParser.primary();
                    if (primary instanceof SQLHexExpr) {
                        stmt.setLinesTerminatedBy((SQLHexExpr) primary);
                    } else {
                        throw new ParserException("invalid expr for lines terminated : " + primary);
                    }
                }
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
            lexer.nextToken();
            stmt.setIgnoreLinesNumber(this.exprParser.expr());
            acceptIdentifier("LINES");
        }

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

        if (lexer.token() == Token.SET) {
            lexer.nextToken();
            this.exprParser.exprList(stmt.getSetList(), stmt);
        }

        return stmt;

    }

    public MySqlPrepareStatement parsePrepare() {
        acceptIdentifier("PREPARE");

        SQLName name = exprParser.name();
        accept(Token.FROM);
        SQLExpr from = exprParser.expr();

        return new MySqlPrepareStatement(name, from);
    }

    public MySqlExecuteStatement parseExecute() {
        MySqlExecuteStatement stmt = new MySqlExecuteStatement();

        SQLName statementName = exprParser.name();
        stmt.setStatementName(statementName);

        if (lexer.identifierEquals("USING")) {
            lexer.nextToken();
            exprParser.exprList(stmt.getParameters(), stmt);
        } else if (lexer.token() == Token.IDENTIFIER) {
            exprParser.exprList(stmt.getParameters(), stmt);
        }
        return stmt;
    }

    public MySqlExecuteForAdsStatement parseExecuteForAds() {
        MySqlExecuteForAdsStatement stmt = new MySqlExecuteForAdsStatement();
        stmt.setAction(exprParser.name());
        stmt.setRole(exprParser.name());

        stmt.setTargetId(exprParser.charExpr());

        if (lexer.token() == Token.IDENTIFIER) {
            stmt.setStatus(exprParser.name());
        }
        return stmt;
    }

    public MysqlDeallocatePrepareStatement parseDeallocatePrepare() {
        acceptIdentifier("DEALLOCATE");
        acceptIdentifier("PREPARE");

        MysqlDeallocatePrepareStatement stmt = new MysqlDeallocatePrepareStatement();
        SQLName statementName = exprParser.name();
        stmt.setStatementName(statementName);

        return stmt;
    }

    public SQLInsertStatement parseInsert() {
        MySqlInsertStatement stmt = new MySqlInsertStatement();

        SQLName tableName = null;
        if (lexer.token() == Token.INSERT) {
            lexer.nextToken();

            for (; ; ) {
                if (lexer.token() == Token.IDENTIFIER) {
                    long hash = lexer.hashLCase();

                    if (hash == FnvHash.Constants.LOW_PRIORITY) {
                        stmt.setLowPriority(true);
                        lexer.nextToken();
                        continue;
                    }

                    if (hash == FnvHash.Constants.DELAYED) {
                        stmt.setDelayed(true);
                        lexer.nextToken();
                        continue;
                    }

                    if (hash == FnvHash.Constants.HIGH_PRIORITY) {
                        stmt.setHighPriority(true);
                        lexer.nextToken();
                        continue;
                    }

                    if (hash == FnvHash.Constants.IGNORE) {
                        stmt.setIgnore(true);
                        lexer.nextToken();
                        continue;
                    }

                    if (hash == FnvHash.Constants.ROLLBACK_ON_FAIL) {
                        stmt.setRollbackOnFail(true);
                        lexer.nextToken();
                        continue;
                    }
                }

                break;
            }

            if (lexer.token() == Token.HINT) {
                List<SQLCommentHint> hints = this.exprParser.parseHints();
                stmt.setHints(hints);
            }

            if (lexer.token() == Token.INTO) {
                lexer.nextToken();
                if (lexer.token() == TABLE) {
                    lexer.nextToken();
                }
            } else if (lexer.identifierEquals(FnvHash.Constants.OVERWRITE)) {
                lexer.nextToken();
                stmt.setOverwrite(true);
                if (lexer.token() == TABLE) {
                    lexer.nextToken();
                } else if (lexer.token() == Token.INTO) {
                    lexer.nextToken();
                }
            }

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

            if (lexer.token() == Token.FULLTEXT) {
                lexer.nextToken();
                if (lexer.identifierEquals(FnvHash.Constants.DICTIONARY)) {
                    lexer.nextToken();
                    stmt.setFulltextDictionary(true);
                }
            }

            tableName = this.exprParser.name();
            stmt.setTableName(tableName);

            if (lexer.token() == Token.HINT) {
                String comment = "/*" + lexer.stringVal() + "*/";
                lexer.nextToken();
                stmt.getTableSource().addAfterComment(comment);
            }

            if (lexer.token() == Token.IDENTIFIER
                    && !lexer.identifierEquals(FnvHash.Constants.VALUE)) {
                stmt.setAlias(lexer.stringVal());
                lexer.nextToken();
            }

            if (lexer.token() == WITH) {
                SQLSelectStatement withStmt = (SQLSelectStatement) parseWith();
                stmt.setQuery(withStmt.getSelect());
            }

            if (lexer.token() == Token.PARTITION) {
                lexer.nextToken();
                accept(Token.LPAREN);
                for (; ; ) {
                    SQLAssignItem ptExpr = new SQLAssignItem();
                    ptExpr.setTarget(this.exprParser.name());
                    if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                        SQLExpr ptValue = this.exprParser.expr();
                        ptExpr.setValue(ptValue);
                    }
                    stmt.addPartition(ptExpr);
                    if (lexer.token() != Token.COMMA) {
                        break;
                    } else {
                        lexer.nextToken();
                    }
                }
                accept(Token.RPAREN);

                if (lexer.token() == Token.IF) {
                    lexer.nextToken();
                    accept(Token.NOT);
                    accept(Token.EXISTS);

                    stmt.setIfNotExists(true);
                }
            }
        }

        int columnSize = 0;
        List<SQLColumnDefinition> columnDefinitionList = null;

        if (lexer.token() == Token.LPAREN) {
            boolean useInsertColumnsCache = lexer.isEnabled(SQLParserFeature.UseInsertColumnsCache);
            InsertColumnsCache insertColumnsCache = null;

            long tableNameHash = 0;
            InsertColumnsCache.Entry cachedColumns = null;
            if (useInsertColumnsCache) {
                insertColumnsCache = this.insertColumnsCache;
                if (insertColumnsCache == null) {
                    insertColumnsCache = InsertColumnsCache.global;
                }

                if (tableName != null) {
                    tableNameHash = tableName.nameHashCode64();
                    cachedColumns = insertColumnsCache.get(tableNameHash);
                }
            }

            SchemaObject tableObject = null;
            int pos = lexer.pos();
            if (cachedColumns != null
                    && lexer.text.startsWith(cachedColumns.columnsString, pos)) {
                if (!lexer.isEnabled(SQLParserFeature.OptimizedForParameterized)) {
                    List<SQLExpr> columns = stmt.getColumns();
                    List<SQLExpr> cachedColumns2 = cachedColumns.columns;
                    for (int i = 0, size = cachedColumns2.size(); i < size; i++) {
                        columns.add(cachedColumns2.get(i).clone());
                    }
                }
                stmt.setColumnsString(cachedColumns.columnsFormattedString, cachedColumns.columnsFormattedStringHash);
                int p2 = pos + cachedColumns.columnsString.length();
                lexer.reset(p2);
                lexer.nextToken();
            } else {
                Lexer.SavePoint mark = lexer.mark();
                lexer.nextToken();
                if (lexer.token() == Token.SELECT) {
                    lexer.reset(mark);
                    SQLSelect select = this.exprParser.createSelectParser().select();
                    select.setParent(stmt);
                    stmt.setQuery(select);
                } else {
                    if (repository != null && lexer.isEnabled(SQLParserFeature.InsertValueCheckType)) {
                        tableObject = repository.findTable(tableName.nameHashCode64());
                    }

                    if (tableObject != null) {
                        columnDefinitionList = new ArrayList<SQLColumnDefinition>();
                    }

                    List<SQLExpr> columns = stmt.getColumns();

                    if (lexer.token() != Token.RPAREN) {
                        for (; ; ) {
                            String identName;
                            long hash;

                            Token token = lexer.token();
                            if (token == Token.IDENTIFIER) {
                                identName = lexer.stringVal();
                                hash = lexer.hashLCase();
                            } else if (token == Token.LITERAL_CHARS) {
                                if (lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                                    identName = lexer.stringVal();
                                } else {
                                    identName = '\'' + lexer.stringVal() + '\'';
                                }
                                hash = 0;
                            } else if (token == Token.LITERAL_ALIAS) {
                                identName = lexer.stringVal();
                                if (lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                                    identName = SQLUtils.normalize(identName, dbType);
                                }
                                hash = 0;
                            } else {
                                identName = lexer.stringVal();
                                hash = 0;
                            }
                            SQLExpr expr = new SQLIdentifierExpr(identName, hash);
                            if (lexer.hasComment()) {
                                expr.addBeforeComment(lexer.readAndResetComments());
                            }
                            lexer.nextTokenComma();
                            while (lexer.token() == Token.DOT) {
                                lexer.nextToken();
                                String propertyName = lexer.stringVal();
                                lexer.nextToken();
                                expr = new SQLPropertyExpr(expr, propertyName);
                            }
                            if (lexer.hasComment()) {
                                expr.addAfterComment(lexer.readAndResetComments());
                            }
                            expr.setParent(stmt);
                            columns.add(expr);
                            columnSize++;

                            if (tableObject != null) {
                                SQLColumnDefinition columnDefinition = tableObject.findColumn(hash);
                                columnDefinitionList.add(columnDefinition);
                            }

                            if (lexer.token() == Token.COMMA) {
                                lexer.nextTokenIdent();
                                continue;
                            }

                            break;
                        }
                        columnSize = stmt.getColumns().size();

                        if (insertColumnsCache != null && tableName != null) {
                            String columnsString = lexer.subString(pos, lexer.pos() - pos);

                            List<SQLExpr> clonedColumns = new ArrayList<SQLExpr>(columnSize);
                            for (int i = 0; i < columns.size(); i++) {
                                clonedColumns.add(columns.get(i).clone());
                            }

                            StringBuilder buf = new StringBuilder();
                            SQLASTOutputVisitor outputVisitor = SQLUtils.createOutputVisitor(buf, dbType);
                            outputVisitor.printInsertColumns(columns);

                            String formattedColumnsString = buf.toString();
                            long columnsFormattedStringHash = FnvHash.fnv1a_64_lower(formattedColumnsString);

                            insertColumnsCache.put(tableName.hashCode64(), columnsString, formattedColumnsString, clonedColumns);
                            stmt.setColumnsString(formattedColumnsString, columnsFormattedStringHash);
                        }
                    }
                    accept(Token.RPAREN);
                }
            }
        }

        List<SQLCommentHint> commentHints = null;
        if (lexer.token() == Token.HINT) {
            commentHints = this.exprParser.parseHints();
        } else if (lexer.token() == Token.LINE_COMMENT) {
            lexer.nextToken();
        }

        if (lexer.token() == Token.VALUES || lexer.identifierEquals(FnvHash.Constants.VALUE)) {
            lexer.nextTokenLParen();
            if (lexer.isEnabled(SQLParserFeature.InsertReader)) {
                return stmt;
            }

            if (lexer.isEnabled(SQLParserFeature.InsertValueNative)) {
                parseValueClauseNative(stmt.getValuesList(), columnDefinitionList, columnSize, stmt);
            } else {
                parseValueClause(stmt.getValuesList(), columnDefinitionList, columnSize, stmt);
            }
        } else if (lexer.token() == Token.SET) {
            lexer.nextToken();

            SQLInsertStatement.ValuesClause values = new SQLInsertStatement.ValuesClause();
            stmt.addValueCause(values);

            for (; ; ) {
                SQLName name = this.exprParser.name();
                stmt.addColumn(name);
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                } else {
                    accept(Token.COLONEQ);
                }
                values.addValue(this.exprParser.expr());

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }

                break;
            }

        } else if (lexer.token() == (Token.SELECT)) {
            SQLSelect select = this.exprParser.createSelectParser().select();
            if (commentHints != null && !commentHints.isEmpty()) {
                select.setHeadHint(commentHints.get(0));
            }
            select.setParent(stmt);
            stmt.setQuery(select);
        } else if (lexer.token() == (Token.LPAREN)) {
            lexer.nextToken();
            SQLSelect select = this.exprParser.createSelectParser().select();
            select.setParent(stmt);
            stmt.setQuery(select);
            accept(Token.RPAREN);
        } else if (lexer.token() == WITH) {
            SQLSelect query = this.exprParser.createSelectParser().select();
            stmt.setQuery(query);
        }

        if (lexer.token() == Token.ON) {
            lexer.nextToken();
            acceptIdentifier("DUPLICATE");
            accept(Token.KEY);
            accept(Token.UPDATE);

            List<SQLExpr> duplicateKeyUpdate = stmt.getDuplicateKeyUpdate();
            for (; ; ) {
                SQLName name = this.exprParser.name();
                accept(Token.EQ);
                SQLExpr value;
                try {
                    value = this.exprParser.expr();
                } catch (EOFParserException e) {
                    throw new ParserException("EOF, " + name + "=", e);
                }

                SQLBinaryOpExpr assignment = new SQLBinaryOpExpr(name, SQLBinaryOperator.Equality, value);
                assignment.setParent(stmt);
                duplicateKeyUpdate.add(assignment);

                if (lexer.token() == Token.COMMA) {
                    lexer.nextTokenIdent();
                    continue;
                }
                break;
            }
        }

        return stmt;
    }

    public MySqlSelectParser createSQLSelectParser() {
        return new MySqlSelectParser(this.exprParser, selectListCache);
    }

    public SQLStatement parseSet() {
        accept(Token.SET);

        if (lexer.identifierEquals(FnvHash.Constants.PASSWORD)) {
            lexer.nextToken();
            SQLSetStatement stmt = new SQLSetStatement();
            stmt.setDbType(dbType);
            stmt.setOption(SQLSetStatement.Option.PASSWORD);

            SQLExpr user = null;
            if (lexer.token() == Token.FOR) {
                lexer.nextToken();
                user = this.exprParser.name();
            }

            accept(Token.EQ);

            SQLExpr password = this.exprParser.expr();

            stmt.set(user, password);

            return stmt;
        }

        Boolean global = null;
        Boolean session = null;
        boolean local = false;
        if (lexer.identifierEquals(GLOBAL)) {
            global = Boolean.TRUE;
            lexer.nextToken();
        } else if (lexer.identifierEquals(FnvHash.Constants.SESSION)) {
            session = Boolean.TRUE;
            lexer.nextToken();
        } else if (lexer.identifierEquals(FnvHash.Constants.LOCAL)) {
            lexer.nextToken();
            local = true;
        }

        if (lexer.identifierEquals(FnvHash.Constants.TRANSACTION)) {
            MySqlSetTransactionStatement stmt = new MySqlSetTransactionStatement();
            stmt.setGlobal(global);
            stmt.setSession(session);

            if (local) {
                stmt.setLocal(true);
            }

            lexer.nextToken();
            if (lexer.identifierEquals("ISOLATION")) {
                lexer.nextToken();
                acceptIdentifier("LEVEL");

                if (lexer.identifierEquals(READ)) {
                    lexer.nextToken();

                    if (lexer.identifierEquals("UNCOMMITTED")) {
                        stmt.setIsolationLevel("READ UNCOMMITTED");
                        lexer.nextToken();
                    } else if (lexer.identifierEquals(WRITE)) {
                        stmt.setIsolationLevel("READ WRITE");
                        lexer.nextToken();
                    } else if (lexer.identifierEquals("ONLY")) {
                        stmt.setIsolationLevel("READ ONLY");
                        lexer.nextToken();
                    } else if (lexer.identifierEquals("COMMITTED")) {
                        stmt.setIsolationLevel("READ COMMITTED");
                        lexer.nextToken();
                    } else {
                        throw new ParserException("UNKOWN TRANSACTION LEVEL : " + lexer.stringVal() + ", " + lexer.info());
                    }
                } else if (lexer.identifierEquals("SERIALIZABLE")) {
                    stmt.setIsolationLevel("SERIALIZABLE");
                    lexer.nextToken();
                } else if (lexer.identifierEquals("REPEATABLE")) {
                    lexer.nextToken();
                    if (lexer.identifierEquals(READ)) {
                        stmt.setIsolationLevel("REPEATABLE READ");
                        lexer.nextToken();
                    } else {
                        throw new ParserException("UNKOWN TRANSACTION LEVEL : " + lexer.stringVal() + ", " + lexer.info());
                    }
                } else {
                    throw new ParserException("UNKOWN TRANSACTION LEVEL : " + lexer.stringVal() + ", " + lexer.info());
                }
            } else if (lexer.identifierEquals(FnvHash.Constants.POLICY)) {
                lexer.nextToken();
                SQLExpr policy = this.exprParser.primary();
                stmt.setPolicy(policy);
            } else if (lexer.identifierEquals(READ)) {
                lexer.nextToken();
                if (lexer.identifierEquals("ONLY")) {
                    stmt.setAccessModel("ONLY");
                    lexer.nextToken();
                } else if (lexer.identifierEquals("WRITE")) {
                    stmt.setAccessModel("WRITE");
                    lexer.nextToken();
                } else {
                    throw new ParserException("UNKOWN ACCESS MODEL : " + lexer.stringVal() + ", " + lexer.info());
                }
            }

            return stmt;
        } else {
            SQLSetStatement stmt = new SQLSetStatement(getDbType());
            boolean mariadbSetStatementFlag = false;
            if (lexer.identifierEquals("STATEMENT")) {
                mariadbSetStatementFlag = true;
                lexer.nextToken();
            }
            parseAssignItems(stmt.getItems(), stmt, true);
            if (mariadbSetStatementFlag) {
                accept(Token.FOR);
                SQLStatement maridbSetForStatement = this.parseStatement();
                stmt.setMaridbSetForStatement(maridbSetForStatement);
            }
            if (global != null) {
                SQLVariantRefExpr varRef = (SQLVariantRefExpr) stmt.getItems().get(0).getTarget();
                varRef.setGlobal(true);
            }

            if (session != null) {
                SQLVariantRefExpr varRef = (SQLVariantRefExpr) stmt.getItems().get(0).getTarget();
                varRef.setSession(true);
            }

            if (lexer.token() == Token.HINT) {
                stmt.setHints(this.exprParser.parseHints());
            }

            return stmt;
        }
    }

    protected SQLStatement alterRest(Lexer.SavePoint mark) {
        if (lexer.identifierEquals(ONLINE)
                || lexer.identifierEquals(OFFLINE)
                || lexer.identifierEquals(FnvHash.Constants.IGNORE)
        ) {
            lexer.reset(mark);
            return alterTable();
        }

        if (lexer.identifierEquals(FnvHash.Constants.LOGFILE)) {
            return alterLogFileGroup();
        }

        if (lexer.identifierEquals(FnvHash.Constants.SERVER)) {
            return alterServer();
        }

        if (lexer.identifierEquals(FnvHash.Constants.ALGORITHM)) {
            return alterView();
        }

        if (lexer.identifierEquals(FnvHash.Constants.OUTLINE)) {
            lexer.reset(mark);
            return alterOutline();
        }

        if (lexer.identifierEquals(FnvHash.Constants.DEFINER)) {
            lexer.nextToken();
            accept(Token.EQ);
            this.getExprParser().userName();
            if (lexer.identifierEquals(FnvHash.Constants.EVENT)) {
                lexer.reset(mark);
                return alterEvent();
            } else {
                lexer.reset(mark);
                return alterView();
            }
        }
        if (lexer.identifierEquals("TABLEGROUP")) {
            lexer.reset(mark);
            return alterTableGroup();
        }

        if (lexer.identifierEquals("SYSTEM")) {
            lexer.reset(mark);
            return alterSystem();
        }

        throw new ParserException("TODO " + lexer.info());
    }

    private SQLStatement parseAddManageInstanceGroup() {
        lexer.nextToken();
        MySqlManageInstanceGroupStatement stmt = new MySqlManageInstanceGroupStatement();
        stmt.setOperation(new SQLIdentifierExpr("ADD"));

        acceptIdentifier("INSTANCE_GROUP");
        for (; ; ) {
            stmt.getGroupNames().add(exprParser.expr());
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }

        acceptIdentifier("REPLICATION");
        accept(Token.EQ);
        stmt.setReplication(exprParser.integerExpr());
        return stmt;
    }

    protected SQLStatement alterFullText() {
        accept(ALTER);
        accept(Token.FULLTEXT);

        MysqlAlterFullTextStatement stmt = new MysqlAlterFullTextStatement();

        stmt.setType(parseFullTextType());

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        accept(Token.SET);

        SQLAssignItem item = this.exprParser.parseAssignItem();
        stmt.setItem(item);
        return stmt;
    }

    protected SQLStatement alterTableGroup() {
        accept(ALTER);
        acceptIdentifier("TABLEGROUP");

        SQLName name = this.exprParser.name();

        SQLAlterTableGroupStatement stmt = new SQLAlterTableGroupStatement();
        stmt.setName(name);

        for (; ; ) {
            SQLName key = this.exprParser.name();
            accept(Token.EQ);
            SQLExpr value = this.exprParser.expr();
            stmt.getOptions().add(new SQLAssignItem(key, value));

            if (lexer.token() == Token.EOF) {
                break;
            }
        }

        return stmt;
    }

    protected SQLStatement alterSystem() {
        accept(ALTER);
        acceptIdentifier("SYSTEM");

        if (lexer.token() == Token.SET) {
            accept(Token.SET);
            acceptIdentifier("CONFIG");

            SQLAlterSystemSetConfigStatement stmt = new SQLAlterSystemSetConfigStatement();

            for (; ; ) {
                SQLName key = this.exprParser.name();
                accept(Token.EQ);
                SQLExpr value = this.exprParser.expr();
                stmt.getOptions().add(new SQLAssignItem(key, value));

                if (lexer.token() == Token.EOF) {
                    break;
                }
            }

            return stmt;

        } else if (lexer.identifierEquals("GET")) {
            acceptIdentifier("GET");
            acceptIdentifier("CONFIG");
            SQLName name = this.exprParser.name();

            SQLAlterSystemGetConfigStatement stmt = new SQLAlterSystemGetConfigStatement();
            stmt.setName(name);

            return stmt;
        }

        throw new ParserException("TODO " + lexer.info());
    }

    protected SQLStatement alterOutline() {
        accept(ALTER);

        if (lexer.identifierEquals(FnvHash.Constants.OUTLINE)) {
            lexer.nextToken();
        } else {
            throw new ParserException("TODO " + lexer.info());
        }

        SQLAlterOutlineStatement stmt = new SQLAlterOutlineStatement();
        stmt.setDbType(dbType);

        stmt.setName(this.exprParser.name());

        if (lexer.identifierEquals("RESYNC")) {
            lexer.nextToken();
            stmt.setResync(true);
        }

        return stmt;
    }

    protected SQLStatement alterView() {
        if (lexer.token() == ALTER) {
            lexer.nextToken();
        }

        SQLAlterViewStatement createView = new SQLAlterViewStatement(getDbType());

        if (lexer.identifierEquals("ALGORITHM")) {
            lexer.nextToken();
            accept(Token.EQ);
            String algorithm = lexer.stringVal();
            createView.setAlgorithm(algorithm);
            lexer.nextToken();
        }

        if (lexer.identifierEquals("DEFINER")) {
            lexer.nextToken();
            accept(Token.EQ);
            SQLName definer = (SQLName) this.exprParser.expr();
            createView.setDefiner(definer);
        }

        if (lexer.identifierEquals("SQL")) {
            lexer.nextToken();
            acceptIdentifier("SECURITY");
            String sqlSecurity = lexer.stringVal();
            createView.setSqlSecurity(sqlSecurity);
            lexer.nextToken();
        }

        if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
            lexer.nextToken();
            createView.setForce(true);
        }

        this.accept(Token.VIEW);

        if (lexer.token() == Token.IF || lexer.identifierEquals("IF")) {
            lexer.nextToken();
            accept(Token.NOT);
            accept(Token.EXISTS);
            createView.setIfNotExists(true);
        }

        createView.setName(exprParser.name());

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

            for (; ; ) {
                if (lexer.token() == Token.CONSTRAINT) {
                    SQLTableConstraint constraint = (SQLTableConstraint) this.exprParser.parseConstraint();
                    createView.addColumn(constraint);
                } else {
                    SQLColumnDefinition column = new SQLColumnDefinition();
                    column.setDbType(dbType);
                    SQLName expr = this.exprParser.name();
                    column.setName(expr);

                    this.exprParser.parseColumnRest(column);

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

                        SQLExpr comment;
                        if (lexer.token() == Token.LITERAL_ALIAS) {
                            String alias = lexer.stringVal();
                            if (alias.length() > 2 && alias.charAt(0) == '"' && alias.charAt(alias.length() - 1) == '"') {
                                alias = alias.substring(1, alias.length() - 1);
                            }
                            comment = new SQLCharExpr(alias);
                            lexer.nextToken();
                        } else {
                            comment = this.exprParser.primary();
                        }
                        column.setComment(comment);
                    }

                    column.setParent(createView);
                    createView.addColumn(column);
                }

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                } else {
                    break;
                }
            }

            accept(Token.RPAREN);
        }

        if (lexer.token() == Token.COMMENT) {
            lexer.nextToken();
            SQLCharExpr comment = (SQLCharExpr) exprParser.primary();
            createView.setComment(comment);
        }

        this.accept(Token.AS);

        SQLSelectParser selectParser = this.createSQLSelectParser();
        createView.setSubQuery(selectParser.select());

        if (lexer.token() == WITH) {
            lexer.nextToken();

            if (lexer.identifierEquals("CASCADED")) {
                createView.setWithCascaded(true);
                lexer.nextToken();
            } else if (lexer.identifierEquals("LOCAL")) {
                createView.setWithLocal(true);
                lexer.nextToken();
            } else if (lexer.identifierEquals("READ")) {
                lexer.nextToken();
                accept(Token.ONLY);
                createView.setWithReadOnly(true);
            }

            if (lexer.token() == Token.CHECK) {
                lexer.nextToken();
                acceptIdentifier("OPTION");
                createView.setWithCheckOption(true);
            }
        }

        return createView;
    }

    protected SQLStatement alterTableSpace() {
        if (lexer.token() == ALTER) {
            lexer.nextToken();
        }

        accept(Token.TABLESPACE);

        SQLName name = this.exprParser.name();

        MySqlAlterTablespaceStatement stmt = new MySqlAlterTablespaceStatement();
        stmt.setName(name);

        if (lexer.identifierEquals(FnvHash.Constants.ADD)) {
            lexer.nextToken();
            acceptIdentifier("DATAFILE");
            SQLExpr file = this.exprParser.primary();
            stmt.setAddDataFile(file);
        } else if (lexer.token() == Token.DROP) {
            lexer.nextToken();
            acceptIdentifier("DATAFILE");
            SQLExpr file = this.exprParser.primary();
            stmt.setDropDataFile(file);
        }

        if (lexer.identifierEquals(FnvHash.Constants.INITIAL_SIZE)) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLExpr initialSize = this.exprParser.expr();
            stmt.setInitialSize(initialSize);
        }

        if (lexer.identifierEquals(FnvHash.Constants.WAIT)) {
            lexer.nextToken();
            stmt.setWait(true);
        }

        if (lexer.identifierEquals(FnvHash.Constants.ENGINE)) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLExpr engine = this.exprParser.expr();
            stmt.setEngine(engine);
        }

        return stmt;
    }

    protected SQLStatement alterServer() {
        if (lexer.token() == ALTER) {
            lexer.nextToken();
        }
        acceptIdentifier("SERVER");

        SQLName name = this.exprParser.name();

        MySqlAlterServerStatement stmt = new MySqlAlterServerStatement();
        stmt.setName(name);

        acceptIdentifier("OPTIONS");
        accept(Token.LPAREN);
        if (lexer.token() == Token.USER) {
            lexer.nextToken();
            SQLExpr user = this.exprParser.name();
            stmt.setUser(user);
        }
        accept(Token.RPAREN);

        return stmt;
    }

    protected SQLStatement parseCreateLogFileGroup() {
        accept(Token.CREATE);
        if (lexer.token() == ALTER) {
            lexer.nextToken();
        }
        acceptIdentifier("LOGFILE");
        accept(GROUP);

        SQLName name = this.exprParser.name();

        MySqlCreateAddLogFileGroupStatement stmt = new MySqlCreateAddLogFileGroupStatement();
        stmt.setName(name);

        acceptIdentifier("ADD");
        acceptIdentifier("UNDOFILE");

        SQLExpr fileName = this.exprParser.primary();
        stmt.setAddUndoFile(fileName);

        if (lexer.identifierEquals(FnvHash.Constants.INITIAL_SIZE)) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLExpr initialSize = this.exprParser.expr();
            stmt.setInitialSize(initialSize);
        }

        if (lexer.identifierEquals(FnvHash.Constants.WAIT)) {
            lexer.nextToken();
            stmt.setWait(true);
        }

        if (lexer.identifierEquals(FnvHash.Constants.ENGINE)) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLExpr engine = this.exprParser.expr();
            stmt.setEngine(engine);
        }

        return stmt;
    }

    protected SQLStatement alterLogFileGroup() {
        if (lexer.token() == ALTER) {
            lexer.nextToken();
        }
        acceptIdentifier("LOGFILE");
        accept(GROUP);

        SQLName name = this.exprParser.name();

        MySqlAlterLogFileGroupStatement stmt = new MySqlAlterLogFileGroupStatement();
        stmt.setName(name);

        acceptIdentifier("ADD");
        acceptIdentifier("UNDOFILE");

        SQLExpr fileName = this.exprParser.primary();
        stmt.setAddUndoFile(fileName);

        if (lexer.identifierEquals(FnvHash.Constants.INITIAL_SIZE)) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLExpr initialSize = this.exprParser.expr();
            stmt.setInitialSize(initialSize);
        }

        if (lexer.identifierEquals(FnvHash.Constants.WAIT)) {
            lexer.nextToken();
            stmt.setWait(true);
        }

        if (lexer.identifierEquals(FnvHash.Constants.ENGINE)) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLExpr engine = this.exprParser.expr();
            stmt.setEngine(engine);
        }

        return stmt;
    }

    protected SQLStatement alterProcedure() {
        if (lexer.token() == ALTER) {
            lexer.nextToken();
        }
        accept(Token.PROCEDURE);

        SQLAlterProcedureStatement stmt = new SQLAlterProcedureStatement();
        stmt.setDbType(dbType);

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        // for mysql
        for (; ; ) {
            if (lexer.token() == Token.COMMENT) {
                lexer.nextToken();
                SQLExpr comment = this.exprParser.primary();
                stmt.setComment(comment);
            } else if (lexer.identifierEquals(FnvHash.Constants.LANGUAGE)) {
                lexer.nextToken();
                acceptIdentifier("SQL");
                stmt.setLanguageSql(true);
            } else if (lexer.identifierEquals(FnvHash.Constants.SQL)) {
                lexer.nextToken();
                acceptIdentifier("SECURITY");

                SQLExpr sqlSecurity = this.exprParser.name();
                stmt.setSqlSecurity(sqlSecurity);
            } else if (lexer.identifierEquals(FnvHash.Constants.CONTAINS) || lexer.token() == Token.CONTAINS) {
                lexer.nextToken();
                acceptIdentifier("SQL");
                stmt.setContainsSql(true);
            } else {
                break;
            }
        }

        return stmt;
    }

    protected SQLStatement parseAlterFunction() {
        SQLAlterFunctionStatement stmt = new SQLAlterFunctionStatement();

        if (lexer.token() == ALTER) {
            lexer.nextToken();
        }
        accept(Token.FUNCTION);
        stmt.setDbType(dbType);

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        // for mysql
        for (; ; ) {
            if (lexer.token() == Token.COMMENT) {
                lexer.nextToken();
                SQLExpr comment = this.exprParser.primary();
                stmt.setComment(comment);
            } else if (lexer.identifierEquals(FnvHash.Constants.LANGUAGE)) {
                lexer.nextToken();
                acceptIdentifier("SQL");
                stmt.setLanguageSql(true);
            } else if (lexer.identifierEquals(FnvHash.Constants.SQL)) {
                lexer.nextToken();
                acceptIdentifier("SECURITY");

                SQLExpr sqlSecurity = this.exprParser.name();
                stmt.setSqlSecurity(sqlSecurity);
            } else if (lexer.identifierEquals(FnvHash.Constants.CONTAINS) || lexer.token() == Token.CONTAINS) {
                lexer.nextToken();
                acceptIdentifier("SQL");
                stmt.setContainsSql(true);
            } else {
                break;
            }
        }

        return stmt;
    }

    protected SQLStatement parseCreateEvent() {
        if (lexer.token() == Token.CREATE) {
            lexer.nextToken();
        }

        MySqlCreateEventStatement stmt = new MySqlCreateEventStatement();

        if (lexer.identifierEquals(FnvHash.Constants.DEFINER)) {
            lexer.nextToken();
            accept(Token.EQ);
            SQLName definer = this.getExprParser().userName();
            stmt.setDefiner(definer);

            if (lexer.token() == Token.LPAREN) {
                lexer.nextToken();
                accept(Token.RPAREN);
            }
        }

        acceptIdentifier("EVENT");

        if (lexer.token() == Token.IF) {
            lexer.nextToken();
            accept(Token.NOT);
            accept(Token.EXISTS);
            stmt.setIfNotExists(true);
        }

        SQLName eventName = this.exprParser.name();
        stmt.setName(eventName);

        while (lexer.token() == Token.ON) {
            lexer.nextToken();
            if (lexer.identifierEquals(FnvHash.Constants.SCHEDULE)) {
                lexer.nextToken();
                MySqlEventSchedule schedule = parseSchedule();
                stmt.setSchedule(schedule);
            } else if (lexer.identifierEquals(FnvHash.Constants.COMPLETION)) {
                lexer.nextToken();

                boolean value;
                if (lexer.token() == Token.NOT) {
                    lexer.nextToken();
                    value = false;
                } else {
                    value = true;
                }
                acceptIdentifier("PRESERVE");
                stmt.setOnCompletionPreserve(value);
            } else {
                throw new ParserException("TODO " + lexer.info());
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.RENAME)) {
            lexer.nextToken();
            accept(Token.TO);
            SQLName renameTo = this.exprParser.name();
            stmt.setRenameTo(renameTo);
        }

        if (lexer.token() == Token.ENABLE) {
            stmt.setEnable(true);
            lexer.nextToken();
        } else if (lexer.token() == Token.DISABLE) {
            lexer.nextToken();
            stmt.setEnable(false);

            if (lexer.token() == Token.ON) {
                lexer.nextToken();
                acceptIdentifier("SLAVE");
                stmt.setDisableOnSlave(true);
            }
        }

        if (lexer.token() == Token.COMMENT) {
            lexer.nextToken();
            SQLExpr comment = this.exprParser.primary();
            stmt.setComment(comment);
        }

        if (lexer.token() == Token.DO) {
            lexer.nextToken();
            SQLStatement eventBody = this.parseStatement();
            stmt.setEventBody(eventBody);
        } else if (lexer.token() == Token.IDENTIFIER) {
            SQLExpr expr = this.exprParser.expr();
            SQLExprStatement eventBody = new SQLExprStatement(expr);
            eventBody.setDbType(dbType);
            stmt.setEventBody(eventBody);
        }

        return stmt;
    }

    protected SQLStatement alterEvent() {
        if (lexer.token() == ALTER) {
            lexer.nextToken();
        }

        MySqlAlterEventStatement stmt = new MySqlAlterEventStatement();

        if (lexer.identifierEquals(FnvHash.Constants.DEFINER)) {
            lexer.nextToken();
            accept(Token.EQ);
            SQLName definer = this.getExprParser().userName();
            stmt.setDefiner(definer);
        }

        acceptIdentifier("EVENT");

        SQLName eventName = this.exprParser.name();
        stmt.setName(eventName);

        while (lexer.token() == Token.ON) {
            lexer.nextToken();
            if (lexer.identifierEquals(FnvHash.Constants.SCHEDULE)) {
                lexer.nextToken();
                MySqlEventSchedule schedule = parseSchedule();
                stmt.setSchedule(schedule);
            } else if (lexer.identifierEquals(FnvHash.Constants.COMPLETION)) {
                lexer.nextToken();

                boolean value;
                if (lexer.token() == Token.NOT) {
                    lexer.nextToken();
                    value = false;
                } else {
                    value = true;
                }
                acceptIdentifier("PRESERVE");
                stmt.setOnCompletionPreserve(value);
            } else {
                throw new ParserException("TODO " + lexer.info());
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.RENAME)) {
            lexer.nextToken();
            accept(Token.TO);
            SQLName renameTo = this.exprParser.name();
            stmt.setRenameTo(renameTo);
        }

        if (lexer.token() == Token.ENABLE) {
            stmt.setEnable(true);
            lexer.nextToken();
        } else if (lexer.token() == Token.DISABLE) {
            lexer.nextToken();
            stmt.setEnable(false);

            if (lexer.token() == Token.ON) {
                lexer.nextToken();
                acceptIdentifier("SLAVE");
                stmt.setDisableOnSlave(true);
            }
        }

        if (lexer.token() == Token.COMMENT) {
            lexer.nextToken();
            SQLExpr comment = this.exprParser.primary();
            stmt.setComment(comment);
        }

        if (lexer.token() == Token.DO) {
            lexer.nextToken();
            SQLStatement eventBody = this.parseStatement();
            stmt.setEventBody(eventBody);
        } else if (lexer.token() == Token.IDENTIFIER) {
            SQLExpr expr = this.exprParser.expr();
            SQLExprStatement eventBody = new SQLExprStatement(expr);
            eventBody.setDbType(dbType);
            stmt.setEventBody(eventBody);
        }

        return stmt;
    }

    private MySqlEventSchedule parseSchedule() {
        MySqlEventSchedule schedule = new MySqlEventSchedule();

        if (lexer.identifierEquals(FnvHash.Constants.AT)) {
            lexer.nextToken();
            schedule.setAt(this.exprParser.expr());
        } else if (lexer.identifierEquals(FnvHash.Constants.EVERY)) {
            lexer.nextToken();
            SQLExpr value = this.exprParser.expr();
            String unit = lexer.stringVal();
            lexer.nextToken();

            SQLIntervalExpr intervalExpr = new SQLIntervalExpr();
            intervalExpr.setValue(value);
            intervalExpr.setUnit(SQLIntervalUnit.valueOf(unit.toUpperCase()));

            schedule.setEvery(intervalExpr);
        }

        if (lexer.identifierEquals(FnvHash.Constants.STARTS)) {
            lexer.nextToken();
            schedule.setStarts(this.exprParser.expr());

            if (lexer.identifierEquals(FnvHash.Constants.ENDS)) {
                lexer.nextToken();
                schedule.setEnds(this.exprParser.expr());
            }
        } else if (lexer.identifierEquals(FnvHash.Constants.ENDS)) {
            lexer.nextToken();
            schedule.setEnds(this.exprParser.expr());
        }

        return schedule;
    }

    private boolean parseAlterSpecification(SQLAlterTableStatement stmt) {
        // Specification except table options.
        switch (lexer.token()) {
            case IDENTIFIER:
                if (lexer.identifierEquals(FnvHash.Constants.ADD)) {
                    alterTableAdd(stmt);
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.ALGORITHM)) {
                    // ALGORITHM [=] {DEFAULT|INPLACE|COPY}
                    lexer.nextToken();
                    if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                    }
                    stmt.addItem(new MySqlAlterTableOption("ALGORITHM", lexer.stringVal()));
                    lexer.nextToken();
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.CHANGE)) {
                    // CHANGE [COLUMN] old_col_name new_col_name column_definition [FIRST|AFTER col_name]
                    lexer.nextToken();
                    if (lexer.token() == Token.COLUMN) {
                        lexer.nextToken();
                    }
                    MySqlAlterTableChangeColumn item = new MySqlAlterTableChangeColumn();
                    item.setColumnName(this.exprParser.name());
                    item.setNewColumnDefinition(this.exprParser.parseColumn());
                    if (lexer.identifierEquals("AFTER")) {
                        lexer.nextToken();
                        item.setAfterColumn(this.exprParser.name());
                    } else if (lexer.identifierEquals("FIRST")) {
                        lexer.nextToken();
                        if (lexer.token() == Token.IDENTIFIER) {
                            item.setFirstColumn(this.exprParser.name());
                        } else {
                            item.setFirst(true);
                        }
                    }
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.CONVERT)) {
                    // CONVERT TO CHARACTER SET charset_name [COLLATE collation_name]
                    lexer.nextToken();
                    accept(Token.TO);
                    acceptIdentifier("CHARACTER");
                    accept(Token.SET);
                    SQLAlterTableConvertCharSet item = new SQLAlterTableConvertCharSet();
                    SQLExpr charset = this.exprParser.name();
                    item.setCharset(charset);
                    if (lexer.identifierEquals("COLLATE")) {
                        lexer.nextToken();
                        SQLExpr collate = this.exprParser.primary();
                        item.setCollate(collate);
                    }
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.DISCARD)) {
                    // DISCARD PARTITION {partition_names | ALL} TABLESPACE
                    // DISCARD TABLESPACE
                    lexer.nextToken();
                    if (lexer.token() == Token.PARTITION) {
                        lexer.nextToken();
                        SQLAlterTableDiscardPartition item = new SQLAlterTableDiscardPartition();

                        if (lexer.token() == Token.ALL) {
                            lexer.nextToken();
                            item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                        } else {
                            this.exprParser.names(item.getPartitions(), item);
                        }

                        if (lexer.token() == Token.TABLESPACE) {
                            lexer.nextToken();
                            item.setTablespace(true);
                        }

                        stmt.addItem(item);
                    } else {
                        accept(Token.TABLESPACE);
                        MySqlAlterTableDiscardTablespace item = new MySqlAlterTableDiscardTablespace();
                        stmt.addItem(item);
                    }
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.IMPORT)) {
                    // IMPORT PARTITION {partition_names | ALL} TABLESPACE
                    // IMPORT TABLESPACE
                    lexer.nextToken();
                    if (lexer.token() == Token.PARTITION) {
                        lexer.nextToken();
                        SQLAlterTableImportPartition item = new SQLAlterTableImportPartition();

                        if (lexer.token() == Token.ALL) {
                            lexer.nextToken();
                            item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                        } else {
                            this.exprParser.names(item.getPartitions(), item);
                        }

                        if (lexer.token() == Token.TABLESPACE) {
                            lexer.nextToken();
                            item.setTablespace(true);
                        }

                        stmt.addItem(item);
                    } else {
                        accept(Token.TABLESPACE);
                        MySqlAlterTableImportTablespace item = new MySqlAlterTableImportTablespace();
                        stmt.addItem(item);
                    }
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
                    // FORCE
                    lexer.nextToken();
                    MySqlAlterTableForce item = new MySqlAlterTableForce();
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.MODIFY)) {
                    // MODIFY [COLUMN] col_name column_definition [FIRST | AFTER col_name]
                    lexer.nextToken();
                    if (lexer.token() == Token.COLUMN) {
                        lexer.nextToken();
                    }
                    boolean paren = false;
                    if (lexer.token() == Token.LPAREN) {
                        paren = true;
                        lexer.nextToken();
                    }
                    for (; ; ) {
                        MySqlAlterTableModifyColumn item = new MySqlAlterTableModifyColumn();
                        item.setNewColumnDefinition(this.exprParser.parseColumn());
                        if (lexer.identifierEquals("AFTER")) {
                            lexer.nextToken();
                            item.setAfterColumn(this.exprParser.name());
                        } else if (lexer.identifierEquals("FIRST")) {
                            lexer.nextToken();
                            if (lexer.token() == Token.IDENTIFIER) {
                                item.setFirstColumn(this.exprParser.name());
                            } else {
                                item.setFirst(true);
                            }
                        }
                        stmt.addItem(item);

                        if (paren && lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }

                    if (paren) {
                        accept(Token.RPAREN);
                    }
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.RENAME)) {
                    // RENAME {INDEX|KEY} old_index_name TO new_index_name
                    // RENAME [TO|AS] new_tbl_name
                    lexer.nextToken();
                    switch (lexer.token()) {
                        case INDEX:
                        case KEY: {
                            lexer.nextToken();
                            SQLName name = this.exprParser.name();
                            accept(Token.TO);
                            SQLName to = this.exprParser.name();
                            SQLAlterTableRenameIndex item = new SQLAlterTableRenameIndex(name, to);
                            stmt.addItem(item);
                            return true;
                        }

                        case COLUMN: {
                            lexer.nextToken();
                            SQLName columnName = exprParser.name();
                            accept(Token.TO);
                            SQLName toName = this.exprParser.name();
                            SQLAlterTableRenameColumn renameColumn = new SQLAlterTableRenameColumn();
                            renameColumn.setColumn(columnName);
                            renameColumn.setTo(toName);
                            stmt.addItem(renameColumn);
                            return true;
                        }

                        case TO:
                        case AS:
                            lexer.nextToken();
                        case IDENTIFIER:
                            SQLAlterTableRename item = new SQLAlterTableRename();
                            SQLName to = this.exprParser.name();
                            item.setTo(to);
                            stmt.addItem(item);
                            return true;

                        default:
                            break;
                    }
                } else if (lexer.identifierEquals(FnvHash.Constants.WITHOUT)) {
                    // WITHOUT VALIDATION
                    lexer.nextToken();
                    acceptIdentifier("VALIDATION");
                    MySqlAlterTableValidation item = new MySqlAlterTableValidation();
                    item.setWithValidation(false);
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals("COALESCE")) {
                    lexer.nextToken();
                    accept(Token.PARTITION);

                    SQLAlterTableCoalescePartition item = new SQLAlterTableCoalescePartition();
                    SQLIntegerExpr countExpr = this.exprParser.integerExpr();
                    item.setCount(countExpr);
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals("REORGANIZE")) {
                    // REORGANIZE PARTITION partition_names INTO (partition_definitions)
                    lexer.nextToken();
                    accept(Token.PARTITION);

                    SQLAlterTableReOrganizePartition item = new SQLAlterTableReOrganizePartition();

                    this.exprParser.names(item.getNames(), item);

                    accept(Token.INTO);
                    accept(Token.LPAREN);
                    for (; ; ) {
                        MysqlPartitionSingle partition = this.getExprParser().parsePartition();

                        item.addPartition(partition);

                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        } else {
                            break;
                        }
                    }
                    accept(Token.RPAREN);
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.EXCHANGE)) {
                    // EXCHANGE PARTITION partition_name WITH TABLE tbl_name [{WITH|WITHOUT} VALIDATION]
                    lexer.nextToken();
                    accept(Token.PARTITION);

                    SQLAlterTableExchangePartition item = new SQLAlterTableExchangePartition();

                    SQLName partition = this.exprParser.name();
                    item.addPartition(partition);

                    accept(WITH);
                    accept(TABLE);
                    SQLName table = this.exprParser.name();
                    item.setTable(table);

                    if (lexer.token() == WITH) {
                        lexer.nextToken();
                        acceptIdentifier("VALIDATION");
                        item.setValidation(true);
                    } else if (lexer.identifierEquals(FnvHash.Constants.WITHOUT)) {
                        lexer.nextToken();
                        acceptIdentifier("VALIDATION");
                        item.setValidation(false);
                    }
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals("REBUILD")) {
                    // REBUILD PARTITION {partition_names | ALL}
                    lexer.nextToken();
                    accept(Token.PARTITION);

                    SQLAlterTableRebuildPartition item = new SQLAlterTableRebuildPartition();

                    if (lexer.token() == Token.ALL) {
                        lexer.nextToken();
                        item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                    } else {
                        this.exprParser.names(item.getPartitions(), item);
                    }
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals("REPAIR")) {
                    // REPAIR PARTITION {partition_names | ALL}
                    lexer.nextToken();
                    accept(Token.PARTITION);

                    SQLAlterTableRepairPartition item = new SQLAlterTableRepairPartition();

                    if (lexer.token() == Token.ALL) {
                        lexer.nextToken();
                        item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                    } else {
                        this.exprParser.names(item.getPartitions(), item);
                    }
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.REMOVE)) {
                    // REMOVE PARTITIONING
                    lexer.nextToken();
                    acceptIdentifier("PARTITIONING");
                    stmt.setRemovePatiting(true);
                } else if (lexer.identifierEquals("UPGRADE")) {
                    // UPGRADE PARTITIONING
                    lexer.nextToken();
                    acceptIdentifier("PARTITIONING");
                    stmt.setUpgradePatiting(true);
                } else if (lexer.identifierEquals("HOT_PARTITION_COUNT")) {
                    // UPGRADE PARTITIONING
                    lexer.nextToken();
                    accept(EQ);
                    try {
                        stmt.getTableOptions().add(new SQLAssignItem(new SQLIdentifierExpr("HOT_PARTITION_COUNT"), this.exprParser.integerExpr()));
                    } catch (Exception e) {
                        throw new ParserException("only integer number is supported for hot_partition_count");
                    }
                }

                //
                // Other not in MySql documents.
                //

                else if (lexer.identifierEquals(FnvHash.Constants.PARTITIONS)) {
                    // Caution: Not in MySql documents.
                    SQLAlterTablePartitionCount item = new SQLAlterTablePartitionCount();
                    lexer.nextToken();
                    if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                    }
                    item.setCount((SQLIntegerExpr) exprParser.integerExpr());
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITION)) {
                    // Caution: Not in MySql documents.
                    lexer.nextToken();
                    if (lexer.identifierEquals(FnvHash.Constants.LIFECYCLE)) {
                        lexer.nextToken();
                        SQLAlterTableSubpartitionLifecycle item = new SQLAlterTableSubpartitionLifecycle();
                        if (lexer.token() == Token.LITERAL_INT) {
                            for (; ; ) {
                                item.getPartitionIds().add(this.exprParser.integerExpr());
                                String pidStr = lexer.stringVal();
                                accept(Token.VARIANT);
                                String s = pidStr.replaceAll(":", "");
                                if (StringUtils.isEmpty(s)) {
                                    item.getSubpartitionLifeCycle().add(exprParser.integerExpr());
                                } else {
                                    item.getSubpartitionLifeCycle().add(new SQLIntegerExpr(Integer.valueOf(s)));
                                }

                                if (lexer.token() == Token.COMMA) {
                                    lexer.nextToken();
                                    continue;
                                }

                                break;
                            }
                        }
                        stmt.addItem(item);
                    }
                    return true;
                } else if (lexer.identifierEquals("BLOCK_SIZE")) {
                    // Caution: Not in MySql documents.
                    SQLAlterTableBlockSize item = new SQLAlterTableBlockSize();
                    lexer.nextToken();
                    if (lexer.token() == Token.EQ) {
                        accept(Token.EQ);
                    }
                    item.setSize((SQLIntegerExpr) exprParser.expr());
                    stmt.addItem(item);
                    return true;
                } else if (lexer.identifierEquals(INSERT_METHOD)) {
                    // Caution: Not in MySql documents.
                    lexer.nextToken();
                    if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                    }
                    stmt.getTableOptions().add(new SQLAssignItem(new SQLIdentifierExpr(INSERT_METHOD), this.exprParser.primary()));
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.CLUSTERED)) {
                    // Caution: Not in MySql documents.
                    SQLAlterTableModifyClusteredBy clusteredBy = new SQLAlterTableModifyClusteredBy();

                    acceptIdentifier("CLUSTERED");
                    accept(Token.BY);
                    accept(Token.LPAREN);

                    // for ads: ALTER TABLE SCHEMA1.TABLE1 CLUSTERED BY ();
                    if (lexer.token() != Token.RPAREN) {
                        for (; ; ) {
                            clusteredBy.addClusterColumn(this.exprParser.name());

                            if (lexer.token() == Token.COMMA) {
                                accept(Token.COMMA);
                                continue;
                            }
                            break;
                        }
                    }

                    accept(Token.RPAREN);

                    stmt.addItem(clusteredBy);
                    return true;
                } else if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITION_AVAILABLE_PARTITION_NUM)) {
                    // Caution: Not in MySql documents.
                    lexer.nextToken();
                    if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                    }
                    SQLIntegerExpr num = this.exprParser.integerExpr();
                    SQLAlterTableSubpartitionAvailablePartitionNum item = new SQLAlterTableSubpartitionAvailablePartitionNum();
                    item.setNumber(num);
                    stmt.addItem(item);
                    return true;
                }
                break;

            case ALTER: {
                lexer.nextToken();
                if (lexer.token() == INDEX) {
                    // Caution: Not in MySql documents.
                    lexer.nextToken();

                    SQLName indexName = this.exprParser.name();

                    if (lexer.identifierEquals("VISIBLE") || lexer.identifierEquals("INVISIBLE")) {
                        SQLAlterTableAlterIndex alterIndex = new SQLAlterTableAlterIndex();
                        alterIndex.setName(indexName);
                        alterIndex.getIndexDefinition().getOptions().setVisible(lexer.identifierEquals("VISIBLE"));
                        alterIndex.getIndexDefinition().getOptions().setInvisible(lexer.identifierEquals("INVISIBLE"));
                        lexer.nextToken();
                        stmt.addItem(alterIndex);
                        break;
                    }

                    MySqlAlterTableAlterFullTextIndex alterIndex = new MySqlAlterTableAlterFullTextIndex();
                    alterIndex.setIndexName(indexName);

                    accept(Token.SET);
                    accept(Token.FULLTEXT);

                    if (lexer.token() == INDEX) {
                        lexer.nextToken();
                        alterIndex.setAnalyzerType(AnalyzerIndexType.INDEX);
                    } else if (lexer.identifierEquals(FnvHash.Constants.QUERY)) {
                        lexer.nextToken();
                        alterIndex.setAnalyzerType(AnalyzerIndexType.QUERY);
                    }

                    acceptIdentifier("ANALYZER");

                    accept(Token.EQ);

                    alterIndex.setAnalyzerName(this.exprParser.name());
                    stmt.addItem(alterIndex);
                } else if (lexer.token() == Token.CHECK || lexer.token() == Token.CONSTRAINT) {
                    lexer.nextToken();
                    MysqlAlterTableAlterCheck check = new MysqlAlterTableAlterCheck();
                    check.setName(this.exprParser.name());
                    boolean enforce = true;
                    if (lexer.token() == Token.NOT) {
                        enforce = false;
                        lexer.nextToken();
                    }
                    if (lexer.stringVal().equalsIgnoreCase("ENFORCED")) {
                        check.setEnforced(enforce);
                        lexer.nextToken();
                    }
                    stmt.addItem(check);
                } else {
                    // ALTER [COLUMN] col_name {SET DEFAULT literal | DROP DEFAULT}
                    if (lexer.token() == Token.COLUMN) {
                        lexer.nextToken();
                    }
                    MySqlAlterTableAlterColumn alterColumn = new MySqlAlterTableAlterColumn();
                    alterColumn.setColumn(this.exprParser.name());
                    if (lexer.token() == Token.SET) {
                        lexer.nextToken();
                        if (lexer.identifierEquals("VISIBLE") || lexer.identifierEquals("INVISIBLE")) {
                            alterColumn.setVisibleType(lexer.stringVal());
                            lexer.nextToken();
                        } else {
                            accept(Token.DEFAULT);
                            alterColumn.setDefaultExpr(this.exprParser.expr());
                        }
                    } else {
                        accept(Token.DROP);
                        accept(Token.DEFAULT);
                        alterColumn.setDropDefault(true);
                    }
                    stmt.addItem(alterColumn);
                }
                return true;
            }

            // [DEFAULT] CHARACTER SET [=] charset_name [COLLATE [=] collation_name]
            // parse in table options.

            case DISABLE:
                lexer.nextToken();
                if (lexer.token() == Token.CONSTRAINT) {
                    // Caution: Not in MySql documents.
                    lexer.nextToken();
                    SQLAlterTableDisableConstraint item = new SQLAlterTableDisableConstraint();
                    item.setConstraintName(this.exprParser.name());
                    stmt.addItem(item);
                } else {
                    // DISABLE KEYS
                    acceptIdentifier("KEYS");
                    SQLAlterTableDisableKeys item = new SQLAlterTableDisableKeys();
                    stmt.addItem(item);
                }
                return true;

            case ENABLE:
                lexer.nextToken();
                if (lexer.token() == Token.CONSTRAINT) {
                    // Caution: Not in MySql documents.
                    lexer.nextToken();
                    SQLAlterTableEnableConstraint item = new SQLAlterTableEnableConstraint();
                    item.setConstraintName(this.exprParser.name());
                    stmt.addItem(item);
                } else {
                    // ENABLE KEYS
                    acceptIdentifier("KEYS");
                    SQLAlterTableEnableKeys item = new SQLAlterTableEnableKeys();
                    stmt.addItem(item);
                }
                return true;

            case LOCK: {
                // LOCK [=] {DEFAULT|NONE|SHARED|EXCLUSIVE}
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                MySqlAlterTableLock item = new MySqlAlterTableLock();
                item.setLockType(new SQLIdentifierExpr(lexer.stringVal()));
                lexer.nextToken();
                stmt.addItem(item);
                return true;
            }

            case ORDER: {
                lexer.nextToken();
                accept(Token.BY);
                MySqlAlterTableOrderBy item = new MySqlAlterTableOrderBy();
                while (true) {
                    if (lexer.token() == Token.IDENTIFIER) {
                        SQLSelectOrderByItem column = this.exprParser.parseSelectOrderByItem();
                        column.setParent(item);
                        item.addColumn(column);
                    } else {
                        break;
                    }
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                    } else {
                        break;
                    }
                }
                stmt.addItem(item);
                return true;
            }

            case WITH: {
                // WITH VALIDATION
                lexer.nextToken();
                acceptIdentifier("VALIDATION");
                MySqlAlterTableValidation item = new MySqlAlterTableValidation();
                item.setWithValidation(true);
                stmt.addItem(item);
                return true;
            }

            case DROP:
                // DROP [COLUMN] col_name
                // DROP {INDEX|KEY} index_name
                // DROP PRIMARY KEY
                // DROP FOREIGN KEY fk_symbol
                // DROP PARTITION partition_names
                // TODO: need check.
                parseAlterDrop(stmt);
                return true;

            case TRUNCATE: {
                // TRUNCATE PARTITION {partition_names | ALL}
                lexer.nextToken();
                accept(Token.PARTITION);
                SQLAlterTableTruncatePartition item = new SQLAlterTableTruncatePartition();
                if (lexer.token() == Token.ALL) {
                    item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                    lexer.nextToken();
                } else {
                    this.exprParser.names(item.getPartitions(), item);
                }
                stmt.addItem(item);
                return true;
            }

            case ANALYZE: {
                // ANALYZE PARTITION {partition_names | ALL}
                lexer.nextToken();
                accept(Token.PARTITION);
                SQLAlterTableAnalyzePartition item = new SQLAlterTableAnalyzePartition();
                if (lexer.token() == Token.ALL) {
                    lexer.nextToken();
                    item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                } else {
                    this.exprParser.names(item.getPartitions(), item);
                }
                stmt.addItem(item);
                return true;
            }

            case CHECK: {
                // CHECK PARTITION {partition_names | ALL}
                lexer.nextToken();
                accept(Token.PARTITION);
                SQLAlterTableCheckPartition item = new SQLAlterTableCheckPartition();
                if (lexer.token() == Token.ALL) {
                    lexer.nextToken();
                    item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                } else {
                    this.exprParser.names(item.getPartitions(), item);
                }
                stmt.addItem(item);
                return true;
            }

            case OPTIMIZE: {
                // OPTIMIZE PARTITION {partition_names | ALL}
                lexer.nextToken();
                accept(Token.PARTITION);
                SQLAlterTableOptimizePartition item = new SQLAlterTableOptimizePartition();
                if (lexer.token() == Token.ALL) {
                    lexer.nextToken();
                    item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                } else {
                    this.exprParser.names(item.getPartitions(), item);
                }
                stmt.addItem(item);
                return true;
            }

            //
            // Other not in MySql documents.
            //

            case SET: {
                lexer.nextToken();

                if (lexer.identifierEquals(FnvHash.Constants.RULE)) {
                    SQLAlterTableSetOption setOption = new SQLAlterTableSetOption();
                    SQLAssignItem item = this.exprParser.parseAssignItem();
                    setOption.addOption(item);
                    stmt.addItem(setOption);
                } else {
                    acceptIdentifier("TBLPROPERTIES");
                    SQLAlterTableSetOption setOption = new SQLAlterTableSetOption();
                    accept(Token.LPAREN);
                    for (; ; ) {
                        SQLAssignItem item = this.exprParser.parseAssignItem();
                        setOption.addOption(item);
                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    accept(Token.RPAREN);
                    stmt.addItem(setOption);

                    if (lexer.token() == Token.ON) {
                        lexer.nextToken();
                        SQLName on = this.exprParser.name();
                        setOption.setOn(on);
                    }
                }

                return true;
            }

            case PARTITION: {
                Lexer.SavePoint mark = lexer.mark();
                lexer.nextToken();
                if (lexer.identifierEquals(FnvHash.Constants.LIFECYCLE)) {
                    lexer.nextToken();
                    SQLAlterTablePartitionLifecycle item = new SQLAlterTablePartitionLifecycle();
                    if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                    }
                    item.setLifecycle((SQLIntegerExpr) exprParser.integerExpr());
                    stmt.addItem(item);
                    return true;
                } else {
                    lexer.reset(mark);
                }
            }
        }
        return false;
    }

    @Override
    protected void alterTableAddRest(SQLAlterTableStatement stmt) {
        acceptIdentifier("ADD");

        switch (lexer.token()) {
            case LPAREN:
                parseAlterTableAddColumn(stmt);
                break;
            // ADD {FULLTEXT|SPATIAL} [INDEX|KEY] [index_name] (key_part,...) [index_option] ...
            case IDENTIFIER:
                if (lexer.identifierEquals(FnvHash.Constants.SPATIAL)
                        || lexer.identifierEquals(FnvHash.Constants.CLUSTERED)
                        || lexer.identifierEquals(FnvHash.Constants.CLUSTERING)
                        || lexer.identifierEquals(FnvHash.Constants.ANN)
                        || lexer.identifierEquals(FnvHash.Constants.GLOBAL)
                        || lexer.identifierEquals(FnvHash.Constants.LOCAL)) {
                    // Index.
                    SQLAlterTableAddIndex item = new SQLAlterTableAddIndex();
                    this.exprParser.parseIndex(item.getIndexDefinition());
                    stmt.addItem(item);
                } else if (lexer.identifierEquals(FnvHash.Constants.EXTPARTITION)) {
                    // Caution: Not in MySql documents.
                    lexer.nextToken();
                    accept(Token.LPAREN);
                    SQLAlterTableAddExtPartition extPartitionItem = new SQLAlterTableAddExtPartition();
                    MySqlExtPartition partitionDef = parseExtPartition();
                    extPartitionItem.setExPartition(partitionDef);
                    stmt.addItem(extPartitionItem);
                    accept(Token.RPAREN);
                } else {
                    // Add column.
                    parseAlterTableAddColumn(stmt);
                }
                break;
            default:
                // Add column.
                parseAlterTableAddColumn(stmt);
                break;
        }
    }

    protected void alterTableAddPartition(SQLAlterTableStatement stmt) {
        acceptIdentifier("ADD");
        accept(PARTITION);
        SQLAlterTableAddPartition item = new SQLAlterTableAddPartition();
        if (lexer.identifierEquals("PARTITIONS")) {
            lexer.nextToken();
            item.setPartitionCount(this.exprParser.integerExpr());
        }
        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();

            for (; ; ) {
                MysqlPartitionSingle partition = this.getExprParser().parsePartition();
                item.addPartition(partition);
                if (lexer.token() == COMMA) {
                    lexer.nextToken();

                    if (lexer.token() == PARTITION) {
                        continue;
                    }
                }
                break;
            }
            accept(Token.RPAREN);
        }
        stmt.addItem(item);
    }

    protected void alterTableBefore(SQLAlterTableStatement stmt) {
        for (;;) {
            if (lexer.nextIfIdentifier(ONLINE)) {
                stmt.setOnline(true);
            } else if (lexer.nextIfIdentifier(OFFLINE)) {
                stmt.setIgnore(true);
            } else if (lexer.nextIfIdentifier(IGNORE)) {
                stmt.setIgnore(true);
            } else {
                break;
            }
        }
    }

    protected SQLAlterStatement alterTableAfterName(SQLAlterTableStatement stmt) {
        while (true) {
            boolean parsed = ((MySqlExprParser) this.exprParser).parseTableOptions(stmt.getTableOptions(), stmt);
            if (!parsed) {
                parsed = parseAlterSpecification(stmt);
            }

            if (parsed) {
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                }
            } else {
                break;
            }
        }

        // partition_options
        if (Token.PARTITION == lexer.token()) {
            SQLPartitionBy partitionBy = this.getSQLCreateTableParser().parsePartitionBy();
            stmt.setPartition(partitionBy);
        } else {
            // Change to rename table if only one rename to xx.
            if (stmt.getTableOptions().isEmpty() && 1 == stmt.getItems().size() && stmt.getItems().get(0) instanceof SQLAlterTableRename) {
                MySqlRenameTableStatement renameStmt = new MySqlRenameTableStatement();
                MySqlRenameTableStatement.Item item = new MySqlRenameTableStatement.Item();
                item.setName((SQLName) stmt.getTableSource().getExpr());
                item.setTo(((SQLAlterTableRename) stmt.getItems().get(0)).getToName());
                renameStmt.addItem(item);
                return renameStmt;
            }
        }

        return stmt;
    }

    /*
    protected SQLStatement parseAlterTableOld(boolean ignore) {
        lexer.nextToken();

        SQLAlterTableStatement stmt = new SQLAlterTableStatement(getDbType());
        stmt.setIgnore(ignore);
        stmt.setName(this.exprParser.name());

        for_:
        for (; ; ) {
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
            }

            switch (lexer.token()) {
                case DROP: {
                    parseAlterDrop(stmt);
                    break;
                }
                case TRUNCATE: {
                    lexer.nextToken();
                    accept(Token.PARTITION);

                    SQLAlterTableTruncatePartition item = new SQLAlterTableTruncatePartition();
                    if (lexer.token() == Token.ALL) {
                        item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                        lexer.nextToken();
                    } else {
                        this.exprParser.names(item.getPartitions(), item);
                    }
                    stmt.addItem(item);
                    break;
                }
                case ALTER: {
                    lexer.nextToken();
                    if (lexer.token() == Token.INDEX) {
                        lexer.nextToken();
                        MySqlAlterTableAlterFullTextIndex alterIndex = new MySqlAlterTableAlterFullTextIndex();
                        SQLName indexName = this.exprParser.name();
                        alterIndex.setIndexName(indexName);

                        accept(Token.SET);
                        accept(Token.FULLTEXT);

                        if (lexer.token() == Token.INDEX) {
                            lexer.nextToken();
                            alterIndex.setAnalyzerType(AnalyzerIndexType.INDEX);
                        } else if (lexer.identifierEquals(FnvHash.Constants.QUERY)) {
                            lexer.nextToken();
                            alterIndex.setAnalyzerType(AnalyzerIndexType.QUERY);
                        }

                        acceptIdentifier("ANALYZER");

                        accept(Token.EQ);

                        alterIndex.setAnalyzerName(this.exprParser.name());
                        stmt.addItem(alterIndex);
                    } else {
                        if (lexer.token() == Token.COLUMN) {
                            lexer.nextToken();
                        }

                        MySqlAlterTableAlterColumn alterColumn = new MySqlAlterTableAlterColumn();
                        alterColumn.setColumn(this.exprParser.name());

                        if (lexer.token() == Token.SET) {
                            lexer.nextToken();
                            accept(Token.DEFAULT);

                            alterColumn.setDefaultExpr(this.exprParser.expr());
                        } else {
                            accept(Token.DROP);
                            accept(Token.DEFAULT);
                            alterColumn.setDropDefault(true);
                        }

                        stmt.addItem(alterColumn);
                    }
                    break;
                }
                case DISABLE: {
                    lexer.nextToken();
                    if (lexer.token() == Token.CONSTRAINT) {
                        lexer.nextToken();
                        SQLAlterTableDisableConstraint item = new SQLAlterTableDisableConstraint();
                        item.setConstraintName(this.exprParser.name());
                        stmt.addItem(item);
                    } else {
                        acceptIdentifier("KEYS");
                        SQLAlterTableDisableKeys item = new SQLAlterTableDisableKeys();
                        stmt.addItem(item);
                    }
                    break;
                }
                case ENABLE: {
                    lexer.nextToken();
                    if (lexer.token() == Token.CONSTRAINT) {
                        lexer.nextToken();
                        SQLAlterTableEnableConstraint item = new SQLAlterTableEnableConstraint();
                        item.setConstraintName(this.exprParser.name());
                        stmt.addItem(item);
                    } else {
                        acceptIdentifier("KEYS");
                        SQLAlterTableEnableKeys item = new SQLAlterTableEnableKeys();
                        stmt.addItem(item);
                    }
                    break;
                }
                case DEFAULT: {
                    lexer.nextToken();
                    if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
                        SQLAlterCharacter item = alterTableCharacter();
                        stmt.addItem(item);
                    } else if (lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        SQLAlterCharacter item = new SQLAlterCharacter();
                        item.setCollate(this.exprParser.primary());
                        stmt.addItem(item);
                    } else {
                        throw new ParserException("TODO " + lexer.info());
                    }
                    continue for_;
                }
                case CHECK: {
                    lexer.nextToken();
                    accept(Token.PARTITION);

                    SQLAlterTableCheckPartition item = new SQLAlterTableCheckPartition();

                    if (lexer.token() == Token.ALL) {
                        lexer.nextToken();
                        item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                    } else {
                        this.exprParser.names(item.getPartitions(), item);
                    }

                    stmt.addItem(item);
                    break;
                }
                case OPTIMIZE: {
                    lexer.nextToken();

                    accept(Token.PARTITION);

                    SQLAlterTableOptimizePartition item = new SQLAlterTableOptimizePartition();

                    if (lexer.token() == Token.ALL) {
                        lexer.nextToken();
                        item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                    } else {
                        this.exprParser.names(item.getPartitions(), item);
                    }

                    stmt.addItem(item);
                    break;
                }
                case ANALYZE: {
                    lexer.nextToken();
                    accept(Token.PARTITION);

                    SQLAlterTableAnalyzePartition item = new SQLAlterTableAnalyzePartition();

                    if (lexer.token() == Token.ALL) {
                        lexer.nextToken();
                        item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                    } else {
                        this.exprParser.names(item.getPartitions(), item);
                    }
                    stmt.addItem(item);
                    break;
                }
                case COMMENT: {
                    lexer.nextToken();
                    if (lexer.token() == Token.EQ) {
                        accept(Token.EQ);
                    }
                    stmt.getTableOptions().put("COMMENT", this.exprParser.charExpr());
                    continue for_;
                }
                case UNION: {
                    lexer.nextToken();
                    if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                    }

                    accept(Token.LPAREN);
                    SQLTableSource tableSrc = this.createSQLSelectParser().parseTableSource();
                    stmt.getTableOptions().put("UNION", tableSrc);
                    accept(Token.RPAREN);
                    continue for_;
                }
                case SET: {
                    lexer.nextToken();

                    if (lexer.identifierEquals(FnvHash.Constants.RULE)) {
                        SQLAlterTableSetOption setOption = new SQLAlterTableSetOption();
                        SQLAssignItem item = this.exprParser.parseAssignItem();
                        setOption.addOption(item);
                        stmt.addItem(setOption);
                    } else {
                        acceptIdentifier("TBLPROPERTIES");
                        SQLAlterTableSetOption setOption = new SQLAlterTableSetOption();
                        accept(Token.LPAREN);
                        for (; ; ) {
                            SQLAssignItem item = this.exprParser.parseAssignItem();
                            setOption.addOption(item);
                            if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            }
                            break;
                        }
                        accept(Token.RPAREN);
                        stmt.addItem(setOption);

                        if (lexer.token() == Token.ON) {
                            lexer.nextToken();
                            SQLName on = this.exprParser.name();
                            setOption.setOn(on);
                        }
                    }

                    break;
                }
                default: {
                    if (lexer.identifierEquals(FnvHash.Constants.ADD)) {
                        lexer.nextToken();

                        if (lexer.token() == Token.COLUMN) {
                            lexer.nextToken();
                            parseAlterTableAddColumn(stmt);
                        } else if (lexer.token() == Token.INDEX
                                || lexer.token() == Token.FULLTEXT
                                || lexer.identifierEquals(FnvHash.Constants.SPATIAL)
                                || lexer.identifierEquals(FnvHash.Constants.CLUSTERED)
                                || lexer.identifierEquals(FnvHash.Constants.ANN)
                                || lexer.identifierEquals(FnvHash.Constants.GLOBAL)
                                || lexer.identifierEquals(FnvHash.Constants.LOCAL)) {
                            SQLAlterTableAddIndex item = parseAlterTableAddIndex();
                            item.setParent(stmt);
                            stmt.addItem(item);
                        } else if (lexer.token() == Token.UNIQUE) {
                            SQLAlterTableAddIndex item = parseAlterTableAddIndex();
                            item.setParent(stmt);
                            stmt.addItem(item);
                        } else if (lexer.token() == Token.PRIMARY) {
                            SQLPrimaryKey primaryKey = this.exprParser.parsePrimaryKey();
                            SQLAlterTableAddConstraint item = new SQLAlterTableAddConstraint(primaryKey);
                            stmt.addItem(item);
                        } else if (lexer.token() == Token.KEY) {
                            // throw new ParserException("TODO " + lexer.token() +
                            // " " + lexer.stringVal());
                            SQLAlterTableAddIndex item = parseAlterTableAddIndex();
                            item.setParent(stmt);
                            stmt.addItem(item);
                        } else if (lexer.identifierEquals(FnvHash.Constants.CLUSTERING)) {
                            SQLAlterTableAddClusteringKey item = parseAlterTableAddClusteringKey();
                            item.setParent(stmt);
                            stmt.addItem(item);
                        } else if (lexer.token() == Token.FOREIGN) {
                            MysqlForeignKey fk = this.getExprParser().parseForeignKey();
                            SQLAlterTableAddConstraint item = new SQLAlterTableAddConstraint(fk);

                            stmt.addItem(item);
                        } else if (lexer.token() == Token.CONSTRAINT) {
                            lexer.nextToken();

                            if (lexer.token() == Token.PRIMARY) {
                                SQLPrimaryKey primaryKey = ((MySqlExprParser) this.exprParser).parsePrimaryKey();
                                SQLAlterTableAddConstraint item = new SQLAlterTableAddConstraint(primaryKey);
                                item.setParent(stmt);

                                stmt.addItem(item);
                            } else if (lexer.token() == Token.FOREIGN) {
                                MysqlForeignKey fk = this.getExprParser().parseForeignKey();
                                fk.setHasConstraint(true);

                                SQLAlterTableAddConstraint item = new SQLAlterTableAddConstraint(fk);

                                stmt.addItem(item);
                            } else if (lexer.token() == Token.UNIQUE) {
                                SQLUnique unique = this.exprParser.parseUnique();
                                SQLAlterTableAddConstraint item = new SQLAlterTableAddConstraint(unique);
                                stmt.addItem(item);
                            } else {
                                SQLName constraintName = this.exprParser.name();

                                if (lexer.token() == Token.PRIMARY) {
                                    SQLPrimaryKey primaryKey = ((MySqlExprParser) this.exprParser).parsePrimaryKey();

                                    primaryKey.setName(constraintName);

                                    SQLAlterTableAddConstraint item = new SQLAlterTableAddConstraint(primaryKey);
                                    item.setParent(stmt);

                                    stmt.addItem(item);
                                } else if (lexer.token() == Token.FOREIGN) {
                                    MysqlForeignKey fk = this.getExprParser().parseForeignKey();
                                    fk.setName(constraintName);
                                    fk.setHasConstraint(true);

                                    SQLAlterTableAddConstraint item = new SQLAlterTableAddConstraint(fk);

                                    stmt.addItem(item);
                                } else if (lexer.token() == Token.UNIQUE) {
                                    SQLUnique unique = this.exprParser.parseUnique();
                                    SQLAlterTableAddConstraint item = new SQLAlterTableAddConstraint(unique);
                                    stmt.addItem(item);
                                } else {
                                    throw new ParserException("TODO " + lexer.info());
                                }
                            }
                        } else if (lexer.token() == Token.PARTITION) {
                            lexer.nextToken();

                            SQLAlterTableAddPartition item = new SQLAlterTableAddPartition();

                            if (lexer.identifierEquals("PARTITIONS")) {
                                lexer.nextToken();
                                item.setPartitionCount(this.exprParser.integerExpr());
                            }

                            if (lexer.token() == Token.LPAREN) {
                                lexer.nextToken();
                                SQLPartition partition = this.getExprParser().parsePartition();
                                accept(Token.RPAREN);
                                item.addPartition(partition);
                            }

                            stmt.addItem(item);
                        } else if (lexer.identifierEquals(FnvHash.Constants.EXTPARTITION)) {
                            lexer.nextToken();
                            accept(Token.LPAREN);
                            SQLAlterTableAddExtPartition extPartitionItem = new SQLAlterTableAddExtPartition();
                            MySqlExtPartition partitionDef = parseExtPartition();
                            extPartitionItem.setExPartition(partitionDef);
                            stmt.addItem(extPartitionItem);
                            accept(Token.RPAREN);
                        } else {
                            parseAlterTableAddColumn(stmt);
                        }
                    } else if (lexer.identifierEquals(FnvHash.Constants.CHANGE)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.COLUMN) {
                            lexer.nextToken();
                        }
                        MySqlAlterTableChangeColumn item = new MySqlAlterTableChangeColumn();
                        item.setColumnName(this.exprParser.name());
                        item.setNewColumnDefinition(this.exprParser.parseColumn());
                        if (lexer.identifierEquals("AFTER")) {
                            lexer.nextToken();
                            item.setAfterColumn(this.exprParser.name());
                        } else if (lexer.identifierEquals("FIRST")) {
                            lexer.nextToken();
                            if (lexer.token() == Token.IDENTIFIER) {
                                item.setFirstColumn(this.exprParser.name());
                            } else {
                                item.setFirst(true);
                            }
                        }
                        stmt.addItem(item);
                    } else if (lexer.identifierEquals(FnvHash.Constants.MODIFY)) {
                        lexer.nextToken();

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

                        boolean paren = false;
                        if (lexer.token() == Token.LPAREN) {
                            paren = true;
                            lexer.nextToken();
                        }

                        for (; ; ) {
                            MySqlAlterTableModifyColumn item = new MySqlAlterTableModifyColumn();
                            item.setNewColumnDefinition(this.exprParser.parseColumn());
                            if (lexer.identifierEquals("AFTER")) {
                                lexer.nextToken();
                                item.setAfterColumn(this.exprParser.name());
                            } else if (lexer.identifierEquals("FIRST")) {
                                lexer.nextToken();
                                if (lexer.token() == Token.IDENTIFIER) {
                                    item.setFirstColumn(this.exprParser.name());
                                } else {
                                    item.setFirst(true);
                                }
                            }
                            stmt.addItem(item);

                            if (paren && lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            }
                            break;
                        }

                        if (paren) {
                            accept(Token.RPAREN);
                        }
                    } else if (lexer.identifierEquals(FnvHash.Constants.RENAME)) {
                        lexer.nextToken();

                        if (lexer.token() == Token.INDEX) {
                            lexer.nextToken();
                            SQLName name = this.exprParser.name();
                            accept(Token.TO);
                            SQLName to = this.exprParser.name();
                            SQLAlterTableRenameIndex item = new SQLAlterTableRenameIndex(name, to);
                            stmt.addItem(item);
                            continue for_;
                        }

                        if (lexer.token() == Token.COLUMN) {
                            lexer.nextToken();

                            SQLName columnName = exprParser.name();
                            accept(Token.TO);
                            SQLName toName = this.exprParser.name();
                            SQLAlterTableRenameColumn renameColumn = new SQLAlterTableRenameColumn();

                            renameColumn.setColumn(columnName);
                            renameColumn.setTo(toName);
                            stmt.addItem(renameColumn);
                            continue for_;
                        }

                        if (lexer.token() == Token.TO || lexer.token() == Token.AS) {
                            lexer.nextToken();
                        }

                        if (stmt.getItems().size() > 0) {
                            SQLAlterTableRename item = new SQLAlterTableRename();
                            SQLName to = this.exprParser.name();
                            item.setTo(to);
                            stmt.addItem(item);
                        } else {
                            MySqlRenameTableStatement renameStmt = new MySqlRenameTableStatement();
                            MySqlRenameTableStatement.Item item = new MySqlRenameTableStatement.Item();
                            item.setName((SQLName) stmt.getTableSource().getExpr());
                            item.setTo(this.exprParser.name());
                            renameStmt.addItem(item);

                            if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();

                                SQLAlterTableRename alterItem = new SQLAlterTableRename();
                                alterItem.setTo(item.getTo());
                                stmt.addItem(alterItem);
                                continue;
                            } else {
                                // SQLAlterTableRename
                                return renameStmt;
                            }

                        }
                    } else if (lexer.token() == Token.ORDER) {
                        throw new ParserException("TODO " + lexer.info());
                    } else if (lexer.identifierEquals("CONVERT")) {
                        lexer.nextToken();
                        accept(Token.TO);
                        acceptIdentifier("CHARACTER");
                        accept(Token.SET);

                        SQLAlterTableConvertCharSet item = new SQLAlterTableConvertCharSet();
                        SQLExpr charset = this.exprParser.primary();
                        item.setCharset(charset);

                        if (lexer.identifierEquals("COLLATE")) {
                            lexer.nextToken();
                            SQLExpr collate = this.exprParser.primary();
                            item.setCollate(collate);
                        }

                        stmt.addItem(item);
                    } else if (lexer.identifierEquals(FnvHash.Constants.DISCARD)) {
                        lexer.nextToken();

                        if (lexer.token() == Token.PARTITION) {
                            lexer.nextToken();
                            SQLAlterTableDiscardPartition item = new SQLAlterTableDiscardPartition();

                            if (lexer.token() == Token.ALL) {
                                lexer.nextToken();
                                item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                            } else {
                                this.exprParser.names(item.getPartitions(), item);
                            }

                            if (lexer.token() == Token.TABLESPACE) {
                                lexer.nextToken();
                                item.setTablespace(true);
                            }

                            stmt.addItem(item);
                        } else {
                            accept(Token.TABLESPACE);
                            MySqlAlterTableDiscardTablespace item = new MySqlAlterTableDiscardTablespace();
                            stmt.addItem(item);
                        }

                    } else if (lexer.identifierEquals("IMPORT")) {
                        lexer.nextToken();

                        if (lexer.token() == Token.PARTITION) {
                            lexer.nextToken();
                            SQLAlterTableImportPartition item = new SQLAlterTableImportPartition();

                            if (lexer.token() == Token.ALL) {
                                lexer.nextToken();
                                item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                            } else {
                                this.exprParser.names(item.getPartitions(), item);
                            }

                            stmt.addItem(item);
                        } else {
                            accept(Token.TABLESPACE);
                            MySqlAlterTableImportTablespace item = new MySqlAlterTableImportTablespace();
                            stmt.addItem(item);
                        }
                    } else if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
                        throw new ParserException("TODO " + lexer.info());
                    } else if (lexer.identifierEquals("COALESCE")) {
                        lexer.nextToken();
                        accept(Token.PARTITION);

                        SQLAlterTableCoalescePartition item = new SQLAlterTableCoalescePartition();
                        SQLIntegerExpr countExpr = this.exprParser.integerExpr();
                        item.setCount(countExpr);
                        stmt.addItem(item);
                    } else if (lexer.identifierEquals("REORGANIZE")) {
                        lexer.nextToken();
                        accept(Token.PARTITION);

                        SQLAlterTableReOrganizePartition item = new SQLAlterTableReOrganizePartition();

                        this.exprParser.names(item.getNames(), item);

                        accept(Token.INTO);
                        accept(Token.LPAREN);
                        for (; ; ) {
                            SQLPartition partition = this.getExprParser().parsePartition();

                            item.addPartition(partition);

                            if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            } else {
                                break;
                            }
                        }
                        accept(Token.RPAREN);
                        stmt.addItem(item);
                    } else if (lexer.identifierEquals(FnvHash.Constants.EXCHANGE)) {
                        lexer.nextToken();
                        accept(Token.PARTITION);

                        SQLAlterTableExchangePartition item = new SQLAlterTableExchangePartition();

                        SQLName partition = this.exprParser.name();
                        item.addPartition(partition);

                        accept(Token.WITH);
                        accept(Token.TABLE);
                        SQLName table = this.exprParser.name();
                        item.setTable(table);

                        if (lexer.token() == Token.WITH) {
                            lexer.nextToken();
                            acceptIdentifier("VALIDATION");
                            item.setValidation(true);
                        } else if (lexer.identifierEquals(FnvHash.Constants.WITHOUT)) {
                            lexer.nextToken();
                            acceptIdentifier("VALIDATION");
                            item.setValidation(false);
                        }

                        stmt.addItem(item);
                    } else if (lexer.identifierEquals("REBUILD")) {
                        lexer.nextToken();

                        accept(Token.PARTITION);

                        SQLAlterTableRebuildPartition item = new SQLAlterTableRebuildPartition();

                        if (lexer.token() == Token.ALL) {
                            lexer.nextToken();
                            item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                        } else {
                            this.exprParser.names(item.getPartitions(), item);
                        }

                        stmt.addItem(item);
                    } else if (lexer.identifierEquals("REPAIR")) {
                        lexer.nextToken();

                        accept(Token.PARTITION);

                        SQLAlterTableRepairPartition item = new SQLAlterTableRepairPartition();

                        if (lexer.token() == Token.ALL) {
                            lexer.nextToken();
                            item.getPartitions().add(new SQLIdentifierExpr("ALL"));
                        } else {
                            this.exprParser.names(item.getPartitions(), item);
                        }

                        stmt.addItem(item);
                    } else if (lexer.identifierEquals(FnvHash.Constants.PARTITIONS)) {
                        SQLAlterTablePartitionCount item = new SQLAlterTablePartitionCount();
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        item.setCount((SQLIntegerExpr) exprParser.integerExpr());
                        stmt.addItem(item);
                    } else if (lexer.token() == Token.PARTITION) {
                        lexer.nextToken();
                        if(lexer.identifierEquals(FnvHash.Constants.LIFECYCLE)) {
                            lexer.nextToken();
                            SQLAlterTablePartitionLifecycle item = new SQLAlterTablePartitionLifecycle();
                            if (lexer.token() == Token.EQ) {
                                lexer.nextToken();
                            }
                            item.setLifecycle((SQLIntegerExpr) exprParser.integerExpr());
                            stmt.addItem(item);
                        }
                    } else if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITION)) {
                        lexer.nextToken();
                        if (lexer.identifierEquals(FnvHash.Constants.LIFECYCLE)) {
                            lexer.nextToken();
                            SQLAlterTableSubpartitionLifecycle item = new SQLAlterTableSubpartitionLifecycle();
                            if (lexer.token() == Token.LITERAL_INT) {
                                for (; ; ) {
                                    item.getPartitionIds().add(this.exprParser.integerExpr());
                                    String pidStr = lexer.stringVal();
                                    accept(Token.VARIANT);
                                    String s = pidStr.replaceAll(":", "");
                                    if (StringUtils.isEmpty(s)) {
                                        item.getSubpartitionLifeCycle().add(exprParser.integerExpr());
                                    } else {
                                        item.getSubpartitionLifeCycle().add(new SQLIntegerExpr(Integer.valueOf(s)));
                                    }

                                    if (lexer.token() == Token.COMMA) {
                                        lexer.nextToken();
                                        continue;
                                    }

                                    break;
                                }
                            }
                            stmt.addItem(item);
                        }
                    } else if (lexer.identifierEquals("BLOCK_SIZE")) {
                        SQLAlterTableBlockSize item = new SQLAlterTableBlockSize();
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            accept(Token.EQ);
                        }
                        item.setSize((SQLIntegerExpr) exprParser.expr());
                        stmt.addItem(item);
                    } else if (lexer.identifierEquals("COMPRESSION")) {
                        SQLAlterTableCompression item = new SQLAlterTableCompression();
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        item.setName(exprParser.charExpr());
                        stmt.addItem(item);
                        continue for_;
                    } else if (lexer.identifierEquals("REMOVE")) {
                        lexer.nextToken();
                        acceptIdentifier("PARTITIONING");
                        stmt.setRemovePatiting(true);
                    } else if (lexer.identifierEquals("UPGRADE")) {
                        lexer.nextToken();
                        acceptIdentifier("PARTITIONING");
                        stmt.setUpgradePatiting(true);
                    } else if (lexer.identifierEquals("ALGORITHM")) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.addItem(new MySqlAlterTableOption("ALGORITHM", lexer.stringVal()));
                        lexer.nextToken();
                        continue for_;
                    } else if (lexer.identifierEquals(ENGINE)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(ENGINE, this.exprParser.primary());
                        continue for_;
                    } else if (lexer.identifierEquals(AUTO_INCREMENT)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(AUTO_INCREMENT, this.exprParser.integerExpr());
                        continue for_;
                    } else if (lexer.identifierEquals(AVG_ROW_LENGTH)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(AVG_ROW_LENGTH, this.exprParser.integerExpr());
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.CHECKSUM)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(CHECKSUM2, this.exprParser.integerExpr());
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.CONNECTION)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(CONNECTION, this.exprParser.charExpr());
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.DATA) ||
                            lexer.token() == Token.INDEX) {
                        boolean isIndex = lexer.token() == Token.INDEX;
                        lexer.nextToken();
                        acceptIdentifier("DIRECTORY");
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(
                                isIndex ? "INDEX DIRECTORY" : "DATA DIRECTORY",
                                this.exprParser.primary());
                        continue for_;
                    } else if (lexer.identifierEquals(DELAY_KEY_WRITE)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(DELAY_KEY_WRITE, this.exprParser.integerExpr());
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.ENCRYPTION)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(ENCRYPTION2, this.exprParser.primary());
                        continue for_;
                    } else if (lexer.identifierEquals(INSERT_METHOD)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(INSERT_METHOD, this.exprParser.primary());
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.KEY_BLOCK_SIZE)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(KEY_BLOCK_SIZE2, this.exprParser.integerExpr());
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.MAX_ROWS)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(MAX_ROWS2, this.exprParser.integerExpr());
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.MIN_ROWS)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(MIN_ROWS2, this.exprParser.integerExpr());
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.PASSWORD)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(PASSWORD2, this.exprParser.primary());
                        continue for_;
                    } else if (lexer.identifierEquals(STATS_AUTO_RECALC)) {
                        lexer.nextToken();
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(STATS_AUTO_RECALC, this.exprParser.primary());
                        continue for_;
                    } else if (lexer.identifierEquals(STATS_PERSISTENT)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(STATS_PERSISTENT, this.exprParser.primary());
                        continue for_;
                    } else if (lexer.identifierEquals(STATS_SAMPLE_PAGES)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        stmt.getTableOptions().put(STATS_SAMPLE_PAGES, this.exprParser.integerExpr());
                        continue for_;
                    } else if (lexer.token() == Token.TABLESPACE) {
                        lexer.nextToken();
                        MySqlCreateTableStatement.TableSpaceOption option = new MySqlCreateTableStatement.TableSpaceOption();
                        option.setName(this.exprParser.name());
                        if (lexer.identifierEquals("STORAGE")) {
                            lexer.nextToken();
                            option.setStorage(this.exprParser.name());
                        }
                        stmt.addItem(new MySqlAlterTableOption(TABLESPACE2, option));
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        SQLAlterCharacter item = new SQLAlterCharacter();
                        item.setCollate(this.exprParser.primary());
                        stmt.addItem(item);
                        continue for_;
                    } else if (lexer.identifierEquals("PACK_KEYS")) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        if (lexer.identifierEquals("PACK")) {
                            lexer.nextToken();
                            accept(Token.ALL);
                            stmt.getTableOptions().put("PACK_KEYS", new SQLIdentifierExpr("PACK ALL"));
                        } else {
                            stmt.getTableOptions().put("PACK_KEYS", this.exprParser.primary());
                        }
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
                        SQLAlterCharacter item = alterTableCharacter();
                        stmt.addItem(item);
                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.CLUSTERED)) {
                        SQLAlterTableModifyClusteredBy clusteredBy = new SQLAlterTableModifyClusteredBy();

                        acceptIdentifier("CLUSTERED");
                        accept(Token.BY);
                        accept(Token.LPAREN);

                        // for ads: ALTER TABLE SCHEMA1.TABLE1 CLUSTERED BY ();
                        if (lexer.token() != Token.RPAREN) {
                            for (; ; ) {
                                clusteredBy.addClusterColumn(this.exprParser.name());

                                if (lexer.token() == Token.COMMA) {
                                    accept(Token.COMMA);
                                    continue;
                                }
                                break;
                            }
                        }

                        accept(Token.RPAREN);

                        stmt.addItem(clusteredBy);
                    } else if (lexer.identifierEquals("ROW_FORMAT")) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }

                        if (lexer.token() == Token.DEFAULT || lexer.token() == Token.IDENTIFIER) {
                            SQLIdentifierExpr rowFormat = new SQLIdentifierExpr(lexer.stringVal());
                            lexer.nextToken();
                            stmt.getTableOptions().put("ROW_FORMAT", rowFormat);
                        } else {
                            throw new ParserException("illegal syntax. " + lexer.info());
                        }

                        continue for_;
                    } else if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITION_AVAILABLE_PARTITION_NUM)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        SQLIntegerExpr num = this.exprParser.integerExpr();
                        SQLAlterTableSubpartitionAvailablePartitionNum item = new SQLAlterTableSubpartitionAvailablePartitionNum();
                        item.setNumber(num);
                        stmt.addItem(item);
                    } else if (lexer.token() == Token.LOCK) {
                        lexer.nextToken();
                        if (lexer.token() == Token.EQ) {
                            lexer.nextToken();
                        }
                        SQLIdentifierExpr rowFormat = new SQLIdentifierExpr(lexer.stringVal());
                        lexer.nextToken();
                        stmt.getTableOptions().put("LOCK", rowFormat);
                    } else {
                        break for_;
                    }
                    break;
                }
            }

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            } else {
                break;
            }

        }

        return stmt;
    }
    */

    private MySqlExtPartition parseExtPartition() {
        MySqlExtPartition partitionDef = new MySqlExtPartition();
        for (; ; ) {
            MySqlExtPartition.Item item = new MySqlExtPartition.Item();

            if (lexer.identifierEquals(FnvHash.Constants.DBPARTITION)) {
                lexer.nextToken();
                SQLName name = this.exprParser.name();
                item.setDbPartition(name);
                accept(Token.BY);
                SQLExpr value = this.exprParser.primary();
                item.setDbPartitionBy(value);
            }

            if (lexer.identifierEquals(FnvHash.Constants.TBPARTITION)) {
                lexer.nextToken();
                SQLName name = this.exprParser.name();
                item.setTbPartition(name);
                accept(Token.BY);
                SQLExpr value = this.exprParser.primary();
                item.setTbPartitionBy(value);
            }

            item.setParent(partitionDef);
            partitionDef.getItems().add(item);

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            } else {
                break;
            }
        }

        return partitionDef;
    }

    private SQLAlterCharacter alterTableCharacter() {
        lexer.nextToken();
        accept(Token.SET);
        if (lexer.token() == Token.EQ) {
            lexer.nextToken();
        }
        SQLAlterCharacter item = new SQLAlterCharacter();
        item.setCharacterSet(this.exprParser.primary());
        if (lexer.token() == Token.COMMA) {
            lexer.nextToken();
            if (lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                item.setCollate(this.exprParser.primary());
            }
        }
        return item;
    }

    protected void parseAlterTableAddColumn(SQLAlterTableStatement stmt) {
        boolean parenFlag = false;
        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            parenFlag = true;
        }

        SQLAlterTableAddColumn item = new SQLAlterTableAddColumn();
        for (; ; ) {
            SQLColumnDefinition columnDef = this.exprParser.parseColumn();
            item.addColumn(columnDef);
            if (lexer.identifierEquals("AFTER")) {
                lexer.nextToken();
                item.setAfterColumn(this.exprParser.name());
            } else if (lexer.identifierEquals("FIRST")) {
                lexer.nextToken();
                if (lexer.token() == Token.IDENTIFIER) {
                    item.setFirstColumn(this.exprParser.name());
                } else {
                    item.setFirst(true);
                }
            }

            if (parenFlag && lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }

            break;
        }

        if (parenFlag) {
            accept(Token.RPAREN);
        }

        if (lexer.identifierEquals(FnvHash.Constants.RESTRICT)) {
            lexer.nextToken();
            item.setRestrict(true);
        } else if (lexer.token() == Token.CASCADE || lexer.identifierEquals(FnvHash.Constants.CASCADE)) {
            lexer.nextToken();
            item.setCascade(true);
        } else {
            item.setCascade(false);
        }

        stmt.addItem(item);
    }

    public void parseAlterDrop(SQLAlterTableStatement stmt) {
        lexer.nextToken();
        if (lexer.token() == INDEX) {
            lexer.nextToken();
            SQLName indexName = this.exprParser.name();
            SQLAlterTableDropIndex item = new SQLAlterTableDropIndex();
            item.setIndexName(indexName);
            stmt.addItem(item);
        } else if (lexer.token() == Token.FOREIGN) {
            lexer.nextToken();
            accept(Token.KEY);
            SQLName indexName = this.exprParser.name();
            SQLAlterTableDropForeignKey item = new SQLAlterTableDropForeignKey();
            item.setIndexName(indexName);
            stmt.addItem(item);
        } else if (lexer.token() == Token.KEY) {
            lexer.nextToken();
            SQLName keyName = this.exprParser.name();
            SQLAlterTableDropKey item = new SQLAlterTableDropKey();
            item.setKeyName(keyName);
            stmt.addItem(item);
        } else if (lexer.token() == Token.PRIMARY) {
            lexer.nextToken();
            accept(Token.KEY);
            SQLAlterTableDropPrimaryKey item = new SQLAlterTableDropPrimaryKey();
            stmt.addItem(item);
        } else if (lexer.token() == Token.CONSTRAINT) {
            lexer.nextToken();
            SQLAlterTableDropConstraint item = new SQLAlterTableDropConstraint();
            item.setConstraintName(this.exprParser.name());
            stmt.addItem(item);
        } else if (lexer.token() == Token.CHECK) {
            lexer.nextToken();
            SQLAlterTableDropCheck item = new SQLAlterTableDropCheck();
            item.setCheckName(this.exprParser.name());
            stmt.addItem(item);
        } else if (lexer.token() == Token.COLUMN) {
            lexer.nextToken();
            SQLAlterTableDropColumnItem item = new SQLAlterTableDropColumnItem();
            if (lexer.token() == Token.IF) {
                lexer.nextToken();
                accept(Token.EXISTS);
                item.setIfExists(true);
            }
            SQLName name = exprParser.name();
            name.setParent(item);
            item.addColumn(name);

            if (dbType != DbType.mysql) {
                while (lexer.token() == Token.COMMA) {
                    Lexer.SavePoint savePoint = lexer.markOut();
                    lexer.nextToken();
                    if (lexer.identifierEquals(FnvHash.Constants.CHANGE)
                            || lexer.identifierEquals(FnvHash.Constants.MODIFY)) {
                        lexer.reset(savePoint);
                        break;
                    }

                    if (lexer.token() == Token.IDENTIFIER) {
                        if ("ADD".equalsIgnoreCase(lexer.stringVal())) {
                            lexer.reset(savePoint);
                            break;
                        }
                        name = exprParser.name();
                        name.setParent(item);
                        item.addColumn(name);
                    } else {
                        lexer.reset(savePoint);
                        break;
                    }
                }
            }

            stmt.addItem(item);
        } else if (lexer.token() == Token.PARTITION) {
            SQLAlterTableDropPartition dropPartition = parseAlterTableDropPartition(false);
            stmt.addItem(dropPartition);
        } else if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITION)) {
            SQLAlterTableDropSubpartition dropPartition = parseAlterTableDropSubpartition();
            stmt.addItem(dropPartition);
        } else if (lexer.identifierEquals(FnvHash.Constants.CLUSTERING) || lexer.identifierEquals(FnvHash.Constants.CLUSTERED)) {
            lexer.nextToken();
            SQLAlterTableDropClusteringKey dropPartition = new SQLAlterTableDropClusteringKey();
            accept(Token.KEY);
            dropPartition.setKeyName(exprParser.name());
            stmt.addItem(dropPartition);
        } else if (lexer.token() == Token.IDENTIFIER) {
            if (lexer.identifierEquals(FnvHash.Constants.EXTPARTITION)) {
                lexer.nextToken();
                accept(Token.LPAREN);
                SQLAlterTableDropExtPartition extPartitionItem = new SQLAlterTableDropExtPartition();
                MySqlExtPartition partitionDef = parseExtPartition();
                extPartitionItem.setExPartition(partitionDef);
                stmt.addItem(extPartitionItem);
                accept(Token.RPAREN);
            } else {
                SQLAlterTableDropColumnItem item = new SQLAlterTableDropColumnItem();

                SQLName name = this.exprParser.name();
                item.addColumn(name);
                stmt.addItem(item);

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

                if (lexer.token() == Token.DROP) {
                    parseAlterDrop(stmt);
                }
            }
        } else {
            super.parseAlterDrop(stmt);
        }
    }

    public SQLStatement parseRename() {
        acceptIdentifier("RENAME");

        if (lexer.token() == Token.SEQUENCE) {
            lexer.nextToken();
            MySqlRenameSequenceStatement stmt = new MySqlRenameSequenceStatement();

            SQLName name = this.exprParser.name();
            stmt.setName(name);

            accept(Token.TO);

            SQLName to = this.exprParser.name();
            stmt.setTo(to);

            return stmt;
        }
        if (lexer.token() == Token.USER) {
            lexer.nextToken();
            SQLRenameUserStatement stmt = new SQLRenameUserStatement();

            SQLName name = this.exprParser.name();
            stmt.setName(name);

            accept(Token.TO);

            SQLName to = this.exprParser.name();
            stmt.setTo(to);

            return stmt;
        }

        accept(TABLE);
        MySqlRenameTableStatement stmt = new MySqlRenameTableStatement();

        for (; ; ) {
            MySqlRenameTableStatement.Item item = new MySqlRenameTableStatement.Item();
            item.setName(this.exprParser.name());
            accept(Token.TO);
            item.setTo(this.exprParser.name());

            stmt.addItem(item);

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }

            break;
        }

        return stmt;
    }

    public SQLStatement parseCreateSchema() {
        return parseCreateDatabase();
    }

    public SQLStatement parseCreateDatabase() {
        if (lexer.token() == Token.CREATE) {
            lexer.nextToken();
        }

        if (lexer.token() == Token.SCHEMA) {
            lexer.nextToken();
        } else {
            accept(Token.DATABASE);
        }

        SQLCreateDatabaseStatement stmt = new SQLCreateDatabaseStatement(dbType);

        if (lexer.token() == Token.HINT) {
            List<SQLCommentHint> hints = this.exprParser.parseHints();
            if (hints.size() == 1) {
                String text = hints.get(0).getText();
                if (text.charAt(0) == '!') {
                    String[] words = text.trim().split("\\s+");
                    if (words.length > 2
                            && words[words.length - 3].equalsIgnoreCase("IF")
                            && words[words.length - 2].equalsIgnoreCase("NOT")
                            && words[words.length - 1].equalsIgnoreCase("EXISTS")) {
                            stmt.setIfNotExists(true);
                    }
                }
            }
        }

        if (lexer.token() == Token.IF) {
            lexer.nextToken();
            accept(Token.NOT);
            accept(Token.EXISTS);
            stmt.setIfNotExists(true);
        }

        stmt.setName(this.exprParser.name());

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

        if (lexer.token() == Token.HINT) {
            List<SQLCommentHint> hints = this.exprParser.parseHints();
            if (hints.size() == 1) {
                String text = hints.get(0).getText();
                if (text.charAt(0) == '!') {
                    String[] words = text.trim().split("\\s+");
                    int idx = 0;
                    for (; idx < words.length; idx++) {
                        if (words[idx].equalsIgnoreCase("CHARACTER")
                                && idx < words.length - 2 && words[idx + 1].equalsIgnoreCase("SET")) {
                            stmt.setCharacterSet(words[idx + 2]);
                            idx += 2;
                        } else if (words[idx].equalsIgnoreCase("COLLATE")
                                && idx < words.length - 1) {
                            stmt.setCollate(words[idx + 1]);
                            idx += 1;
                        }
                    }
                }
            }
        }

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

        for (; ; ) {
            if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
                lexer.nextToken();
                accept(Token.SET);
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                String charset = lexer.stringVal();
                accept(Token.IDENTIFIER);
                stmt.setCharacterSet(charset);
            } else if (lexer.identifierEquals(FnvHash.Constants.CHARSET)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                String charset = lexer.stringVal();
                accept(Token.IDENTIFIER);
                stmt.setCharacterSet(charset);
            } else if (lexer.token() == Token.DEFAULT) {
                lexer.nextToken();
            } else if (lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                String collate = lexer.stringVal();
                accept(Token.IDENTIFIER);
                stmt.setCollate(collate);
            } else if (lexer.identifierEquals(FnvHash.Constants.PASSWORD)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr password = this.exprParser.primary();
                stmt.setPassword(password);
            }
            //ads 特殊支持
            else if (lexer.identifierEquals("SHARDS") || lexer.identifierEquals("SHARD_ID")
                    || lexer.identifierEquals("REPLICATION") || lexer.identifierEquals("STORAGE_DEPENDENCY")
                    || lexer.identifierEquals("REPLICA_TYPE") || lexer.identifierEquals("DATA_REPLICATION")) {
                String key = lexer.stringVal();
                lexer.nextToken();
                accept(Token.EQ);
                SQLExpr value = this.exprParser.expr();
                stmt.getOptions().put(key, value);
            } else {
                break;
            }
        }

        if (lexer.token() == Token.FOR) {
            lexer.nextToken();
            String user = lexer.stringVal();
            lexer.nextToken();
            stmt.setUser(user);
        }

        if (lexer.identifierEquals(FnvHash.Constants.OPTIONS)) {
            lexer.nextToken();
            accept(Token.LPAREN);
            for (; ; ) {
                if (lexer.token() == Token.RPAREN) {
                    accept(Token.RPAREN);
                    break;
                }
                String key = lexer.stringVal();
                lexer.nextToken();
                accept(Token.EQ);
                SQLExpr value = this.exprParser.expr();
                stmt.getOptions().put(key, value);
            }
        }

        if (lexer.token() == WITH) {
            lexer.nextToken();

            accept(Token.LPAREN);
            for (; ; ) {
                SQLAssignItem assignItem = this.exprParser.parseAssignItem();
                assignItem.setParent(stmt);
                stmt.getDbProperties().add(assignItem);

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
        }

        if (lexer.identifierEquals(FnvHash.Constants.STORED)) {
            lexer.nextToken();

            if (lexer.token() == Token.BY) {
                accept(Token.BY);

                for (; ; ) {
                    List<SQLAssignItem> storedByItem = new ArrayList<SQLAssignItem>();
                    accept(Token.LPAREN);
                    for (; ; ) {
                        SQLAssignItem assignItem = this.exprParser.parseAssignItem();
                        assignItem.setParent(stmt);
                        storedByItem.add(assignItem);

                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    accept(Token.RPAREN);
                    stmt.getStoredBy().add(storedByItem);

                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }

                    break;
                }
            } else if (lexer.token() == Token.IN) {
                lexer.nextToken();

                stmt.setStoredIn(this.exprParser.name());

                accept(Token.ON);
                accept(Token.LPAREN);
                for (; ; ) {
                    SQLAssignItem assignItem = this.exprParser.parseAssignItem();
                    assignItem.setParent(stmt);
                    stmt.getStoredOn().add(assignItem);

                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
                accept(Token.RPAREN);
            } else if (lexer.token() == Token.AS) {
                lexer.nextToken();
                SQLExpr like = this.exprParser.expr();
                stmt.setStoredAs(like);
            } else {
                throw new ParserException("TODO " + lexer.info());
            }
        }

        return stmt;
    }

    protected void parseUpdateSet(SQLUpdateStatement update) {
        accept(Token.SET);

        for (; ; ) {
            SQLUpdateSetItem item = this.exprParser.parseUpdateSetItem();
            update.addItem(item);

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

            lexer.nextToken();
        }
    }

    protected SQLStatement alterSchema() {
        return alterDatabase();
    }

    public SQLStatement alterDatabase() {
        lexer.nextIf(ALTER);

        if (lexer.token() == Token.SCHEMA) {
            lexer.nextToken();
        } else {
            accept(Token.DATABASE);
        }

        SQLAlterDatabaseStatement stmt = new SQLAlterDatabaseStatement(dbType);

        SQLName name = this.exprParser.name();
        stmt.setName(name);

        if (lexer.token() == Token.SET) {
            lexer.nextToken();
            MySqlAlterDatabaseSetOption option = new MySqlAlterDatabaseSetOption();
            for (; ; ) {
                SQLName key = this.exprParser.name();
                accept(Token.EQ);
                SQLExpr value = this.exprParser.expr();

                option.getOptions().add(new SQLAssignItem(key, value));

                if (lexer.token() == Token.EOF || lexer.token() == Token.ON) {
                    break;
                }

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                }
            }
            stmt.setItem(option);

            if (lexer.token() == Token.ON) {
                lexer.nextToken();
                SQLName on = this.exprParser.name();
                option.setOn(on);
            }

            return stmt;
        }

        if (lexer.token() == Token.KILL) {
            MySqlAlterDatabaseKillJob item = new MySqlAlterDatabaseKillJob();
            lexer.nextToken();
            SQLName jobType = this.exprParser.name();
            SQLName jobId = this.exprParser.name();
            item.setJobType(jobType);
            item.setJobId(jobId);

            stmt.setItem(item);
        }

        if (lexer.identifierEquals("UPGRADE")) {
            lexer.nextToken();
            acceptIdentifier("DATA");
            acceptIdentifier("DIRECTORY");
            acceptIdentifier("NAME");
            stmt.setUpgradeDataDirectoryName(true);
        }

        if (lexer.token() == Token.DEFAULT) {
            lexer.nextToken();
            if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
                SQLAlterCharacter item = alterTableCharacter();
                stmt.setCharacter(item);
            } else if (lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLAlterCharacter character = new SQLAlterCharacter();
                character.setCollate(this.exprParser.primary());
                stmt.setCharacter(character);
            } else {
                throw new ParserException("TODO " + lexer.info());
            }
        } else if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
            SQLAlterCharacter item = alterTableCharacter();
            stmt.setCharacter(item);
        } else if (lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLAlterCharacter character = new SQLAlterCharacter();
            character.setCollate(this.exprParser.primary());
            stmt.setCharacter(character);
        }

        return stmt;
    }

    public MySqlAlterUserStatement alterUser() {
        accept(Token.USER);

        MySqlAlterUserStatement stmt = new MySqlAlterUserStatement();

        if (lexer.token() == Token.IF) {
            lexer.nextToken();
            accept(Token.EXISTS);
            stmt.setIfExists(true);
        }

        for (; ; ) {
            MySqlAlterUserStatement.AlterUser alterUser = new MySqlAlterUserStatement.AlterUser();

            SQLExpr user = this.exprParser.expr();
            alterUser.setUser(user);
            if (lexer.identifierEquals("ACCOUNT")) {
                lexer.nextToken();
                alterUser.setAccountLockOption(lexer.stringVal());
                lexer.nextToken();
            }
            if (lexer.identifierEquals("IDENTIFIED")) {
                lexer.nextToken();
                accept(Token.BY);

                MySqlAlterUserStatement.AuthOption authOption = new MySqlAlterUserStatement.AuthOption();
                SQLCharExpr authString = this.exprParser.charExpr();
                authOption.setAuthString(authString);

                alterUser.setAuthOption(authOption);
            }

            if (lexer.identifierEquals("PASSWORD")) {
                lexer.nextToken();
                if (lexer.identifierEquals("EXPIRE")) {
                    lexer.nextToken();

                    MySqlAlterUserStatement.PasswordOption passwordOption = new MySqlAlterUserStatement.PasswordOption();

                    if (lexer.token() == Token.DEFAULT) {
                        lexer.nextToken();
                        passwordOption.setExpire(MySqlAlterUserStatement.PasswordExpire.PASSWORD_EXPIRE_DEFAULT);
                    } else if (lexer.identifierEquals("NEVER")) {
                        lexer.nextToken();
                        passwordOption.setExpire(MySqlAlterUserStatement.PasswordExpire.PASSWORD_EXPIRE_NEVER);
                    } else if (lexer.token() == Token.INTERVAL) {
                        lexer.nextToken();
                        passwordOption.setExpire(MySqlAlterUserStatement.PasswordExpire.PASSWORD_EXPIRE_INTERVAL);
                        SQLIntegerExpr days = this.exprParser.integerExpr();
                        passwordOption.setIntervalDays(days);
                        acceptIdentifier("DAY");
                    } else {
                        passwordOption.setExpire(MySqlAlterUserStatement.PasswordExpire.PASSWORD_EXPIRE);
                    }
                    stmt.setPasswordOption(passwordOption);
                }
            }

            stmt.getAlterUsers().add(alterUser);

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }

            break;
        }
        return stmt;
    }

    @Override
    public MySqlExprParser getExprParser() {
        return (MySqlExprParser) exprParser;
    }

    public SQLCreateFunctionStatement parseCreateFunction() {
        SQLCreateFunctionStatement stmt = new SQLCreateFunctionStatement();
        stmt.setDbType(dbType);

        if (lexer.token() == Token.CREATE) {
            lexer.nextToken();

            if (lexer.token() == Token.OR) {
                lexer.nextToken();
                accept(Token.REPLACE);
                stmt.setOrReplace(true);
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.DEFINER)) {
            lexer.nextToken();
            accept(Token.EQ);
            SQLName definer = this.getExprParser().userName();
            stmt.setDefiner(definer);
        }

        accept(Token.FUNCTION);

        stmt.setName(this.exprParser.name());

        if (lexer.token() == Token.LPAREN) { // match "("
            lexer.nextToken();
            parserParameters(stmt.getParameters(), stmt);
            accept(Token.RPAREN); // match ")"
        }

        acceptIdentifier("RETURNS");
        SQLDataType dataType = this.exprParser.parseDataType();
        stmt.setReturnDataType(dataType);

        for (; ; ) {
            if (lexer.identifierEquals("DETERMINISTIC")) {
                lexer.nextToken();
                stmt.setDeterministic(true);
                continue;
            }

            if (lexer.identifierEquals("DETERMINISTIC")) {
                lexer.nextToken();
                stmt.setDeterministic(true);
                continue;
            }

            if (lexer.token() == Token.COMMENT) {
                lexer.nextToken();
                stmt.setComment(lexer.stringVal());
                lexer.nextToken();
                continue;
            }

            if (lexer.identifierEquals("LANGUAGE")) {
                lexer.nextToken();
                stmt.setLanguage(lexer.stringVal());
                lexer.nextToken();
                continue;
            }

            break;
        }

        SQLStatement block;
        if (lexer.token() == Token.BEGIN) {
            block = this.parseBlock();
        } else {
            block = this.parseStatement();
        }

        stmt.setBlock(block);

        return stmt;
    }

    /**
     * parse create procedure statement
     */
    public SQLCreateProcedureStatement parseCreateProcedure() {
        /**
         * CREATE OR REPALCE PROCEDURE SP_NAME(parameter_list) BEGIN block_statement END
         */
        SQLCreateProcedureStatement stmt = new SQLCreateProcedureStatement();
        stmt.setDbType(dbType);

        if (lexer.token() == Token.CREATE) {
            lexer.nextToken();

            if (lexer.token() == Token.OR) {
                lexer.nextToken();
                accept(Token.REPLACE);
                stmt.setOrReplace(true);
            }
        }

        if (lexer.identifierEquals(FnvHash.Constants.DEFINER)) {
            lexer.nextToken();
            accept(Token.EQ);
            SQLName definer = this.getExprParser().userName();
            stmt.setDefiner(definer);
        }

        accept(Token.PROCEDURE);

        stmt.setName(this.exprParser.name());

        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            parserParameters(stmt.getParameters(), stmt);
            accept(Token.RPAREN);
        }

        for (; ; ) {
            if (lexer.token() == Token.COMMENT) {
                lexer.nextToken();
                stmt.setComment(this.exprParser.charExpr());
            }
            if (lexer.identifierEquals(FnvHash.Constants.LANGUAGE)) {
                lexer.nextToken();
                acceptIdentifier("SQL");
                stmt.setLanguageSql(true);
            }

            if (lexer.identifierEquals(FnvHash.Constants.DETERMINISTIC)) {
                lexer.nextToken();
                stmt.setDeterministic(true);
                continue;
            }
            if (lexer.identifierEquals(FnvHash.Constants.CONTAINS) || lexer.token() == Token.CONTAINS) {
                lexer.nextToken();
                acceptIdentifier("SQL");
                stmt.setContainsSql(true);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.SQL)) {
                lexer.nextToken();
                acceptIdentifier("SECURITY");
                SQLName authid = this.exprParser.name();
                stmt.setAuthid(authid);
            }

            break;
        }

        SQLStatement block;
        if (lexer.token() == Token.BEGIN) {
            block = this.parseBlock();
        } else {
            block = this.parseStatement();
        }

        stmt.setBlock(block);

        return stmt;
    }

    /**
     * parse procedure statement block
     *
     * @param statementList
     */
    private void parseProcedureStatementList(List<SQLStatement> statementList) {
        parseProcedureStatementList(statementList, -1);
    }

    /**
     * parse procedure statement block
     */
    private void parseProcedureStatementList(List<SQLStatement> statementList, int max) {
        for (; ; ) {
            if (max != -1) {
                if (statementList.size() >= max) {
                    return;
                }
            }

            if (lexer.token() == Token.EOF) {
                return;
            }
            if (lexer.token() == Token.END) {
                return;
            }
            if (lexer.token() == Token.ELSE) {
                return;
            }
            if (lexer.token() == (SEMI)) {
                lexer.nextToken();
                continue;
            }
            if (lexer.token() == Token.WHEN) {
                return;
            }
            if (lexer.token() == Token.UNTIL) {
                return;
            }
            // select into
            if (lexer.token() == (Token.SELECT)) {
                statementList.add(this.parseSelectInto());
                continue;
            }

            // update
            if (lexer.token() == (Token.UPDATE)) {
                statementList.add(parseUpdateStatement());
                continue;
            }

            // create
            if (lexer.token() == (Token.CREATE)) {
                statementList.add(parseCreate());
                continue;
            }

            // insert
            if (lexer.token() == Token.INSERT) {
                SQLStatement stmt = parseInsert();
                statementList.add(stmt);
                continue;
            }

            // delete
            if (lexer.token() == (Token.DELETE)) {
                statementList.add(parseDeleteStatement());
                continue;
            }

            // call
            if (lexer.token() == Token.LBRACE || lexer.identifierEquals("CALL")) {
                statementList.add(this.parseCall());
                continue;
            }

            // begin
            if (lexer.token() == Token.BEGIN) {
                statementList.add(this.parseBlock());
                continue;
            }

            if (lexer.token() == Token.VARIANT) {
                SQLExpr variant = this.exprParser.primary();
                if (variant instanceof SQLBinaryOpExpr) {
                    SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) variant;
                    if (binaryOpExpr.getOperator() == SQLBinaryOperator.Assignment) {
                        SQLSetStatement stmt = new SQLSetStatement(binaryOpExpr.getLeft(), binaryOpExpr.getRight(),
                                getDbType());
                        statementList.add(stmt);
                        continue;
                    }
                }
                accept(Token.COLONEQ);
                SQLExpr value = this.exprParser.expr();

                SQLSetStatement stmt = new SQLSetStatement(variant, value, getDbType());
                statementList.add(stmt);
                continue;
            }

            // select
            if (lexer.token() == Token.LPAREN) {
                Lexer.SavePoint savePoint = lexer.markOut();
                lexer.nextToken();

                if (lexer.token() == Token.SELECT) {
                    lexer.reset(savePoint);
                    statementList.add(this.parseSelect());
                    continue;
                } else {
                    throw new ParserException("TODO. " + lexer.info());
                }
            }
            // assign statement
            if (lexer.token() == Token.SET) {
                statementList.add(this.parseAssign());
                continue;
            }

            // while statement
            if (lexer.token() == Token.WHILE) {
                SQLStatement stmt = this.parseWhile();
                statementList.add(stmt);
                continue;
            }

            // loop statement
            if (lexer.token() == Token.LOOP) {
                statementList.add(this.parseLoop());
                continue;
            }

            // if statement
            if (lexer.token() == Token.IF) {
                statementList.add(this.parseIf());
                continue;
            }

            // case statement
            if (lexer.token() == Token.CASE) {
                statementList.add(this.parseCase());
                continue;
            }

            // declare statement
            if (lexer.token() == Token.DECLARE) {
                SQLStatement stmt = this.parseDeclare();
                statementList.add(stmt);
                continue;
            }

            // leave statement
            if (lexer.token() == Token.LEAVE) {
                statementList.add(this.parseLeave());
                continue;
            }

            // iterate statement
            if (lexer.token() == Token.ITERATE) {
                statementList.add(this.parseIterate());
                continue;
            }

            // repeat statement
            if (lexer.token() == Token.REPEAT) {
                statementList.add(this.parseRepeat());
                continue;
            }

            // open cursor
            if (lexer.token() == Token.OPEN) {
                statementList.add(this.parseOpen());
                continue;
            }

            // close cursor
            if (lexer.token() == Token.CLOSE) {
                statementList.add(this.parseClose());
                continue;
            }

            // fetch cursor into
            if (lexer.token() == Token.FETCH) {
                statementList.add(this.parseFetch());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.CHECKSUM)) {
                statementList.add(this.parseChecksum());
                continue;
            }

            if (lexer.token() == Token.IDENTIFIER) {
                String label = lexer.stringVal();
                Lexer.SavePoint savePoint = lexer.markOut();
                lexer.nextToken();
                if (lexer.token() == Token.VARIANT && lexer.stringVal().equals(":")) {
                    lexer.nextToken();
                    if (lexer.token() == Token.LOOP) {
                        // parse loop statement
                        statementList.add(this.parseLoop(label));
                    } else if (lexer.token() == Token.WHILE) {
                        // parse while statement with label
                        statementList.add(this.parseWhile(label));
                    } else if (lexer.token() == Token.BEGIN) {
                        // parse begin-end statement with label
                        statementList.add(this.parseBlock(label));
                    } else if (lexer.token() == Token.REPEAT) {
                        // parse repeat statement with label
                        statementList.add(this.parseRepeat(label));
                    }
                    continue;
                } else {
                    lexer.reset(savePoint);
                }

            }
            throw new ParserException("TODO, " + lexer.info());
        }

    }

    public MySqlChecksumTableStatement parseChecksum() {
        MySqlChecksumTableStatement stmt = new MySqlChecksumTableStatement();
        if (lexer.identifierEquals(FnvHash.Constants.CHECKSUM)) {
            lexer.nextToken();
        } else {
            throw new ParserException("TODO " + lexer.info());
        }

        accept(TABLE);

        for (; ; ) {
            SQLName table = this.exprParser.name();
            stmt.addTable(new SQLExprTableSource(table));

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }

            break;
        }

        return stmt;
    }

    @Override
    protected void parseIfElse(SQLIfStatement stmt) {
        while (lexer.nextIf(Token.ELSEIF)) {
            SQLIfStatement.ElseIf elseIf = new SQLIfStatement.ElseIf();
            elseIf.setCondition(this.exprParser.expr());
            elseIf.setParent(stmt);
            accept(Token.THEN);
            this.parseStatementList(elseIf.getStatements(), -1, elseIf);
            stmt.getElseIfList().add(elseIf);
        }

        while (lexer.nextIf(Token.ELSE)) {
            if (lexer.nextIf(Token.IF)) {
                SQLIfStatement.ElseIf elseIf = new SQLIfStatement.ElseIf();

                elseIf.setCondition(this.exprParser.expr());
                elseIf.setParent(stmt);

                accept(Token.THEN);
                this.parseStatementList(elseIf.getStatements(), -1, elseIf);

                stmt.getElseIfList().add(elseIf);
            } else {
                SQLIfStatement.Else elseItem = new SQLIfStatement.Else();
                this.parseStatementList(elseItem.getStatements(), -1, elseItem);
                stmt.setElseItem(elseItem);
                break;
            }
        }
    }

    /**
     * parse while statement with label
     *
     * @return MySqlWhileStatement
     */
    public SQLWhileStatement parseWhile(String label) {
        accept(Token.WHILE);

        SQLWhileStatement stmt = new SQLWhileStatement();

        stmt.setLabelName(label);

        stmt.setCondition(this.exprParser.expr());

        accept(Token.DO);

        this.parseStatementList(stmt.getStatements(), -1, stmt);

        accept(Token.END);

        accept(Token.WHILE);

        acceptIdentifier(label);

        accept(SEMI);

        stmt.setAfterSemi(true);

        return stmt;

    }

    /**
     * parse case statement
     *
     * @return MySqlCaseStatement
     */
    public MySqlCaseStatement parseCase() {
        MySqlCaseStatement stmt = new MySqlCaseStatement();
        accept(Token.CASE);

        if (lexer.token() == Token.WHEN)// grammar 1
        {
            while (lexer.token() == Token.WHEN) {
                MySqlWhenStatement when = new MySqlWhenStatement();

                accept(Token.WHEN);

                // when expr
                when.setCondition(exprParser.expr());

                accept(Token.THEN);

                // when block
                this.parseStatementList(when.getStatements(), -1, when);

                stmt.addWhenStatement(when);
            }
            if (lexer.token() == Token.ELSE) {
                // parse else block
                SQLIfStatement.Else elseStmt = new SQLIfStatement.Else();
                this.parseStatementList(elseStmt.getStatements(), -1, elseStmt);
                stmt.setElseItem(elseStmt);
            }
        } else {
            // case expr
            stmt.setCondition(exprParser.expr());

            while (lexer.token() == Token.WHEN) {
                accept(Token.WHEN);
                MySqlWhenStatement when = new MySqlWhenStatement();
                // when expr
                when.setCondition(exprParser.expr());

                accept(Token.THEN);

                // when block
                this.parseStatementList(when.getStatements(), -1, when);

                stmt.addWhenStatement(when);
            }
            if (lexer.token() == Token.ELSE) {
                accept(Token.ELSE);
                // else block
                SQLIfStatement.Else elseStmt = new SQLIfStatement.Else();
                this.parseStatementList(elseStmt.getStatements(), -1, elseStmt);
                stmt.setElseItem(elseStmt);
            }
        }
        accept(Token.END);
        accept(Token.CASE);
        accept(SEMI);
        return stmt;

    }

    /**
     * parse declare statement
     */
    public SQLStatement parseDeclare() {
        Lexer.SavePoint savePoint = lexer.markOut();

        lexer.nextToken();

        if (lexer.token() == Token.CONTINUE) {
            lexer.reset(savePoint);
            return this.parseDeclareHandler();
        }

        lexer.nextToken();
        if (lexer.token() == Token.CURSOR) {
            lexer.reset(savePoint);
            return this.parseCursorDeclare();
        } else if (lexer.identifierEquals("HANDLER")) {
            //DECLARE异常处理程序 [add by zhujun 2016-04-16]
            lexer.reset(savePoint);
            return this.parseDeclareHandler();
        } else if (lexer.token() == Token.CONDITION) {
            //DECLARE异常 [add by zhujun 2016-04-17]
            lexer.reset(savePoint);
            return this.parseDeclareCondition();
        } else {
            lexer.reset(savePoint);
        }

        MySqlDeclareStatement stmt = new MySqlDeclareStatement();
        accept(Token.DECLARE);
        // lexer.nextToken();
        for (; ; ) {
            SQLDeclareItem item = new SQLDeclareItem();
            item.setName(exprParser.name());

            stmt.addVar(item);
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                stmt.setAfterSemi(true);
                continue;
            } else if (lexer.token() != Token.EOF) {
                // var type
                item.setDataType(exprParser.parseDataType());

                if (lexer.token() == Token.DEFAULT) {
                    lexer.nextToken();
                    SQLExpr defaultValue = this.exprParser.primary();
                    item.setValue(defaultValue);
                }

                break;
            } else {
                throw new ParserException("TODO. " + lexer.info());
            }
        }
        return stmt;
    }

    /**
     * parse assign statement
     */
    public SQLSetStatement parseAssign() {
        accept(Token.SET);
        SQLSetStatement stmt = new SQLSetStatement(getDbType());
        parseAssignItems(stmt.getItems(), stmt);
        return stmt;
    }

    /**
     * parse select into
     */
    public MySqlSelectIntoStatement parseSelectInto() {
        MySqlSelectIntoParser parse = new MySqlSelectIntoParser(this.exprParser);
        return parse.parseSelectInto();
    }

    /**
     * parse loop statement
     */
    public SQLLoopStatement parseLoop() {
        SQLLoopStatement loopStmt = new SQLLoopStatement();
        accept(Token.LOOP);
        this.parseStatementList(loopStmt.getStatements(), -1, loopStmt);
        accept(Token.END);
        accept(Token.LOOP);
        accept(SEMI);
        loopStmt.setAfterSemi(true);
        return loopStmt;
    }

    /**
     * parse loop statement with label
     */
    public SQLLoopStatement parseLoop(String label) {
        SQLLoopStatement loopStmt = new SQLLoopStatement();
        loopStmt.setLabelName(label);
        accept(Token.LOOP);
        this.parseStatementList(loopStmt.getStatements(), -1, loopStmt);
        accept(Token.END);
        accept(Token.LOOP);
        if (lexer.token() != SEMI) {
            acceptIdentifier(label);
        }
        accept(SEMI);
        loopStmt.setAfterSemi(true);
        return loopStmt;
    }

    /**
     * parse loop statement with label
     */
    public SQLBlockStatement parseBlock(String label) {
        SQLBlockStatement block = new SQLBlockStatement();
        block.setLabelName(label);
        accept(Token.BEGIN);
        this.parseStatementList(block.getStatementList(), -1, block);
        accept(Token.END);
        acceptIdentifier(label);
        return block;
    }

    /**
     * parse leave statement
     */
    public MySqlLeaveStatement parseLeave() {
        accept(Token.LEAVE);
        MySqlLeaveStatement leaveStmt = new MySqlLeaveStatement();
        leaveStmt.setLabelName(exprParser.name().getSimpleName());
        accept(SEMI);
        return leaveStmt;
    }

    /**
     * parse iterate statement
     */
    public MySqlIterateStatement parseIterate() {
        accept(Token.ITERATE);
        MySqlIterateStatement iterateStmt = new MySqlIterateStatement();
        iterateStmt.setLabelName(exprParser.name().getSimpleName());
        accept(SEMI);
        return iterateStmt;
    }

    /**
     * parse repeat statement
     */
    public MySqlRepeatStatement parseRepeat() {
        MySqlRepeatStatement stmt = new MySqlRepeatStatement();
        accept(Token.REPEAT);
        parseStatementList(stmt.getStatements(), -1, stmt);
        accept(Token.UNTIL);
        stmt.setCondition(exprParser.expr());
        accept(Token.END);
        accept(Token.REPEAT);
        accept(SEMI);
        stmt.setAfterSemi(true);
        return stmt;
    }

    /**
     * Parses a REPEAT statement and returns a MySqlRepeatStatement object representing the parsed statement.
     *
     * @param label the label associated with the REPEAT statement
     * @return a MySqlRepeatStatement object representing the parsed REPEAT statement
     */
    public MySqlRepeatStatement parseRepeat(String label) {
        MySqlRepeatStatement repeatStmt = new MySqlRepeatStatement();
        repeatStmt.setLabelName(label);
        accept(Token.REPEAT);
        this.parseStatementList(repeatStmt.getStatements(), -1, repeatStmt);
        accept(Token.UNTIL);
        repeatStmt.setCondition(exprParser.expr());
        accept(Token.END);
        accept(Token.REPEAT);
        acceptIdentifier(label);
        accept(SEMI);
        return repeatStmt;
    }

    /**
     * parse cursor declare statement
     */
    public MySqlCursorDeclareStatement parseCursorDeclare() {
        MySqlCursorDeclareStatement stmt = new MySqlCursorDeclareStatement();
        accept(Token.DECLARE);

        stmt.setCursorName(exprParser.name());

        accept(Token.CURSOR);

        accept(Token.FOR);

        //SQLSelectStatement selelctStmt = (SQLSelectStatement) parseSelect();
        SQLSelect select = this.createSQLSelectParser().select();
        stmt.setSelect(select);

        accept(SEMI);

        return stmt;
    }

    /**
     * zhujun [455910092@qq.com]
     * parse spstatement
     */
    public SQLStatement parseSpStatement() {
        // update
        if (lexer.token() == (Token.UPDATE)) {
            return parseUpdateStatement();
        }

        // create
        if (lexer.token() == (Token.CREATE)) {
            return parseCreate();
        }

        // insert
        if (lexer.token() == Token.INSERT) {
            return parseInsert();
        }

        // delete
        if (lexer.token() == (Token.DELETE)) {
            return parseDeleteStatement();
        }

        // begin
        if (lexer.token() == Token.BEGIN) {
            return this.parseBlock();
        }

        // select
        if (lexer.token() == Token.LPAREN) {
            Lexer.SavePoint savePoint = lexer.markOut();
            lexer.nextToken();

            if (lexer.token() == Token.SELECT) {
                lexer.reset(savePoint);
                return this.parseSelect();
            } else {
                throw new ParserException("TODO. " + lexer.info());
            }
        }
        // assign statement
        if (lexer.token() == Token.SET) {
            return parseAssign();
        }

        throw new ParserException("error sp_statement. " + lexer.info());
    }

    /**
     * 定义异常处理程序
     *
     * @author zhujun [455910092@qq.com]
     * 2016-04-16
     */
    public MySqlDeclareHandlerStatement parseDeclareHandler() {
        //DECLARE handler_type HANDLER FOR condition_value[,...] sp_statement
        //handler_type 取值为 CONTINUE | EXIT | UNDO
        //condition_value 取值为 SQLWARNING | NOT FOUND | SQLEXCEPTION | SQLSTATE value(异常码 e.g 1062)

        MySqlDeclareHandlerStatement stmt = new MySqlDeclareHandlerStatement();
        accept(Token.DECLARE);
        //String handlerType = exprParser.name().getSimpleName();
        if (lexer.token() == Token.CONTINUE) {
            stmt.setHandleType(MySqlHandlerType.CONTINUE);
        } else if (lexer.token() == Token.EXIT) {
            stmt.setHandleType(MySqlHandlerType.CONTINUE);
        } else if (lexer.token() == Token.UNDO) {
            stmt.setHandleType(MySqlHandlerType.CONTINUE);
        } else {
            throw new ParserException("unkown handle type. " + lexer.info());
        }
        lexer.nextToken();

        acceptIdentifier("HANDLER");

        accept(Token.FOR);

        for (; ; ) {
            String tokenName = lexer.stringVal();
            ConditionValue condition = new ConditionValue();

            if (tokenName.equalsIgnoreCase("NOT")) {
                //for 'NOT FOUND'
                lexer.nextToken();
                acceptIdentifier("FOUND");
                condition.setType(ConditionType.SYSTEM);
                condition.setValue("NOT FOUND");

            } else if (tokenName.equalsIgnoreCase("SQLSTATE")) {
                //for SQLSTATE (SQLSTATE '10001')
                condition.setType(ConditionType.SQLSTATE);
                lexer.nextToken();
                //condition.setValue(lexer.stringVal());
                //lexer.nextToken();
                condition.setValue(exprParser.name().toString());
            } else if (lexer.identifierEquals("SQLEXCEPTION")) {
                //for SQLEXCEPTION
                condition.setType(ConditionType.SYSTEM);
                condition.setValue(lexer.stringVal());
                lexer.nextToken();
            } else if (lexer.identifierEquals("SQLWARNING")) {
                //for SQLWARNING
                condition.setType(ConditionType.SYSTEM);
                condition.setValue(lexer.stringVal());
                lexer.nextToken();
            } else { //for condition_name or mysql_error_code
                if (lexer.token() == Token.LITERAL_INT) {
                    condition.setType(ConditionType.MYSQL_ERROR_CODE);
                    condition.setValue(lexer.integerValue().toString());
                } else {
                    condition.setType(ConditionType.SELF);
                    condition.setValue(tokenName);
                }
                lexer.nextToken();
            }
            stmt.getConditionValues().add(condition);
            if (lexer.token() == Token.COMMA) {
                accept(Token.COMMA);
                continue;
            } else if (lexer.token() != Token.EOF) {
                break;
            } else {
                throw new ParserException("declare handle not eof");
            }
        }

        stmt.setSpStatement(parseSpStatement());

        if (!(stmt.getSpStatement() instanceof SQLBlockStatement)) {
            accept(SEMI);
        }

        return stmt;
    }

    /**
     * zhujun [455910092@qq.com]
     * 2016-04-17
     * 定义条件
     */
    public MySqlDeclareConditionStatement parseDeclareCondition() {
        MySqlDeclareConditionStatement stmt = new MySqlDeclareConditionStatement();
        accept(Token.DECLARE);

        stmt.setConditionName(exprParser.name().toString());

        accept(Token.CONDITION);

        accept(Token.FOR);

        String tokenName = lexer.stringVal();
        ConditionValue condition = new ConditionValue();
        if (tokenName.equalsIgnoreCase("SQLSTATE")) { //for SQLSTATE (SQLSTATE '10001')
            condition.setType(ConditionType.SQLSTATE);
            lexer.nextToken();
            condition.setValue(exprParser.name().toString());
        } else if (lexer.token() == Token.LITERAL_INT) {
            condition.setType(ConditionType.MYSQL_ERROR_CODE);
            condition.setValue(lexer.integerValue().toString());
            lexer.nextToken();
        } else {
            throw new ParserException("declare condition grammer error. " + lexer.info());
        }

        stmt.setConditionValue(condition);

        accept(SEMI);

        return stmt;
    }

    @Override
    public SQLStatement parseFlashback() {
        MySqlFlashbackStatement stmt = new MySqlFlashbackStatement();
        acceptIdentifier("FLASHBACK");
        accept(TABLE);

        SQLName name = this.exprParser.name();
        stmt.setName(name);
        accept(Token.TO);
        acceptIdentifier("BEFORE");
        accept(Token.DROP);

        if (lexer.identifierEquals(FnvHash.Constants.RENAME)) {
            lexer.nextToken();
            accept(Token.TO);
            SQLName to = this.exprParser.name();
            stmt.setRenameTo(to);
        }

        return stmt;
    }

    public java.sql.Timestamp getCurrentTimestamp() {
        return now;
    }

    public java.sql.Date getCurrentDate() {
        return currentDate;
    }

    public MySqlCreateTableParser getSQLCreateTableParser() {
        return new MySqlCreateTableParser(this.exprParser);
    }

    @Override
    public SQLStatement parseCopy() {
        acceptIdentifier("COPY");

        SQLCopyFromStatement stmt = new SQLCopyFromStatement();

        SQLExpr table = this.exprParser.name();
        stmt.setTable(new SQLExprTableSource(table));

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

        if (lexer.token() == Token.PARTITION) {
            lexer.nextToken();
            this.exprParser.parseAssignItem(stmt.getPartitions(), stmt);
        }

        accept(Token.FROM);
        SQLExpr from = this.exprParser.expr();
        stmt.setFrom(from);

        if (lexer.identifierEquals(FnvHash.Constants.CREDENTIALS)) {
            lexer.nextToken();

            for (; ; ) {
                if (lexer.identifierEquals(FnvHash.Constants.ACCESS_KEY_ID)) {
                    lexer.nextToken();
                    SQLExpr accessKeyId = this.exprParser.primary();
                    stmt.setAccessKeyId(accessKeyId);
                } else if (lexer.identifierEquals(FnvHash.Constants.ACCESS_KEY_SECRET)) {
                    lexer.nextToken();
                    SQLExpr accessKeySecret = this.exprParser.primary();
                    stmt.setAccessKeySecret(accessKeySecret);
                } else {
                    break;
                }
            }
        }

        if (lexer.token() == WITH) {
            lexer.nextToken();
            accept(Token.LPAREN);
            for (; ; ) {
                SQLName name = this.exprParser.name();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr value = this.exprParser.expr();
                SQLAssignItem item = new SQLAssignItem(name, value);
                item.setParent(stmt);
                stmt.getOptions().add(item);
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
        }
        return stmt;
    }

    @Override
    protected void parseCreateMaterializedViewRest(SQLCreateMaterializedViewStatement stmt) {
        stmt.setDbType(dbType);
        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            for (; ; ) {
                Token token = lexer.token();

                if (lexer.identifierEquals(FnvHash.Constants.CLUSTERED)) {
                    lexer.nextToken();
                    if (lexer.token() == Token.KEY) {
                        MySqlKey clsKey = new MySqlKey();
                        this.exprParser.parseIndex(clsKey.getIndexDefinition());
                        clsKey.setIndexType("CLUSTERED");
                        clsKey.setParent(stmt);
                        stmt.getTableElementList().add(clsKey);

                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                    } else if (lexer.token() == INDEX) {
                        MySqlTableIndex idx = new MySqlTableIndex();
                        this.exprParser.parseIndex(idx.getIndexDefinition());
                        idx.setIndexType("CLUSTERED");
                        idx.setParent(stmt);
                        stmt.getTableElementList().add(idx);

                        if (lexer.token() == Token.RPAREN) {
                            break;
                        } else if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                    }
                }

                if (token == Token.IDENTIFIER) {
                    SQLColumnDefinition column = this.exprParser.parseColumn(stmt);
                    stmt.getTableElementList().add((SQLTableElement) column);
                } else if (token == Token.PRIMARY //
                        || token == Token.UNIQUE //
                        || token == Token.CHECK //
                        || token == Token.CONSTRAINT
                        || token == Token.FOREIGN) {
                    SQLConstraint constraint = this.exprParser.parseConstraint();
                    constraint.setParent(stmt);
                    stmt.getTableElementList().add((SQLTableElement) constraint);
                } else if (lexer.token() == (INDEX)) {
                    MySqlTableIndex idx = new MySqlTableIndex();
                    this.exprParser.parseIndex(idx.getIndexDefinition());

                    idx.setParent(stmt);
                    stmt.getTableElementList().add(idx);
                } else if (lexer.token() == (Token.KEY)) {
                    Lexer.SavePoint savePoint = lexer.mark();
                    lexer.nextToken();

                    boolean isColumn = false;
                    if (lexer.identifierEquals(FnvHash.Constants.VARCHAR)) {
                        isColumn = true;
                    }
                    lexer.reset(savePoint);

                    if (isColumn) {
                        stmt.getTableElementList().add(this.exprParser.parseColumn());
                    } else {
                        SQLName name = null;
                        if (lexer.token() == Token.IDENTIFIER) {
                            name = this.exprParser.name();
                        }

                        MySqlKey key = new MySqlKey();
                        this.exprParser.parseIndex(key.getIndexDefinition());

                        if (name != null) {
                            key.setName(name);
                        }
                        key.setParent(stmt);
                        stmt.getTableElementList().add(key);
                    }
                    continue;
                }
                if (lexer.token() == COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
        }

        for (; ; ) {
            if (lexer.identifierEquals(FnvHash.Constants.DISTRIBUTED)) {
                lexer.nextToken();
                accept(Token.BY);
                if (lexer.identifierEquals(FnvHash.Constants.HASH)) {
                    lexer.nextToken();
                    accept(Token.LPAREN);
                    for (; ; ) {
                        SQLName name = this.exprParser.name();
                        stmt.getDistributedBy().add(name);
                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    accept(Token.RPAREN);
                    stmt.setDistributedByType(new SQLIdentifierExpr("HASH"));
                } else if (lexer.identifierEquals(FnvHash.Constants.DUPLICATE)) {
                    lexer.nextToken();
                    accept(Token.LPAREN);
                    for (; ; ) {
                        SQLName name = this.exprParser.name();
                        stmt.getDistributedBy().add(name);
                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    accept(Token.RPAREN);
                    stmt.setDistributedByType(new SQLIdentifierExpr("DUPLICATE"));
                } else if (lexer.identifierEquals(FnvHash.Constants.BROADCAST)) {
                    lexer.nextToken();
                    stmt.setDistributedByType(new SQLIdentifierExpr("BROADCAST"));
                }
                continue;
            } else if (lexer.identifierEquals("INDEX_ALL")) {
                lexer.nextToken();
                accept(Token.EQ);
                if (lexer.token() == Token.LITERAL_CHARS) {
                    if ("Y".equalsIgnoreCase(lexer.stringVal())) {
                        lexer.nextToken();
                        stmt.addOption("INDEX_ALL", new SQLCharExpr("Y"));
                    } else if ("N".equalsIgnoreCase(lexer.stringVal())) {
                        lexer.nextToken();
                        stmt.addOption("INDEX_ALL", new SQLCharExpr("N"));
                    } else {
                        throw new ParserException("INDEX_ALL accept parameter ['Y' or 'N'] only.");
                    }
                }
                continue;
            } else if (lexer.identifierEquals(FnvHash.Constants.ENGINE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr expr = this.exprParser.expr();
                stmt.addOption("ENGINE", expr);
                continue;
            } else if (lexer.token() == Token.PARTITION) {
                SQLPartitionBy partitionBy = this.exprParser.parsePartitionBy();
                stmt.setPartitionBy(partitionBy);
                continue;
            } else if (lexer.token() == Token.COMMENT) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.setComment(this.exprParser.expr());
                continue;
            }
            break;
        }
    }

    @Override
    public void parseExplainFormatPartition(SQLExplainStatement explain) {
        if (lexer.identifierEquals("FORMAT")
                || lexer.identifierEquals("PARTITIONS")) {
            explain.setType(lexer.stringVal());
            lexer.nextToken();
        }
    }

    @Override
    public void parseExplainFormatType(SQLExplainStatement explain) {
        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();

            if (lexer.identifierEquals("FORMAT")) {
                lexer.nextToken();
                lexer.nextToken();
            } else if (lexer.identifierEquals("TYPE")) {
                lexer.nextToken();
                lexer.nextToken();
            }

            accept(Token.RPAREN);
        }
    }
}
