/*
 * Copyright (C) 2016-2022 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.meta.table;

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.MySqlKey;
import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey;
import com.alibaba.druid.sql.dialect.mysql.ast.MySqlUnique;
import com.alibaba.druid.sql.dialect.mysql.ast.MysqlForeignKey;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.*;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlCreateTableParser;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlSelectParser;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import com.alibaba.druid.util.MySqlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by szf on 2018/12/4.
 * extends druid MySqlCreateTableParser for AUTO_INCREMENT
 * exempts code check because the source code is copy from druid
 * source code is copy from druid version 1.2.3 by ylz 2020/12/11
 * source code is copy from druid version 1.2.6 by yanhuqing 2021/05/12
 */
public class DbleCreateTableParser extends MySqlCreateTableParser {

    private static final Logger LOGGER = LoggerFactory.getLogger(DbleCreateTableParser.class);

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

    public MySqlCreateTableStatement parseCreateTable(boolean acceptCreate) {
        MySqlCreateTableStatement stmt = new MySqlCreateTableStatement();
        if (acceptCreate) {
            if (lexer.hasComment() && lexer.isKeepComments()) {
                stmt.addBeforeComment(lexer.readAndResetComments());
            }
            accept(Token.CREATE);
        }

        if (lexer.identifierEquals("TEMPORARY")) {
            lexer.nextToken();
            stmt.setType(SQLCreateTableStatement.Type.GLOBAL_TEMPORARY);
        } else if (lexer.identifierEquals("SHADOW")) {
            lexer.nextToken();
            stmt.setType(SQLCreateTableStatement.Type.SHADOW);
        }

        if (lexer.identifierEquals(FnvHash.Constants.DIMENSION)) {
            lexer.nextToken();
            stmt.setDimension(true);
        }

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

        if (lexer.identifierEquals(FnvHash.Constants.EXTERNAL)) {
            lexer.nextToken();
            stmt.setExternal(true);
        }

        accept(Token.TABLE);

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

            stmt.setIfNotExiists(true);
        }

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

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

        if (lexer.token() == Token.WITH) {
            SQLSelect query = new MySqlSelectParser(this.exprParser).select();
            stmt.setSelect(query);
        } else if (lexer.token() == (Token.LPAREN)) {
            lexer.nextToken();

            if (lexer.token() == Token.SELECT) {
                SQLSelect query = new MySqlSelectParser(this.exprParser).select();
                stmt.setSelect(query);
            } else {
                for (; ; ) {
                    SQLColumnDefinition column = null;

                    boolean global = false;
                    if (lexer.identifierEquals(FnvHash.Constants.GLOBAL)) {
                        final Lexer.SavePoint mark = lexer.mark();
                        lexer.nextToken();
                        if (lexer.token() == Token.INDEX || lexer.token() == Token.UNIQUE) {
                            global = true;
                        } else {
                            lexer.reset(mark);
                        }
                    }

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

                        if (lexer.token() == Token.KEY) {
                            MySqlKey fulltextKey = new MySqlKey();
                            this.exprParser.parseIndex(fulltextKey.getIndexDefinition());
                            fulltextKey.setIndexType("FULLTEXT");
                            fulltextKey.setParent(stmt);
                            stmt.getTableElementList().add(fulltextKey);

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

                            if (lexer.token() == Token.RPAREN) {
                                break;
                            } else if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            }
                        } else if (lexer.token() == Token.INDEX) {
                            MySqlTableIndex idx = new MySqlTableIndex();
                            this.exprParser.parseIndex(idx.getIndexDefinition());
                            idx.setIndexType("FULLTEXT");
                            idx.setParent(stmt);
                            stmt.getTableElementList().add(idx);

                            if (lexer.token() == Token.RPAREN) {
                                break;
                            } else if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            }
                        } else if (lexer.token() == Token.IDENTIFIER && MySqlUtils.isBuiltinDataType(lexer.stringVal())) {
                            lexer.reset(mark);
                        } else {
                            MySqlTableIndex idx = new MySqlTableIndex();
                            this.exprParser.parseIndex(idx.getIndexDefinition());
                            idx.setIndexType("FULLTEXT");
                            idx.setParent(stmt);
                            stmt.getTableElementList().add(idx);

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

                    } else if (lexer.identifierEquals(FnvHash.Constants.SPATIAL)) {
                        Lexer.SavePoint mark = lexer.mark();
                        lexer.nextToken();
                        if (lexer.token() == Token.INDEX || lexer.token() == Token.KEY ||
                                lexer.token() != Token.IDENTIFIER || !MySqlUtils.isBuiltinDataType(lexer.stringVal())) {
                            MySqlTableIndex idx = new MySqlTableIndex();
                            this.exprParser.parseIndex(idx.getIndexDefinition());
                            idx.setIndexType("SPATIAL");
                            idx.setParent(stmt);
                            stmt.getTableElementList().add(idx);

                            if (lexer.token() == Token.RPAREN) {
                                break;
                            } else if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            }
                        } else {
                            lexer.reset(mark);
                        }
                    }

                    if (lexer.identifierEquals(FnvHash.Constants.ANN)) {
                        Lexer.SavePoint mark = lexer.mark();
                        lexer.nextToken();
                        if (lexer.token() == Token.INDEX || lexer.token() == Token.KEY) {
                            MySqlTableIndex idx = new MySqlTableIndex();
                            this.exprParser.parseIndex(idx.getIndexDefinition());
                            idx.setIndexType("ANN");
                            idx.setParent(stmt);
                            stmt.getTableElementList().add(idx);

                            if (lexer.token() == Token.RPAREN) {
                                break;
                            } else if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            }
                        } else {
                            lexer.reset(mark);
                        }
                    }
                    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() == 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;
                            }
                        }
                    } else if (lexer.identifierEquals(FnvHash.Constants.CLUSTERING)) {
                        lexer.nextToken();
                        if (lexer.token() == Token.KEY) {
                            MySqlKey clsKey = new MySqlKey();
                            this.exprParser.parseIndex(clsKey.getIndexDefinition());
                            clsKey.setIndexType("CLUSTERING");
                            clsKey.setParent(stmt);
                            stmt.getTableElementList().add(clsKey);

                            if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            }
                        } else if (lexer.token() == Token.INDEX) {
                            MySqlTableIndex idx = new MySqlTableIndex();
                            this.exprParser.parseIndex(idx.getIndexDefinition());
                            idx.setIndexType("CLUSTERING");
                            idx.setParent(stmt);
                            stmt.getTableElementList().add(idx);

                            if (lexer.token() == Token.RPAREN) {
                                break;
                            } else if (lexer.token() == Token.COMMA) {
                                lexer.nextToken();
                                continue;
                            }
                        }
                    } else if (lexer.token() == Token.IDENTIFIER || lexer.token() == Token.LITERAL_CHARS) {
                        column = this.exprParser.parseColumn();
                        column.setParent(stmt);
                        stmt.getTableElementList().add(column);

                        if (lexer.isKeepComments() && lexer.hasComment()) {
                            column.addAfterComment(lexer.readAndResetComments());
                        }
                    } else if (lexer.token() == Token.CONSTRAINT || lexer.token() == Token.PRIMARY || lexer.token() == Token.UNIQUE) {
                        SQLTableConstraint constraint = this.parseConstraint();
                        constraint.setParent(stmt);

                        if (constraint instanceof MySqlUnique) {
                            MySqlUnique unique = (MySqlUnique) constraint;
                            if (global) {
                                unique.setGlobal(true);
                            }
                        }

                        stmt.getTableElementList().add(constraint);
                    } else if (lexer.token() == (Token.INDEX)) {
                        MySqlTableIndex idx = new MySqlTableIndex();
                        this.exprParser.parseIndex(idx.getIndexDefinition());

                        if (global) {
                            idx.getIndexDefinition().setGlobal(true);
                        }

                        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) {
                            column = this.exprParser.parseColumn();
                            stmt.getTableElementList().add(column);
                        } else {
                            stmt.getTableElementList().add(parseConstraint());
                        }
                    } else if (lexer.token() == (Token.FOREIGN)) {
                        SQLForeignKeyConstraint fk = this.getExprParser().parseForeignKey();
                        fk.setParent(stmt);
                        stmt.getTableElementList().add(fk);
                    } else if (lexer.token() == Token.CHECK) {
                        SQLCheck check = this.exprParser.parseCheck();
                        stmt.getTableElementList().add(check);
                    } else if (lexer.token() == Token.LIKE) {
                        lexer.nextToken();
                        SQLTableLike tableLike = new SQLTableLike();
                        tableLike.setTable(new SQLExprTableSource(this.exprParser.name()));
                        tableLike.setParent(stmt);
                        stmt.getTableElementList().add(tableLike);

                        if (lexer.identifierEquals(FnvHash.Constants.INCLUDING)) {
                            lexer.nextToken();
                            acceptIdentifier("PROPERTIES");
                            tableLike.setIncludeProperties(true);
                        } else if (lexer.identifierEquals(FnvHash.Constants.EXCLUDING)) {
                            lexer.nextToken();
                            acceptIdentifier("PROPERTIES");
                            tableLike.setExcludeProperties(true);
                        }
                    } else {
                        column = this.exprParser.parseColumn();
                        stmt.getTableElementList().add(column);
                    }

                    if (lexer.token() != Token.COMMA) {
                        break;
                    } else {
                        lexer.nextToken();
                        if (lexer.isKeepComments() && lexer.hasComment() && column != null) {
                            column.addAfterComment(lexer.readAndResetComments());
                        }
                    }
                }
            }

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

            accept(Token.RPAREN);

            if (lexer.token() == Token.HINT && lexer.stringVal().charAt(0) == '!') {
                lexer.nextToken();
            }
        }

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

            if (lexer.identifierEquals(FnvHash.Constants.ENGINE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                SQLExpr expr = null;
                if (lexer.token() == Token.MERGE) {
                    expr = new SQLIdentifierExpr(lexer.stringVal());
                    lexer.nextToken();
                } else {
                    expr = this.exprParser.expr();
                }
                stmt.addOption("ENGINE", expr);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.BLOCK_SIZE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                SQLExpr expr = null;
                if (lexer.token() == Token.MERGE) {
                    expr = new SQLIdentifierExpr(lexer.stringVal());
                    lexer.nextToken();
                } else {
                    expr = this.exprParser.integerExpr();
                }
                stmt.addOption("BLOCK_SIZE", expr);
                continue;
            }

            if (lexer.identifierEquals("BLOCK_FORMAT")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                SQLExpr expr = this.exprParser.primary();
                stmt.addOption("BLOCK_FORMAT", expr);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.REPLICA_NUM)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                SQLExpr expr = this.exprParser.integerExpr();
                stmt.addOption("REPLICA_NUM", expr);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.TABLET_SIZE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                SQLExpr expr = this.exprParser.integerExpr();
                stmt.addOption("TABLET_SIZE", expr);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.PCTFREE)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                SQLExpr expr = this.exprParser.integerExpr();
                stmt.addOption("PCTFREE", expr);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.USE_BLOOM_FILTER)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                SQLExpr expr = this.exprParser.primary();
                stmt.addOption("USE_BLOOM_FILTER", expr);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.AUTO_INCREMENT)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                LOGGER.warn("parse sql drop AUTO_INCREMENT detail in " + stmt.getTableSource().getName());
                this.exprParser.expr();
                continue;
            }

            if (lexer.identifierEquals("AVG_ROW_LENGTH")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("AVG_ROW_LENGTH", this.exprParser.expr());
                continue;
            }

            if (lexer.token() == Token.DEFAULT) {
                lexer.nextToken();
                parseTableOptionCharsetOrCollate(stmt);
                continue;
            }

            if (parseTableOptionCharsetOrCollate(stmt)) {
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.CHECKSUM)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("CHECKSUM", this.exprParser.expr());
                continue;
            }

            if (lexer.token() == Token.COMMENT) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.setComment(this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.CONNECTION)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("CONNECTION", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.DATA)) {
                lexer.nextToken();
                acceptIdentifier("DIRECTORY");
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("DATA DIRECTORY", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals("DELAY_KEY_WRITE")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("DELAY_KEY_WRITE", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals("FULLTEXT_DICT")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("FULLTEXT_DICT", this.exprParser.charExpr());
                continue;
            }

            if (lexer.token() == Token.INDEX) {
                lexer.nextToken();
                acceptIdentifier("DIRECTORY");
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("INDEX DIRECTORY", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals("INSERT_METHOD")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("INSERT_METHOD", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals("KEY_BLOCK_SIZE")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("KEY_BLOCK_SIZE", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.MAX_ROWS)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("MAX_ROWS", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.MIN_ROWS)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("MIN_ROWS", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.PACK_KEYS)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("PACK_KEYS", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.PASSWORD)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("PASSWORD", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals("ROW_FORMAT")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("ROW_FORMAT", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals("STATS_AUTO_RECALC")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                stmt.addOption("STATS_AUTO_RECALC", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals("STATS_PERSISTENT")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                stmt.addOption("STATS_PERSISTENT", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals("STATS_SAMPLE_PAGES")) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }

                stmt.addOption("STATS_SAMPLE_PAGES", this.exprParser.expr());
                continue;
            }

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


                accept(Token.LPAREN);
                SQLListExpr list = new SQLListExpr();
                this.exprParser.exprList(list.getItems(), list);
                stmt.addOption("UNION", list);
                accept(Token.RPAREN);
                continue;
            }

            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.addOption("TABLESPACE", option);
                continue;
            }

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

                SQLName tableGroup = this.exprParser.name();
                stmt.setTableGroup(tableGroup);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.TYPE)) {
                lexer.nextToken();
                accept(Token.EQ);
                stmt.addOption("TYPE", this.exprParser.expr());
                continue;
            }


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

            if (lexer.identifierEquals("RT_INDEX_ALL")) {
                lexer.nextToken();
                accept(Token.EQ);
                if (lexer.token() == Token.LITERAL_CHARS) {
                    if ("Y".equalsIgnoreCase(lexer.stringVal())) {
                        lexer.nextToken();
                        stmt.addOption("RT_INDEX_ALL", new SQLCharExpr("Y"));
                    } else if ("N".equalsIgnoreCase(lexer.stringVal())) {
                        lexer.nextToken();
                        stmt.addOption("RT_INDEX_ALL", new SQLCharExpr("N"));
                    } else {
                        throw new ParserException("RT_INDEX_ALL accepts parameter ['Y' or 'N'] only.");
                    }
                }

                continue;
            }
            if (lexer.identifierEquals(FnvHash.Constants.ARCHIVE)) {
                lexer.nextToken();
                accept(Token.BY);
                acceptIdentifier("OSS");
                stmt.setArchiveBy(new SQLIdentifierExpr("OSS"));
                continue;
            }

            if (lexer.identifierEquals("STORAGE_TYPE")) {
                lexer.nextToken();
                accept(Token.EQ);
                stmt.addOption("STORAGE_TYPE", this.exprParser.charExpr());
                continue;
            }

            if (lexer.identifierEquals("STORAGE_POLICY")) {
                lexer.nextToken();
                accept(Token.EQ);
                stmt.addOption("STORAGE_POLICY", this.exprParser.charExpr());
                continue;
            }

            if (lexer.identifierEquals("HOT_PARTITION_COUNT")) {
                lexer.nextToken();
                accept(Token.EQ);
                try {
                    stmt.addOption("HOT_PARTITION_COUNT", this.exprParser.integerExpr());
                } catch (Exception e) {
                    throw new ParserException("only integer number is supported for hot_partition_count");
                }
                continue;
            }

            if (lexer.identifierEquals("TABLE_PROPERTIES")) {
                lexer.nextToken();
                accept(Token.EQ);
                stmt.addOption("TABLE_PROPERTIES", exprParser.charExpr());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.ENCRYPTION)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("ENCRYPTION", this.exprParser.expr());
                continue;
            } else if (lexer.identifierEquals(FnvHash.Constants.COMPRESSION)) {
                lexer.nextToken();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                stmt.addOption("COMPRESSION", this.exprParser.expr());
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.CLUSTERED)) {
                lexer.nextToken();
                accept(Token.BY);
                accept(Token.LPAREN);
                for (; ; ) {
                    SQLSelectOrderByItem item = this.exprParser.parseSelectOrderByItem();
                    stmt.addClusteredByItem(item);
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
                accept(Token.RPAREN);
                continue;
            }

            if (lexer.token() == Token.PARTITION) {
                SQLPartitionBy partitionClause = parsePartitionBy();
                stmt.setPartitioning(partitionClause);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.BROADCAST)) {
                lexer.nextToken();
                stmt.setBroadCast(true);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.DISTRIBUTE) || 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.getDistributeBy().add(name);
                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    accept(Token.RPAREN);
                    stmt.setDistributeByType(new SQLIdentifierExpr("HASH"));
                } else if (lexer.identifierEquals(FnvHash.Constants.BROADCAST)) {
                    lexer.nextToken();
                    stmt.setDistributeByType(new SQLIdentifierExpr("BROADCAST"));
                }
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.DBPARTITION)) {
                lexer.nextToken();
                accept(Token.BY);
                SQLExpr dbPartitoinBy = this.exprParser.primary();
                stmt.setDbPartitionBy(dbPartitoinBy);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.DBPARTITIONS)) {
                lexer.nextToken();
                SQLExpr dbPartitoins = this.exprParser.primary();
                stmt.setDbPartitions(dbPartitoins);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.TBPARTITION)) {
                lexer.nextToken();
                accept(Token.BY);
                SQLExpr expr = this.exprParser.expr();
                if (lexer.identifierEquals(FnvHash.Constants.STARTWITH)) {
                    lexer.nextToken();
                    SQLExpr start = this.exprParser.primary();
                    acceptIdentifier("ENDWITH");
                    SQLExpr end = this.exprParser.primary();
                    expr = new SQLBetweenExpr(expr, start, end);
                }
                stmt.setTablePartitionBy(expr);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.TBPARTITIONS)) {
                lexer.nextToken();
                SQLExpr tbPartitions = this.exprParser.primary();
                stmt.setTablePartitions(tbPartitions);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.EXTPARTITION)) {
                lexer.nextToken();
                accept(Token.LPAREN);

                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;
                    }
                }
                accept(Token.RPAREN);
                stmt.setExPartition(partitionDef);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.OPTIONS)) {
                lexer.nextToken();
                accept(Token.LPAREN);

                stmt.putAttribute("ads.options", Boolean.TRUE);
                for (; ; ) {
                    String name = lexer.stringVal();
                    lexer.nextToken();
                    accept(Token.EQ);
                    SQLExpr value = this.exprParser.primary();
                    stmt.addOption(name, value);
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }

                accept(Token.RPAREN);
                continue;
            }

            if (lexer.identifierEquals(FnvHash.Constants.STORED)) {
                lexer.nextToken();
                accept(Token.BY);
                SQLName name = this.exprParser.name();
                stmt.setStoredBy(name);
            }

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

                for (; ; ) {
                    String name = lexer.stringVal();
                    lexer.nextToken();
                    accept(Token.EQ);
                    SQLName value = this.exprParser.name();
                    stmt.getWith().put(name, value);
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }

                accept(Token.RPAREN);
                continue;
            }

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

            break;
        }

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

        if (lexer.token() == Token.REPLACE) {
            lexer.nextToken();
            stmt.setReplace(true);
        } else if (lexer.identifierEquals("IGNORE")) {
            lexer.nextToken();
            stmt.setIgnore(true);
        }

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

            if (lexer.token() == Token.LPAREN) {
                lexer.nextToken();
                SQLSelect query = new MySqlSelectParser(this.exprParser).select();
                stmt.setSelect(query);
                accept(Token.RPAREN);
            }
        }

        SQLCommentHint hint = null;
        if (lexer.token() == Token.HINT) {
            hint = this.exprParser.parseHint();
        }

        if (lexer.token() == (Token.SELECT)) {
            SQLSelect query = new MySqlSelectParser(this.exprParser).select();
            if (hint != null) {
                query.setHeadHint(hint);
            }
            stmt.setSelect(query);

            if (lexer.token() == Token.WITH) {
                lexer.nextToken();
                if (lexer.identifierEquals(FnvHash.Constants.NO)) {
                    lexer.nextToken();
                    acceptIdentifier("DATA");
                    stmt.setWithData(false);
                } else {
                    acceptIdentifier("DATA");
                    stmt.setWithData(true);
                }
            }
        }

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

    public SQLPartitionBy parsePartitionBy() {
        lexer.nextToken();
        accept(Token.BY);

        SQLPartitionBy partitionClause;

        boolean linera = false;
        if (lexer.identifierEquals(FnvHash.Constants.LINEAR)) {
            lexer.nextToken();
            linera = true;
        }

        if (lexer.token() == Token.KEY) {
            MySqlPartitionByKey clause = new MySqlPartitionByKey();
            lexer.nextToken();

            if (linera) {
                clause.setLinear(true);
            }

            if (lexer.identifierEquals(FnvHash.Constants.ALGORITHM)) {
                lexer.nextToken();
                accept(Token.EQ);
                clause.setAlgorithm(lexer.integerValue().shortValue());
                lexer.nextToken();
            }

            accept(Token.LPAREN);
            if (lexer.token() != Token.RPAREN) {
                for (; ; ) {
                    clause.addColumn(this.exprParser.name());
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
            }
            accept(Token.RPAREN);

            partitionClause = clause;

            partitionClauseRest(clause);
        } else if (lexer.identifierEquals("HASH") || lexer.identifierEquals("UNI_HASH")) {
            SQLPartitionByHash clause = new SQLPartitionByHash();

            if (lexer.identifierEquals("UNI_HASH")) {
                clause.setUnique(true);
            }

            lexer.nextToken();

            if (linera) {
                clause.setLinear(true);
            }

            if (lexer.token() == Token.KEY) {
                lexer.nextToken();
                clause.setKey(true);
            }

            accept(Token.LPAREN);
            this.exprParser.exprList(clause.getColumns(), clause);
            accept(Token.RPAREN);
            partitionClause = clause;

            partitionClauseRest(clause);

        } else if (lexer.identifierEquals("RANGE")) {
            SQLPartitionByRange clause = partitionByRange();
            partitionClause = clause;

            partitionClauseRest(clause);

        } else if (lexer.identifierEquals("VALUE")) {
            SQLPartitionByValue clause = partitionByValue();
            partitionClause = clause;

            partitionClauseRest(clause);

        } else if (lexer.identifierEquals("LIST")) {
            lexer.nextToken();
            SQLPartitionByList clause = new SQLPartitionByList();

            if (lexer.token() == Token.LPAREN) {
                lexer.nextToken();
                clause.addColumn(this.exprParser.expr());
                accept(Token.RPAREN);
            } else {
                acceptIdentifier("COLUMNS");
                accept(Token.LPAREN);
                for (; ; ) {
                    clause.addColumn(this.exprParser.name());
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
                accept(Token.RPAREN);
            }
            partitionClause = clause;

            partitionClauseRest(clause);
        } else if (lexer.token() == Token.IDENTIFIER) {
            SQLPartitionByRange clause = partitionByRange();
            partitionClause = clause;

            partitionClauseRest(clause);
        } else {
            throw new ParserException("TODO. " + lexer.info());
        }

        if (lexer.identifierEquals(FnvHash.Constants.LIFECYCLE)) {
            lexer.nextToken();
            partitionClause.setLifecycle((SQLIntegerExpr) exprParser.expr());
        }

        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            for (; ; ) {
                SQLPartition partitionDef = this.getExprParser().parsePartition();

                partitionClause.addPartition(partitionDef);

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

    protected SQLPartitionByRange partitionByRange1() {

        acceptIdentifier("RANGE");

        SQLPartitionByRange clause = new SQLPartitionByRange();

        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            clause.addColumn(this.exprParser.expr());
            accept(Token.RPAREN);
        } else {
            acceptIdentifier("COLUMNS");
            accept(Token.LPAREN);
            for (; ; ) {
                clause.addColumn(this.exprParser.name());
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
        }
        return clause;
    }

    protected SQLPartitionByValue partitionByValue() {
        SQLPartitionByValue clause = new SQLPartitionByValue();
        if (lexer.identifierEquals(FnvHash.Constants.VALUE)) {
            lexer.nextToken();

            if (lexer.token() == Token.LPAREN) {
                lexer.nextToken();
                clause.addColumn(this.exprParser.expr());
                accept(Token.RPAREN);
            }
        }
        return clause;
    }

    protected SQLPartitionByRange partitionByRange() {
        SQLPartitionByRange clause = new SQLPartitionByRange();
        if (lexer.identifierEquals(FnvHash.Constants.RANGE)) {
            lexer.nextToken();

            if (lexer.token() == Token.LPAREN) {
                lexer.nextToken();
                clause.addColumn(this.exprParser.expr());
                accept(Token.RPAREN);
            } else {
                acceptIdentifier("COLUMNS");
                accept(Token.LPAREN);
                for (; ; ) {
                    clause.addColumn(this.exprParser.name());
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
                accept(Token.RPAREN);
            }
        } else {
            SQLExpr expr = this.exprParser.expr();
            if (lexer.identifierEquals(FnvHash.Constants.STARTWITH)) {
                lexer.nextToken();
                SQLExpr start = this.exprParser.primary();
                acceptIdentifier("ENDWITH");
                SQLExpr end = this.exprParser.primary();
                expr = new SQLBetweenExpr(expr, start, end);
            }
            clause.setInterval(expr);
        }

        return clause;
    }

    protected void partitionClauseRest(SQLPartitionBy clause) {
        if (lexer.identifierEquals(FnvHash.Constants.PARTITIONS) || lexer.identifierEquals(FnvHash.Constants.TBPARTITIONS) || lexer.identifierEquals(FnvHash.Constants.DBPARTITIONS)) {
            lexer.nextToken();
            SQLIntegerExpr countExpr = this.exprParser.integerExpr();
            clause.setPartitionsCount(countExpr);
        }

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

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

            clause.setPartitionsCount(this.exprParser.expr());

            clause.putAttribute("ads.partition", Boolean.TRUE);
        }

        if (lexer.identifierEquals(FnvHash.Constants.LIFECYCLE)) {
            lexer.nextToken();
            clause.setLifecycle((SQLIntegerExpr) exprParser.expr());
        }

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

            SQLSubPartitionBy subPartitionByClause = null;

            boolean linear = false;
            if (lexer.identifierEquals("LINEAR")) {
                lexer.nextToken();
                linear = true;
            }

            if (lexer.token() == Token.KEY) {
                MySqlSubPartitionByKey subPartitionKey = new MySqlSubPartitionByKey();
                lexer.nextToken();

                if (linear) {
                    clause.setLinear(true);
                }

                if (lexer.identifierEquals(FnvHash.Constants.ALGORITHM)) {
                    lexer.nextToken();
                    accept(Token.EQ);
                    subPartitionKey.setAlgorithm(lexer.integerValue().shortValue());
                    lexer.nextToken();
                }

                accept(Token.LPAREN);
                for (; ; ) {
                    subPartitionKey.addColumn(this.exprParser.name());
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
                accept(Token.RPAREN);

                subPartitionByClause = subPartitionKey;

            } else if (lexer.identifierEquals("VALUE")) {
                MySqlSubPartitionByValue subPartitionByValue = new MySqlSubPartitionByValue();
                lexer.nextToken();
                accept(Token.LPAREN);
                for (; ; ) {
                    subPartitionByValue.addColumn(this.exprParser.expr());
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
                accept(Token.RPAREN);

                subPartitionByClause = subPartitionByValue;

            } else if (lexer.identifierEquals("HASH")) {
                lexer.nextToken();
                SQLSubPartitionByHash subPartitionHash = new SQLSubPartitionByHash();

                if (linear) {
                    clause.setLinear(true);
                }

                if (lexer.token() == Token.KEY) {
                    lexer.nextToken();
                    subPartitionHash.setKey(true);
                }

                accept(Token.LPAREN);
                subPartitionHash.setExpr(this.exprParser.expr());
                accept(Token.RPAREN);
                subPartitionByClause = subPartitionHash;

            } else if (lexer.identifierEquals("LIST")) {
                lexer.nextToken();
                MySqlSubPartitionByList subPartitionList = new MySqlSubPartitionByList();

                //for ads
                if (lexer.token() == Token.KEY) {
                    lexer.nextToken();
                    accept(Token.LPAREN);

                    for (; ; ) {
                        SQLExpr expr = this.exprParser.expr();

                        if (expr instanceof SQLIdentifierExpr && (lexer.identifierEquals("bigint") || lexer.identifierEquals("long"))) {
                            String dataType = lexer.stringVal();
                            lexer.nextToken();

                            SQLColumnDefinition column = this.exprParser.createColumnDefinition();
                            column.setName((SQLIdentifierExpr) expr);
                            column.setDataType(new SQLDataTypeImpl(dataType));
                            subPartitionList.addColumn(column);

                            subPartitionList.putAttribute("ads.subPartitionList", Boolean.TRUE);
                        }

                        subPartitionList.addKey(expr);
                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    subPartitionList.putAttribute("ads.subPartitionList", Boolean.TRUE);
                    accept(Token.RPAREN);
                } else if (lexer.token() == Token.LPAREN) {
                    lexer.nextToken();

                    SQLExpr expr;
                    if (lexer.token() == Token.LITERAL_ALIAS) {
                        expr = new SQLIdentifierExpr(lexer.stringVal());
                        lexer.nextToken();
                    } else {
                        expr = this.exprParser.expr();
                    }

                    if (expr instanceof SQLIdentifierExpr && (lexer.identifierEquals("bigint") || lexer.identifierEquals("long"))) {
                        String dataType = lexer.stringVal();
                        lexer.nextToken();

                        SQLColumnDefinition column = this.exprParser.createColumnDefinition();
                        column.setName((SQLIdentifierExpr) expr);
                        column.setDataType(new SQLDataTypeImpl(dataType));
                        subPartitionList.addColumn(column);

                        subPartitionList.putAttribute("ads.subPartitionList", Boolean.TRUE);
                    } else {
                        subPartitionList.addKey(expr);
                    }
                    accept(Token.RPAREN);
                } else {
                    acceptIdentifier("COLUMNS");
                    accept(Token.LPAREN);
                    for (; ; ) {
                        subPartitionList.addColumn(this.exprParser.parseColumn());
                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    accept(Token.RPAREN);
                }
                subPartitionByClause = subPartitionList;
            } else if (lexer.identifierEquals(FnvHash.Constants.RANGE)) {
                lexer.nextToken();
                SQLSubPartitionByRange range = new SQLSubPartitionByRange();

                accept(Token.LPAREN);
                this.exprParser.exprList(range.getColumns(), range);
                accept(Token.RPAREN);
                subPartitionByClause = range;
            }


            if (subPartitionByClause != null) {

                if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITION)) {
                    lexer.nextToken();
                    acceptIdentifier("OPTIONS");
                    this.exprParser.parseAssignItem(subPartitionByClause.getOptions(), subPartitionByClause);
                }

                if (lexer.identifierEquals(FnvHash.Constants.SUBPARTITIONS)) {
                    lexer.nextToken();
                    Number intValue = lexer.integerValue();
                    SQLNumberExpr numExpr = new SQLNumberExpr(intValue);
                    subPartitionByClause.setSubPartitionsCount(numExpr);
                    lexer.nextToken();
                } else if (lexer.identifierEquals(FnvHash.Constants.PARTITIONS)) { // ADB
                    lexer.nextToken();
                    subPartitionByClause.setSubPartitionsCount((SQLIntegerExpr) exprParser.expr());
                    subPartitionByClause.getAttributes().put("adb.partitons", true);
                }

                if (lexer.identifierEquals(FnvHash.Constants.LIFECYCLE)) {
                    lexer.nextToken();
                    subPartitionByClause.setLifecycle((SQLIntegerExpr) exprParser.expr());
                }

                subPartitionByClause.setLinear(linear);

                clause.setSubPartitionBy(subPartitionByClause);
            }
        }
    }

    private boolean parseTableOptionCharsetOrCollate(MySqlCreateTableStatement stmt) {
        if (lexer.identifierEquals("CHARACTER")) {
            lexer.nextToken();
            accept(Token.SET);
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLExpr charset;
            if (lexer.token() == Token.IDENTIFIER) {
                charset = new SQLIdentifierExpr(lexer.stringVal());
                lexer.nextToken();
            } else if (lexer.token() == Token.LITERAL_CHARS) {
                charset = new SQLCharExpr(lexer.stringVal());
                lexer.nextToken();
            } else {
                charset = this.exprParser.primary();
            }
            stmt.addOption("CHARACTER SET", charset);
            return true;
        }

        if (lexer.identifierEquals("CHARSET")) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            SQLExpr charset;
            if (lexer.token() == Token.IDENTIFIER) {
                charset = new SQLIdentifierExpr(lexer.stringVal());
                lexer.nextToken();
            } else if (lexer.token() == Token.LITERAL_CHARS) {
                charset = new SQLCharExpr(lexer.stringVal());
                lexer.nextToken();
            } else {
                charset = this.exprParser.primary();
            }
            stmt.addOption("CHARSET", charset);
            return true;
        }

        if (lexer.identifierEquals("COLLATE")) {
            lexer.nextToken();
            if (lexer.token() == Token.EQ) {
                lexer.nextToken();
            }
            stmt.addOption("COLLATE", this.exprParser.expr());
            return true;
        }

        return false;
    }

    protected SQLTableConstraint parseConstraint() {
        SQLName name = null;
        boolean hasConstaint = false;
        if (lexer.token() == (Token.CONSTRAINT)) {
            hasConstaint = true;
            lexer.nextToken();
        }

        if (lexer.token() == Token.IDENTIFIER) {
            name = this.exprParser.name();
        }

        SQLTableConstraint constraint = null;

        if (lexer.token() == (Token.KEY)) {
            MySqlKey key = new MySqlKey();
            this.exprParser.parseIndex(key.getIndexDefinition());
            key.setHasConstraint(hasConstaint);

            if (name != null) {
                key.setName(name);
            }

            constraint = key;
        } else if (lexer.token() == Token.PRIMARY) {
            MySqlPrimaryKey pk = this.getExprParser().parsePrimaryKey();
            if (name != null) {
                pk.setName(name);
            }
            pk.setHasConstraint(hasConstaint);
            constraint = pk;
        } else if (lexer.token() == Token.UNIQUE) {
            MySqlUnique uk = this.getExprParser().parseUnique();
            // should not use CONSTRAINT [symbol] for index name if index_name already specified
            if (name != null && uk.getName() == null) {
                uk.setName(name);
            }

            uk.setHasConstraint(hasConstaint);

            constraint = uk;
        } else if (lexer.token() == Token.FOREIGN) {
            MysqlForeignKey fk = this.getExprParser().parseForeignKey();
            fk.setName(name);
            fk.setHasConstraint(hasConstaint);
            constraint = fk;
        } else if (lexer.token() == Token.CHECK) {
            lexer.nextToken();
            SQLCheck check = new SQLCheck();
            check.setName(name);
            SQLExpr expr = this.exprParser.primary();
            check.setExpr(expr);
            constraint = check;

            boolean enforce = true;
            if (Token.NOT.equals(lexer.token())) {
                enforce = false;
                lexer.nextToken();
            }
            if (lexer.stringVal().equalsIgnoreCase("ENFORCED")) {
                check.setEnforced(enforce);
                lexer.nextToken();
            }
            if (lexer.token() == Token.HINT) {
                String hintText = lexer.stringVal();
                if (hintText != null) {
                    hintText = hintText.trim();
                    if (hintText.startsWith("!")) {
                        if (hintText.endsWith("NOT ENFORCED")) {
                            check.setEnforced(false);
                        } else if (hintText.endsWith(" ENFORCED")) {
                            check.setEnforced(true);
                        }
                        lexer.nextToken();
                    }
                }

            }
        }

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

            return constraint;
        }

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

