package com.chenyang.druid.sql.parser;

import com.chenyang.druid.DbType;
import com.chenyang.druid.sql.SQLUtils;
import com.chenyang.druid.sql.ast.AutoIncrementType;
import com.chenyang.druid.sql.ast.SQLArrayDataType;
import com.chenyang.druid.sql.ast.SQLCommentHint;
import com.chenyang.druid.sql.ast.SQLCurrentTimeExpr;
import com.chenyang.druid.sql.ast.SQLCurrentUserExpr;
import com.chenyang.druid.sql.ast.SQLDataType;
import com.chenyang.druid.sql.ast.SQLDataTypeImpl;
import com.chenyang.druid.sql.ast.SQLDataTypeRefExpr;
import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLExprImpl;
import com.chenyang.druid.sql.ast.SQLHint;
import com.chenyang.druid.sql.ast.SQLIndex;
import com.chenyang.druid.sql.ast.SQLIndexDefinition;
import com.chenyang.druid.sql.ast.SQLLimit;
import com.chenyang.druid.sql.ast.SQLMapDataType;
import com.chenyang.druid.sql.ast.SQLName;
import com.chenyang.druid.sql.ast.SQLObject;
import com.chenyang.druid.sql.ast.SQLObjectImpl;
import com.chenyang.druid.sql.ast.SQLOrderBy;
import com.chenyang.druid.sql.ast.SQLOrderingSpecification;
import com.chenyang.druid.sql.ast.SQLOver;
import com.chenyang.druid.sql.ast.SQLPartition;
import com.chenyang.druid.sql.ast.SQLPartitionBy;
import com.chenyang.druid.sql.ast.SQLPartitionByValue;
import com.chenyang.druid.sql.ast.SQLPartitionSpec;
import com.chenyang.druid.sql.ast.SQLPartitionValue;
import com.chenyang.druid.sql.ast.SQLRowDataType;
import com.chenyang.druid.sql.ast.SQLStructDataType;
import com.chenyang.druid.sql.ast.SQLUnionDataType;
import com.chenyang.druid.sql.ast.TDDLHint;
import com.chenyang.druid.sql.ast.expr.SQLAggregateExpr;
import com.chenyang.druid.sql.ast.expr.SQLAggregateOption;
import com.chenyang.druid.sql.ast.expr.SQLAllColumnExpr;
import com.chenyang.druid.sql.ast.expr.SQLAllExpr;
import com.chenyang.druid.sql.ast.expr.SQLAnyExpr;
import com.chenyang.druid.sql.ast.expr.SQLArrayExpr;
import com.chenyang.druid.sql.ast.expr.SQLBetweenExpr;
import com.chenyang.druid.sql.ast.expr.SQLBigIntExpr;
import com.chenyang.druid.sql.ast.expr.SQLBinaryExpr;
import com.chenyang.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.chenyang.druid.sql.ast.expr.SQLBinaryOpExprGroup;
import com.chenyang.druid.sql.ast.expr.SQLBinaryOperator;
import com.chenyang.druid.sql.ast.expr.SQLBooleanExpr;
import com.chenyang.druid.sql.ast.expr.SQLCaseExpr;
import com.chenyang.druid.sql.ast.expr.SQLCastExpr;
import com.chenyang.druid.sql.ast.expr.SQLCharExpr;
import com.chenyang.druid.sql.ast.expr.SQLContainsExpr;
import com.chenyang.druid.sql.ast.expr.SQLCurrentOfCursorExpr;
import com.chenyang.druid.sql.ast.expr.SQLDateExpr;
import com.chenyang.druid.sql.ast.expr.SQLDateTimeExpr;
import com.chenyang.druid.sql.ast.expr.SQLDbLinkExpr;
import com.chenyang.druid.sql.ast.expr.SQLDecimalExpr;
import com.chenyang.druid.sql.ast.expr.SQLDefaultExpr;
import com.chenyang.druid.sql.ast.expr.SQLDoubleExpr;
import com.chenyang.druid.sql.ast.expr.SQLExistsExpr;
import com.chenyang.druid.sql.ast.expr.SQLFloatExpr;
import com.chenyang.druid.sql.ast.expr.SQLGroupingSetExpr;
import com.chenyang.druid.sql.ast.expr.SQLHexExpr;
import com.chenyang.druid.sql.ast.expr.SQLIdentifierExpr;
import com.chenyang.druid.sql.ast.expr.SQLInListExpr;
import com.chenyang.druid.sql.ast.expr.SQLInSubQueryExpr;
import com.chenyang.druid.sql.ast.expr.SQLIntegerExpr;
import com.chenyang.druid.sql.ast.expr.SQLIntervalExpr;
import com.chenyang.druid.sql.ast.expr.SQLIntervalUnit;
import com.chenyang.druid.sql.ast.expr.SQLJSONExpr;
import com.chenyang.druid.sql.ast.expr.SQLListExpr;
import com.chenyang.druid.sql.ast.expr.SQLMatchAgainstExpr;
import com.chenyang.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.chenyang.druid.sql.ast.expr.SQLNCharExpr;
import com.chenyang.druid.sql.ast.expr.SQLNotExpr;
import com.chenyang.druid.sql.ast.expr.SQLNullExpr;
import com.chenyang.druid.sql.ast.expr.SQLNumericLiteralExpr;
import com.chenyang.druid.sql.ast.expr.SQLPropertyExpr;
import com.chenyang.druid.sql.ast.expr.SQLQueryExpr;
import com.chenyang.druid.sql.ast.expr.SQLRealExpr;
import com.chenyang.druid.sql.ast.expr.SQLSequenceExpr;
import com.chenyang.druid.sql.ast.expr.SQLSmallIntExpr;
import com.chenyang.druid.sql.ast.expr.SQLSomeExpr;
import com.chenyang.druid.sql.ast.expr.SQLTimeExpr;
import com.chenyang.druid.sql.ast.expr.SQLTimestampExpr;
import com.chenyang.druid.sql.ast.expr.SQLTinyIntExpr;
import com.chenyang.druid.sql.ast.expr.SQLUnaryExpr;
import com.chenyang.druid.sql.ast.expr.SQLUnaryOperator;
import com.chenyang.druid.sql.ast.expr.SQLVariantRefExpr;
import com.chenyang.druid.sql.ast.statement.SQLAssignItem;
import com.chenyang.druid.sql.ast.statement.SQLCharacterDataType;
import com.chenyang.druid.sql.ast.statement.SQLCheck;
import com.chenyang.druid.sql.ast.statement.SQLColumnCheck;
import com.chenyang.druid.sql.ast.statement.SQLColumnDefinition;
import com.chenyang.druid.sql.ast.statement.SQLColumnPrimaryKey;
import com.chenyang.druid.sql.ast.statement.SQLColumnReference;
import com.chenyang.druid.sql.ast.statement.SQLColumnUniqueKey;
import com.chenyang.druid.sql.ast.statement.SQLConstraint;
import com.chenyang.druid.sql.ast.statement.SQLDB2TimeIntervalDataType;
import com.chenyang.druid.sql.ast.statement.SQLDefault;
import com.chenyang.druid.sql.ast.statement.SQLExprTableSource;
import com.chenyang.druid.sql.ast.statement.SQLExternalRecordFormat;
import com.chenyang.druid.sql.ast.statement.SQLForeignKeyConstraint;
import com.chenyang.druid.sql.ast.statement.SQLForeignKeyImpl;
import com.chenyang.druid.sql.ast.statement.SQLNotNullConstraint;
import com.chenyang.druid.sql.ast.statement.SQLNullConstraint;
import com.chenyang.druid.sql.ast.statement.SQLPrimaryKey;
import com.chenyang.druid.sql.ast.statement.SQLPrimaryKeyImpl;
import com.chenyang.druid.sql.ast.statement.SQLSelect;
import com.chenyang.druid.sql.ast.statement.SQLSelectItem;
import com.chenyang.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.chenyang.druid.sql.ast.statement.SQLSelectQuery;
import com.chenyang.druid.sql.ast.statement.SQLUnique;
import com.chenyang.druid.sql.ast.statement.SQLUpdateSetItem;
import com.chenyang.druid.sql.dialect.db2.ast.expr.DB2CurrentTimeExpr;
import com.chenyang.druid.sql.dialect.mysql.ast.expr.MySqlCharExpr;
import com.chenyang.druid.sql.dialect.mysql.ast.expr.MySqlIntervalExpr;
import com.chenyang.druid.sql.dialect.oracle.ast.expr.OracleArgumentExpr;
import com.chenyang.druid.sql.dialect.oracle.ast.expr.OracleMultisetExpr;
import com.chenyang.druid.util.FnvHash;
import com.chenyang.druid.util.HexBin;
import com.chenyang.druid.util.MySqlUtils;
import com.chenyang.druid.util.StringUtils;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public class SQLExprParser extends SQLParser {
    public static final String[] AGGREGATE_FUNCTIONS;
    public static final long[] AGGREGATE_FUNCTIONS_CODES;
    protected String[] aggregateFunctions;
    protected long[] aggregateFunctionHashCodes;
    protected boolean allowIdentifierMethod;

    public SQLExprParser(String sql) {
        super(sql);
        this.aggregateFunctions = AGGREGATE_FUNCTIONS;
        this.aggregateFunctionHashCodes = AGGREGATE_FUNCTIONS_CODES;
        this.allowIdentifierMethod = true;
    }

    public SQLExprParser(String sql, DbType dbType, SQLParserFeature... features) {
        super(sql, dbType, features);
        this.aggregateFunctions = AGGREGATE_FUNCTIONS;
        this.aggregateFunctionHashCodes = AGGREGATE_FUNCTIONS_CODES;
        this.allowIdentifierMethod = true;
    }

    public SQLExprParser(Lexer lexer) {
        super(lexer);
        this.aggregateFunctions = AGGREGATE_FUNCTIONS;
        this.aggregateFunctionHashCodes = AGGREGATE_FUNCTIONS_CODES;
        this.allowIdentifierMethod = true;
    }

    public SQLExprParser(Lexer lexer, DbType dbType) {
        super(lexer, dbType);
        this.aggregateFunctions = AGGREGATE_FUNCTIONS;
        this.aggregateFunctionHashCodes = AGGREGATE_FUNCTIONS_CODES;
        this.allowIdentifierMethod = true;
    }

    public void setAllowIdentifierMethod(boolean allowIdentifierMethod) {
        this.allowIdentifierMethod = allowIdentifierMethod;
    }

    public SQLExpr expr() {
        if (this.lexer.token == Token.STAR) {
            this.lexer.nextToken();
            SQLExpr expr = new SQLAllColumnExpr();
            if (this.lexer.token == Token.DOT) {
                this.lexer.nextToken();
                this.accept(Token.STAR);
                return new SQLPropertyExpr(expr, "*");
            } else {
                return expr;
            }
        } else {
            if (this.dbType == DbType.mysql && this.lexer.token == Token.INTERVAL) {
                this.lexer.mark();
                this.lexer.nextToken();
                if (this.lexer.token != Token.LPAREN) {
                    MySqlIntervalExpr interval = new MySqlIntervalExpr();
                    SQLExpr value = this.expr();
                    SQLExpr unit = this.expr();
                    interval.setUnit(unit);
                    interval.setValue(value);
                    return interval;
                }

                this.lexer.reset();
            }

            SQLExpr expr = this.primary();
            Token token = this.lexer.token;
            if (token == Token.COMMA) {
                return expr;
            } else if (token == Token.EQ) {
                expr = this.relationalRest(expr);
                expr = this.andRest(expr);
                expr = this.xorRest(expr);
                expr = this.orRest(expr);
                return expr;
            } else {
                return this.exprRest(expr);
            }
        }
    }

    public SQLExpr exprRest(SQLExpr expr) {
        expr = this.bitXorRest(expr);
        expr = this.multiplicativeRest(expr);
        expr = this.additiveRest(expr);
        expr = this.shiftRest(expr);
        expr = this.bitAndRest(expr);
        expr = this.bitOrRest(expr);
        expr = this.inRest(expr);
        expr = this.relationalRest(expr);
        expr = this.andRest(expr);
        expr = this.xorRest(expr);
        expr = this.orRest(expr);
        return expr;
    }

    public final SQLExpr bitXor() {
        SQLExpr expr = this.primary();
        return this.bitXorRest(expr);
    }

    public SQLExpr bitXorRest(SQLExpr expr) {
        Token token = this.lexer.token;
        switch (token) {
            case CARET: {
                this.lexer.nextToken();
                SQLBinaryOperator op;
                if (this.lexer.token == Token.EQ) {
                    this.lexer.nextToken();
                    op = SQLBinaryOperator.BitwiseXorEQ;
                } else {
                    op = SQLBinaryOperator.BitwiseXor;
                }

                SQLExpr rightExp = this.primary();
                expr = new SQLBinaryOpExpr(expr, op, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
                break;
            }
            case SUBGT: {
                this.lexer.nextToken();
                SQLExpr rightExp;
                if (this.dbType == DbType.mysql) {
                    if (this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.LITERAL_ALIAS) {
                        rightExp = this.expr();
                    } else {
                        rightExp = this.primary();
                    }
                } else {
                    rightExp = this.primary();
                }

                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.SubGt, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
                break;
            }
            case LT_SUB_GT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.primary();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.PG_ST_DISTANCE, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
                break;
            }
            case SUBGTGT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.primary();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.SubGtGt, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
                break;
            }
            case POUNDGT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.primary();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.PoundGt, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
                break;
            }
            case POUNDGTGT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.primary();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.PoundGtGt, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
                break;
            }
            case QUESQUES: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.primary();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.QuesQues, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
                break;
            }
            case QUESBAR: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.primary();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.QuesBar, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
                break;
            }
            case QUESAMP: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.primary();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.QuesAmp, rightExp, this.dbType);
                expr = this.bitXorRest(expr);
            }
        }

        return expr;
    }

    public final SQLExpr multiplicative() {
        SQLExpr expr = this.bitXor();
        return this.multiplicativeRest(expr);
    }

    public SQLExpr multiplicativeRest(SQLExpr expr) {
        Token token = this.lexer.token;
        if (token == Token.STAR) {
            this.lexer.nextToken();
            SQLExpr rightExp = this.bitXor();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Multiply, rightExp, this.getDbType());
            expr = this.multiplicativeRest(expr);
        } else if (token == Token.SLASH) {
            this.lexer.nextToken();
            SQLExpr rightExp = this.bitXor();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Divide, rightExp, this.getDbType());
            expr = this.multiplicativeRest(expr);
        } else if (token == Token.PERCENT) {
            this.lexer.nextToken();
            SQLExpr rightExp = this.bitXor();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Modulus, rightExp, this.getDbType());
            expr = this.multiplicativeRest(expr);
        } else if (token == Token.DIV) {
            this.lexer.nextToken();
            SQLExpr rightExp = this.bitXor();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.DIV, rightExp, this.getDbType());
            expr = this.multiplicativeRest(expr);
        } else if (!this.lexer.identifierEquals(FnvHash.Constants.MOD) && this.lexer.token != Token.MOD) {
            if (token == Token.LITERAL_INT && this.lexer.isNegativeIntegerValue()) {
                Number number = this.lexer.integerValue();
                Object rightExp;
                if (number instanceof Integer) {
                    rightExp = -number.intValue();
                } else if (number instanceof Long) {
                    rightExp = -number.longValue();
                } else {
                    if (!(number instanceof BigInteger)) {
                        throw new ParserException("not support value : " + number + ", " + this.lexer.info());
                    }

                    rightExp = ((BigInteger) number).abs();
                }

                rightExp = new SQLIntegerExpr((Number) rightExp);
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Subtract, (SQLExpr) rightExp, this.dbType);
                this.lexer.nextToken();
                expr = this.multiplicativeRest(expr);
            }
        } else {
            Lexer.SavePoint savePoint = this.lexer.mark();
            this.lexer.nextToken();
            if (this.lexer.token == Token.COMMA || this.lexer.token == Token.EOF) {
                this.lexer.reset(savePoint);
                return expr;
            }

            SQLExpr rightExp = this.bitXor();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Modulus, rightExp, this.dbType);
            expr = this.multiplicativeRest(expr);
        }

        return expr;
    }

    public SQLIntegerExpr integerExpr() {
        SQLIntegerExpr intExpr = null;
        if (this.lexer.token() == Token.SUB) {
            this.lexer.nextToken();
            intExpr = new SQLIntegerExpr(this.lexer.integerValue().longValue() * -1L);
        } else {
            intExpr = new SQLIntegerExpr(this.lexer.integerValue());
        }

        this.accept(Token.LITERAL_INT);
        return intExpr;
    }

    public SQLCharExpr charExpr() {
        SQLCharExpr charExpr = new SQLCharExpr(this.lexer.stringVal());
        this.accept(Token.LITERAL_CHARS);
        return charExpr;
    }

    public int parseIntValue() {
        if (this.lexer.token == Token.LITERAL_INT) {
            Number number = this.lexer.integerValue();
            int intVal = (Integer) number;
            this.lexer.nextToken();
            return intVal;
        } else {
            throw new ParserException("not int. " + this.lexer.info());
        }
    }

    public SQLExpr primary() {
        List<String> beforeComments = null;
        if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
            beforeComments = this.lexer.readAndResetComments();
        }

        SQLExpr sqlExpr;
        sqlExpr = null;
        Token tok = this.lexer.token;
        label588:
        switch (tok) {
            case LITERAL_INT:
                sqlExpr = new SQLIntegerExpr(this.lexer.integerValue());
                this.lexer.nextToken();
                break;
            case LITERAL_FLOAT:
                sqlExpr = this.lexer.numberExpr();
                this.lexer.nextToken();
                break;
            case LITERAL_CHARS:
                sqlExpr = new SQLCharExpr(this.lexer.stringVal());
                if (DbType.mysql == this.dbType) {
                    this.lexer.nextTokenValue();

                    while (true) {
                        while (this.lexer.token != Token.LITERAL_ALIAS) {
                            if (this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.LITERAL_NCHARS) {
                                break label588;
                            }

                            String concat = ((SQLCharExpr) sqlExpr).getText();
                            concat = concat + this.lexer.stringVal();
                            this.lexer.nextTokenValue();
                            sqlExpr = new SQLCharExpr(concat);
                        }

                        String concat = ((SQLCharExpr) sqlExpr).getText();
                        concat = concat + this.lexer.stringVal();
                        this.lexer.nextTokenValue();
                        sqlExpr = new SQLCharExpr(concat);
                    }
                } else {
                    this.lexer.nextToken();
                    break;
                }
            case LITERAL_ALIAS:
                sqlExpr = this.parseAliasExpr(this.lexer.stringVal());
                this.lexer.nextToken();
                break;
            case QUES:
                if (this.dbType == DbType.mysql) {
                    this.lexer.nextTokenValue();
                } else {
                    this.lexer.nextToken();
                }

                SQLVariantRefExpr quesVarRefExpr = new SQLVariantRefExpr("?");
                quesVarRefExpr.setIndex(this.lexer.nextVarIndex());
                sqlExpr = quesVarRefExpr;
                break;
            case PLUS:
                this.lexer.nextToken();
                switch (this.lexer.token) {
                    case LITERAL_INT:
                    case LITERAL_FLOAT:
                    case PLUS:
                    case SUB:
                    case LPAREN:
                    case IDENTIFIER:
                    case BANG:
                    case CASE:
                    case CAST:
                    case NULL:
                    case INTERVAL:
                    case LBRACE:
                        sqlExpr = this.primary();

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

                        sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Plus, sqlExpr);
                        break label588;
                    case LITERAL_CHARS:
                    case LITERAL_ALIAS:
                        SQLExpr var25 = new SQLIdentifierExpr(this.lexer.stringVal());
                        sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Plus, var25);
                        this.lexer.nextToken();
                        break label588;
                    case QUES:
                        SQLVariantRefExpr variantRefExpr = new SQLVariantRefExpr("?");
                        variantRefExpr.setIndex(this.lexer.nextVarIndex());
                        sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Plus, variantRefExpr);
                        this.lexer.nextToken();
                        break label588;
                    default:
                        throw new ParserException("TODO " + this.lexer.info());
                }
            case SUB:
                this.lexer.nextToken();
                switch (this.lexer.token) {
                    case LITERAL_DBNUMBER:
                        this.lexer.nextToken();
                        break label588;
                    case LITERAL_INT:
                        Number integerValue = this.lexer.integerValue();
                        Object unaryValueExpr;
                        if (integerValue instanceof Integer) {
                            int intVal = integerValue.intValue();
                            if (intVal == Integer.MIN_VALUE) {
                                unaryValueExpr = (long) intVal * -1L;
                            } else {
                                unaryValueExpr = intVal * -1;
                            }
                        } else if (integerValue instanceof Long) {
                            long longVal = (Long) integerValue;
                            if (longVal == 2147483648L) {
                                unaryValueExpr = (int) (longVal * -1L);
                            } else {
                                unaryValueExpr = longVal * -1L;
                            }
                        } else {
                            unaryValueExpr = ((BigInteger) integerValue).negate();
                        }

                        sqlExpr = new SQLIntegerExpr((Number) unaryValueExpr);
                        this.lexer.nextToken();
                        break label588;
                    case LITERAL_FLOAT:
                        sqlExpr = this.lexer.numberExpr(true);
                        this.lexer.nextToken();
                        break label588;
                    case LITERAL_CHARS:
                    case LITERAL_ALIAS:
                        if (this.dbType == DbType.mysql) {
                            sqlExpr = new SQLCharExpr(this.lexer.stringVal());
                        } else {
                            sqlExpr = new SQLIdentifierExpr(this.lexer.stringVal());
                        }

                        this.lexer.nextToken();
                        if (this.lexer.token == Token.LPAREN || this.lexer.token == Token.LBRACKET || this.lexer.token == Token.DOT) {
                            sqlExpr = this.primaryRest(sqlExpr);
                        }

                        sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Negative, sqlExpr);
                        break label588;
                    case QUES:
                        SQLVariantRefExpr variantRefExpr = new SQLVariantRefExpr("?");
                        variantRefExpr.setIndex(this.lexer.nextVarIndex());
                        sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Negative, variantRefExpr);
                        this.lexer.nextToken();
                        break label588;
                    case PLUS:
                    case SUB:
                    case LPAREN:
                    case IDENTIFIER:
                    case BANG:
                    case CASE:
                    case CAST:
                    case NULL:
                    case INTERVAL:
                    case LBRACE:
                    case IF:
                        sqlExpr = this.primary();

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

                        sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Negative, sqlExpr);
                        break label588;
                    case VARIANT:
                        sqlExpr = this.primary();
                        sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Negative, sqlExpr);
                        break label588;
                    case DAY:
                        sqlExpr = this.expr();
                        break label588;
                    default:
                        throw new ParserException("TODO : " + this.lexer.info());
                }
            case LPAREN:
                int paranCount = 0;
                this.lexer.nextToken();
                sqlExpr = this.expr();
                if (this.lexer.token == Token.COMMA) {
                    SQLListExpr listExpr = new SQLListExpr();
                    listExpr.addItem(sqlExpr);

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

                    sqlExpr = listExpr;
                }

                if (sqlExpr instanceof SQLBinaryOpExpr) {
                    ((SQLBinaryOpExpr) sqlExpr).setBracket(true);
                }

                if ((this.lexer.token == Token.UNION || this.lexer.token == Token.MINUS || this.lexer.token == Token.EXCEPT) && sqlExpr instanceof SQLQueryExpr) {
                    SQLQueryExpr queryExpr = (SQLQueryExpr) sqlExpr;
                    SQLSelectQuery query = this.createSelectParser().queryRest(queryExpr.getSubQuery().getQuery(), true);
                    queryExpr.getSubQuery().setQuery(query);
                }

                this.accept(Token.RPAREN);
                break;
            case IDENTIFIER:
                String ident = this.lexer.stringVal();
                long hash_lower = this.lexer.hash_lower();
                int sourceLine = -1;
                int sourceColumn = -1;
                if (this.lexer.keepSourceLocation) {
                    this.lexer.computeRowAndColumn();
                    sourceLine = this.lexer.posLine;
                    sourceColumn = this.lexer.posColumn;
                }

                this.lexer.nextToken();
                if (hash_lower == FnvHash.Constants.TRY_CAST) {
                    this.accept(Token.LPAREN);
                    SQLCastExpr cast = new SQLCastExpr();
                    cast.setTry(true);
                    cast.setExpr(this.expr());
                    this.accept(Token.AS);
                    cast.setDataType(this.parseDataType(false));
                    this.accept(Token.RPAREN);
                    sqlExpr = cast;
                } else if (hash_lower == FnvHash.Constants.DATE && (this.lexer.token == Token.LITERAL_CHARS || this.lexer.token == Token.VARIANT) && SQLDateExpr.isSupport(this.dbType)) {
                    String literal = this.lexer.token == Token.LITERAL_CHARS ? this.lexer.stringVal() : "?";
                    this.lexer.nextToken();
                    SQLDateExpr dateExpr = new SQLDateExpr();
                    dateExpr.setLiteral(literal);
                    sqlExpr = dateExpr;
                } else if (hash_lower == FnvHash.Constants.TIMESTAMP && (this.lexer.token == Token.LITERAL_CHARS || this.lexer.token == Token.VARIANT) && DbType.oracle != this.dbType) {
                    SQLTimestampExpr dateExpr = new SQLTimestampExpr(this.lexer.stringVal());
                    this.lexer.nextToken();
                    sqlExpr = dateExpr;
                } else if (hash_lower != FnvHash.Constants.TIME || this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.VARIANT) {
                    if (hash_lower == FnvHash.Constants.TIME && this.lexer.token == Token.LITERAL_ALIAS) {
                        SQLTimeExpr dateExpr = new SQLTimeExpr(SQLUtils.normalize(this.lexer.stringVal()));
                        this.lexer.nextToken();
                        sqlExpr = dateExpr;
                    } else if (hash_lower != FnvHash.Constants.DATETIME || this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.VARIANT) {
                        if (hash_lower == FnvHash.Constants.DATETIME && this.lexer.token == Token.LITERAL_ALIAS) {
                            SQLDateTimeExpr dateExpr = new SQLDateTimeExpr(SQLUtils.normalize(this.lexer.stringVal()));
                            this.lexer.nextToken();
                            sqlExpr = dateExpr;
                        } else if (hash_lower == FnvHash.Constants.BOOLEAN && this.lexer.token == Token.LITERAL_CHARS) {
                            sqlExpr = new SQLBooleanExpr(Boolean.valueOf(this.lexer.stringVal()));
                            this.lexer.nextToken();
                        } else if (hash_lower == FnvHash.Constants.VARCHAR && this.lexer.token == Token.LITERAL_CHARS) {
                            if (this.dbType == DbType.mysql) {
                                MySqlCharExpr mysqlChar = new MySqlCharExpr(this.lexer.stringVal());
                                mysqlChar.setType("VARCHAR");
                                sqlExpr = mysqlChar;
                            } else {
                                sqlExpr = new SQLCharExpr(this.lexer.stringVal());
                            }

                            this.lexer.nextToken();
                        } else if (hash_lower == FnvHash.Constants.CHAR && this.lexer.token == Token.LITERAL_CHARS) {
                            if (this.dbType == DbType.mysql) {
                                MySqlCharExpr mysqlChar = new MySqlCharExpr(this.lexer.stringVal());
                                mysqlChar.setType("CHAR");
                                sqlExpr = mysqlChar;
                            } else {
                                sqlExpr = new SQLCharExpr(this.lexer.stringVal());
                            }

                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && ident.startsWith("0x") && ident.length() % 2 == 0) {
                            sqlExpr = new SQLHexExpr(ident.substring(2));
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.JSON && this.lexer.token == Token.LITERAL_CHARS) {
                            sqlExpr = new SQLJSONExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.DECIMAL && this.lexer.token == Token.LITERAL_CHARS) {
                            sqlExpr = new SQLDecimalExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.DOUBLE && this.lexer.token == Token.LITERAL_CHARS) {
                            sqlExpr = new SQLDoubleExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.FLOAT && this.lexer.token == Token.LITERAL_CHARS) {
                            sqlExpr = new SQLFloatExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.SMALLINT && this.lexer.token == Token.LITERAL_CHARS) {
                            sqlExpr = new SQLSmallIntExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.TINYINT && this.lexer.token == Token.LITERAL_CHARS) {
                            sqlExpr = new SQLTinyIntExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.BIGINT && this.lexer.token == Token.LITERAL_CHARS) {
                            String strVal = this.lexer.stringVal();
                            if (strVal.startsWith("--")) {
                                strVal = strVal.substring(2);
                            }

                            sqlExpr = new SQLBigIntExpr(strVal);
                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.INTEGER && this.lexer.token == Token.LITERAL_CHARS) {
                            String strVal = this.lexer.stringVal();
                            if (strVal.startsWith("--")) {
                                strVal = strVal.substring(2);
                            }

                            SQLIntegerExpr integerExpr = SQLIntegerExpr.ofIntOrLong(Long.parseLong(strVal));
                            integerExpr.setType("INTEGER");
                            sqlExpr = integerExpr;
                            this.lexer.nextToken();
                        } else if (DbType.mysql == this.dbType && hash_lower == FnvHash.Constants.REAL && this.lexer.token == Token.LITERAL_CHARS) {
                            sqlExpr = new SQLRealExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                        } else {
                            char c0 = ident.charAt(0);
                            if (c0 == '`' || c0 == '[' || c0 == '"') {
                                if (this.lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                                    ident = ident.substring(1, ident.length() - 1);
                                }

                                hash_lower = FnvHash.hashCode64(ident);
                            }

                            SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr(ident, hash_lower);
                            if (sourceLine != -1) {
                                identifierExpr.setSourceLine(sourceLine);
                                identifierExpr.setSourceColumn(sourceColumn);
                            }

                            sqlExpr = identifierExpr;
                        }
                    } else {
                        SQLDateTimeExpr dateExpr = new SQLDateTimeExpr(this.lexer.stringVal());
                        this.lexer.nextToken();
                        sqlExpr = dateExpr;
                    }
                } else {
                    SQLTimeExpr dateExpr = new SQLTimeExpr(this.lexer.stringVal());
                    this.lexer.nextToken();
                    sqlExpr = dateExpr;
                }
                break;
            case BANG:
                this.lexer.nextToken();
                sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Not, this.primary());
                break;
            case CASE:
                SQLCaseExpr caseExpr = new SQLCaseExpr();
                this.lexer.nextToken();
                if (this.lexer.token != Token.WHEN) {
                    caseExpr.setValueExpr(this.expr());
                }

                this.accept(Token.WHEN);
                SQLExpr testExpr = this.expr();
                this.accept(Token.THEN);
                SQLExpr valueExpr = this.expr();
                SQLCaseExpr.Item caseItem = new SQLCaseExpr.Item(testExpr, valueExpr);
                caseExpr.addItem(caseItem);

                while (this.lexer.token == Token.WHEN) {
                    this.lexer.nextToken();
                    testExpr = this.expr();
                    this.accept(Token.THEN);
                    valueExpr = this.expr();
                    caseItem = new SQLCaseExpr.Item(testExpr, valueExpr);
                    caseExpr.addItem(caseItem);
                }

                if (this.lexer.token == Token.ELSE) {
                    this.lexer.nextToken();
                    caseExpr.setElseExpr(this.expr());
                }

                this.accept(Token.END);
                sqlExpr = caseExpr;
                break;
            case CAST:
                String castStr = this.lexer.stringVal();
                this.lexer.nextToken();
                if (this.lexer.token != Token.LPAREN) {
                    sqlExpr = new SQLIdentifierExpr(castStr);
                } else {
                    this.lexer.nextToken();
                    SQLCastExpr cast = new SQLCastExpr();
                    cast.setExpr(this.expr());
                    this.accept(Token.AS);
                    cast.setDataType(this.parseDataType(false));
                    this.accept(Token.RPAREN);
                    sqlExpr = cast;
                }
                break;
            case NULL:
                sqlExpr = new SQLNullExpr();
                this.lexer.nextToken();
                break;
            case INTERVAL:
                sqlExpr = this.parseInterval();
                break;
            case LBRACE:
                this.lexer.nextToken();
                if (this.lexer.identifierEquals(FnvHash.Constants.TS)) {
                    this.lexer.nextToken();
                    String literal = this.lexer.stringVal();
                    this.lexer.nextToken();
                    sqlExpr = new SQLTimestampExpr(literal);
                } else if (!this.lexer.identifierEquals(FnvHash.Constants.D) && !this.lexer.identifierEquals(FnvHash.Constants.DATE)) {
                    if (this.lexer.identifierEquals(FnvHash.Constants.T)) {
                        this.lexer.nextToken();
                        String literal = this.lexer.stringVal();
                        this.lexer.nextToken();
                        sqlExpr = new SQLTimeExpr(literal);
                    } else if (this.lexer.identifierEquals(FnvHash.Constants.FN)) {
                        this.lexer.nextToken();
                        sqlExpr = this.expr();
                    } else {
                        if (DbType.mysql != this.dbType) {
                            throw new ParserException("ERROR. " + this.lexer.info());
                        }

                        sqlExpr = this.expr();
                    }
                } else {
                    this.lexer.nextToken();
                    String literal = this.lexer.stringVal();
                    if (literal.length() > 2 && literal.charAt(0) == '"' && literal.charAt(literal.length() - 1) == '"') {
                        literal = literal.substring(1, literal.length() - 1);
                    }

                    this.lexer.nextToken();
                    sqlExpr = new SQLDateExpr(literal);
                }

                this.accept(Token.RBRACE);
                break;
            case IF:
            case DAY:
            case DUAL:
            case KEY:
            case LIMIT:
            case SCHEMA:
            case COLUMN:
            case END:
            case COMMENT:
            case COMPUTE:
            case ENABLE:
            case DISABLE:
            case INITIALLY:
            case SEQUENCE:
            case USER:
            case EXPLAIN:
            case WITH:
            case GRANT:
            case REPLACE:
            case INDEX:
            case PCTFREE:
            case INITRANS:
            case MAXTRANS:
            case SEGMENT:
            case CREATION:
            case IMMEDIATE:
            case DEFERRED:
            case STORAGE:
            case NEXT:
            case MINEXTENTS:
            case MAXEXTENTS:
            case MAXSIZE:
            case PCTINCREASE:
            case FLASH_CACHE:
            case CELL_FLASH_CACHE:
            case NONE:
            case LOB:
            case STORE:
            case ROW:
            case CHUNK:
            case CACHE:
            case NOCACHE:
            case LOGGING:
            case NOCOMPRESS:
            case KEEP_DUPLICATES:
            case EXCEPTIONS:
            case PURGE:
            case FULL:
            case TO:
            case IDENTIFIED:
            case PASSWORD:
            case BINARY:
            case WINDOW:
            case OFFSET:
            case SHARE:
            case START:
            case CONNECT:
            case MATCHED:
            case ERRORS:
            case REJECT:
            case UNLIMITED:
            case BEGIN:
            case EXCLUSIVE:
            case MODE:
            case ADVISE:
            case VIEW:
            case ESCAPE:
            case OVER:
            case ORDER:
            case CONSTRAINT:
            case TYPE:
            case OPEN:
            case REPEAT:
            case TABLE:
            case TRUNCATE:
            case EXCEPTION:
            case FUNCTION:
            case IDENTITY:
            case EXTRACT:
            case DESC:
            case DO:
            case GROUP:
            case MOD:
            case CONCAT:
            case PRIMARY:
            case PARTITION:
            case LEAVE:
            case CLOSE:
            case CONDITION:
            case OUT:
            case USE:
            case EXCEPT:
            case INTERSECT:
            case MERGE:
            case MINUS:
            case UNTIL:
            case TOP:
            case SHOW:
            case INOUT:
            case MINUTE:
            case SECOND:
            case HOUR:
            case YEAR:
            case MONTH:
            case MICROSECOND:
            case WEEK:
            case QUARTER:
            case SECOND_MICROSECOND:
            case MINUTE_MICROSECOND:
            case MINUTE_SECOND:
            case HOUR_MICROSECOND:
            case HOUR_SECOND:
            case HOUR_MINUTE:
            case DAY_MICROSECOND:
            case DAY_SECOND:
            case DAY_MINUTE:
            case DAY_HOUR:
            case YEAR_MONTH:
                sqlExpr = new SQLIdentifierExpr(this.lexer.stringVal());
                this.lexer.nextToken();
                break;
            case VARIANT:
                String varName = this.lexer.stringVal();
                this.lexer.nextToken();
                if (varName.equals(":") && this.lexer.token == Token.IDENTIFIER && DbType.oracle == this.dbType) {
                    String part2 = this.lexer.stringVal();
                    this.lexer.nextToken();
                    varName = varName + part2;
                }

                SQLVariantRefExpr varRefExpr = new SQLVariantRefExpr(varName);
                if (varName.startsWith(":")) {
                    varRefExpr.setIndex(this.lexer.nextVarIndex());
                }

                if (varRefExpr.getName().equals("@") && this.lexer.token == Token.LITERAL_CHARS) {
                    varRefExpr.setName("@'" + this.lexer.stringVal() + "'");
                    this.lexer.nextToken();
                } else if (varRefExpr.getName().equals("@@") && this.lexer.token == Token.LITERAL_CHARS) {
                    varRefExpr.setName("@@'" + this.lexer.stringVal() + "'");
                    this.lexer.nextToken();
                }

                sqlExpr = varRefExpr;
                break;
            case INSERT:
                this.lexer.nextToken();
                if (this.lexer.token != Token.LPAREN) {
                    throw new ParserException("syntax error. " + this.lexer.info());
                }

                sqlExpr = new SQLIdentifierExpr("INSERT");
                break;
            case NEW:
                throw new ParserException("TODO " + this.lexer.info());
            case LITERAL_NCHARS:
                sqlExpr = new SQLNCharExpr(this.lexer.stringVal());
                this.lexer.nextToken();
                if (DbType.mysql == this.dbType) {
                    SQLMethodInvokeExpr concat = null;

                    label445:
                    while (true) {
                        while (this.lexer.token != Token.LITERAL_ALIAS) {
                            if (this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.LITERAL_NCHARS) {
                                break label445;
                            }

                            if (concat == null) {
                                concat = new SQLMethodInvokeExpr("CONCAT");
                                concat.addArgument(sqlExpr);
                                sqlExpr = concat;
                            }

                            String chars = this.lexer.stringVal();
                            this.lexer.nextToken();
                            SQLCharExpr concat_right = new SQLCharExpr(chars);
                            concat.addArgument(concat_right);
                        }

                        if (concat == null) {
                            concat = new SQLMethodInvokeExpr("CONCAT");
                            concat.addArgument(sqlExpr);
                            sqlExpr = concat;
                        }

                        String alias = this.lexer.stringVal();
                        this.lexer.nextToken();
                        SQLCharExpr concat_right = new SQLCharExpr(alias.substring(1, alias.length() - 1));
                        concat.addArgument(concat_right);
                    }
                }
                break;
            case DEFAULT:
                if (this.dbType == DbType.clickhouse) {
                    sqlExpr = new SQLIdentifierExpr(this.lexer.stringVal());
                } else {
                    sqlExpr = new SQLDefaultExpr();
                }

                this.lexer.nextToken();
                break;
            case EXISTS:
                this.lexer.nextToken();
                this.accept(Token.LPAREN);
                sqlExpr = new SQLExistsExpr(this.createSelectParser().select());
                this.accept(Token.RPAREN);
                this.parseQueryPlanHint(sqlExpr);
                break;
            case NOT:
                this.lexer.nextToken();
                if (this.lexer.token == Token.EXISTS) {
                    this.lexer.nextToken();
                    this.accept(Token.LPAREN);
                    SQLExistsExpr exists = new SQLExistsExpr(this.createSelectParser().select(), true);
                    this.accept(Token.RPAREN);
                    this.parseQueryPlanHint(exists);
                    if (this.lexer.token == Token.EQ) {
                        exists.setNot(false);
                        SQLExpr relational = this.relationalRest(exists);
                        sqlExpr = new SQLNotExpr(relational);
                    } else {
                        sqlExpr = exists;
                    }
                } else {
                    if (this.lexer.token == Token.LPAREN) {
                        this.lexer.nextToken();
                        SQLExpr notTarget = this.expr();
                        this.accept(Token.RPAREN);
                        notTarget = this.bitXorRest(notTarget);
                        notTarget = this.multiplicativeRest(notTarget);
                        notTarget = this.additiveRest(notTarget);
                        notTarget = this.shiftRest(notTarget);
                        notTarget = this.bitAndRest(notTarget);
                        notTarget = this.bitOrRest(notTarget);
                        notTarget = this.inRest(notTarget);
                        notTarget = this.relationalRest(notTarget);
                        SQLExpr var21 = new SQLNotExpr(notTarget);
                        this.parseQueryPlanHint(var21);
                        return this.primaryRest(var21);
                    }

                    SQLExpr restExpr = this.relational();
                    sqlExpr = new SQLNotExpr(restExpr);
                    this.parseQueryPlanHint(sqlExpr);
                }
                break;
            case FROM:
            case SELECT:
                SQLQueryExpr queryExpr = new SQLQueryExpr(this.createSelectParser().select());
                sqlExpr = queryExpr;
                break;
            case TILDE:
                this.lexer.nextToken();
                SQLExpr unaryValueExpr = this.primary();
                SQLUnaryExpr unary = new SQLUnaryExpr(SQLUnaryOperator.Compl, unaryValueExpr);
                sqlExpr = unary;
                break;
            case LEFT:
                sqlExpr = new SQLIdentifierExpr("LEFT");
                this.lexer.nextToken();
                break;
            case RIGHT:
                sqlExpr = new SQLIdentifierExpr("RIGHT");
                this.lexer.nextToken();
                break;
            case INNER:
                sqlExpr = new SQLIdentifierExpr("INNER");
                this.lexer.nextToken();
                break;
            case DATABASE:
                sqlExpr = new SQLIdentifierExpr("DATABASE");
                this.lexer.nextToken();
                break;
            case LOCK:
                sqlExpr = new SQLIdentifierExpr("LOCK");
                this.lexer.nextToken();
                break;
            case BANGBANG:
                if (this.dbType == DbType.hive) {
                    throw new ParserException(this.lexer.info());
                }

                this.lexer.nextToken();
                sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Not, this.primary());
                break;
            case BANG_TILDE:
                this.lexer.nextToken();
                SQLExpr bangExpr = this.primary();
                sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Not, new SQLUnaryExpr(SQLUnaryOperator.Compl, bangExpr));
                break;
            case LITERAL_HEX:
                String hex = this.lexer.hexString();
                sqlExpr = new SQLHexExpr(hex);
                this.lexer.nextToken();
                break;
            case COLON:
                this.lexer.nextToken();
                if (this.lexer.token == Token.LITERAL_ALIAS) {
                    sqlExpr = new SQLVariantRefExpr(":\"" + this.lexer.stringVal() + "\"");
                    this.lexer.nextToken();
                }
                break;
            case ANY:
                sqlExpr = this.parseAny();
                break;
            case SOME:
                sqlExpr = this.parseSome();
                break;
            case ALL:
                sqlExpr = this.parseAll();
                break;
            case EOF:
                throw new EOFParserException();
            case TRUE:
                this.lexer.nextToken();
                sqlExpr = new SQLBooleanExpr(true);
                break;
            case FALSE:
                this.lexer.nextToken();
                sqlExpr = new SQLBooleanExpr(false);
                break;
            case BITS:
                String strVal = this.lexer.stringVal();
                this.lexer.nextToken();
                sqlExpr = new SQLBinaryExpr(strVal);
                break;
            case GLOBAL:
            case CONTAINS:
                sqlExpr = this.inRest((SQLExpr) null);
                break;
            case SET:
                Lexer.SavePoint savePoint = this.lexer.mark();
                this.lexer.nextToken();
                if (this.lexer.token() != Token.LPAREN) {
                    this.lexer.reset(savePoint);
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                sqlExpr = new SQLIdentifierExpr("SET");
                break;
            case VALUES:
            case TRIGGER:
            case FOR:
            case CHECK:
            case DELETE:
            case BY:
            case UPDATE:
            case LOOP:
            case LIKE:
            case UNION:
            case CREATE:
                if (this.dbType != DbType.odps && this.dbType != DbType.hive && this.dbType != DbType.postgresql) {
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                sqlExpr = new SQLIdentifierExpr(this.lexer.stringVal());
                this.lexer.nextToken();
                break;
            case DISTINCT:
                if (this.dbType != DbType.elastic_search && this.dbType != DbType.mysql) {
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                Lexer.SavePoint mark = this.lexer.mark();
                sqlExpr = new SQLIdentifierExpr(this.lexer.stringVal());
                this.lexer.nextToken();
                if (this.lexer.token != Token.LPAREN) {
                    this.lexer.reset(mark);
                    throw new ParserException("ERROR. " + this.lexer.info());
                }
                break;
            case IN:
                if (this.dbType != DbType.odps) {
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                this.lexer.nextToken();
                this.accept(Token.LPAREN);
                SQLInListExpr in = new SQLInListExpr();
                in.setExpr(this.expr());
                if (this.lexer.token == Token.COMMA) {
                    this.lexer.nextToken();
                    this.exprList(in.getTargetList(), in);
                }

                this.accept(Token.RPAREN);
                sqlExpr = in;
                break;
            case LBRACKET:
                if (this.dbType != DbType.odps && this.dbType != DbType.clickhouse) {
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                SQLArrayExpr array = new SQLArrayExpr();
                this.lexer.nextToken();
                this.exprList(array.getValues(), array);
                this.accept(Token.RBRACKET);
                sqlExpr = array;
                break;
            case ON:
                if (this.dbType != DbType.postgresql) {
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                String methodName = this.lexer.stringVal();
                this.lexer.nextToken();
                if (this.lexer.token != Token.LPAREN) {
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                sqlExpr = this.methodRest(new SQLIdentifierExpr(methodName), true);
                break;
            case COLONCOLON:
                if (this.dbType != DbType.odps) {
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                this.lexer.nextToken();
                SQLMethodInvokeExpr method = (SQLMethodInvokeExpr) this.primary();
                method.setOwner(new SQLIdentifierExpr(""));
                sqlExpr = method;
                break;
            case SYSDATE:
                if (this.dbType != DbType.kingbase) {
                    throw new ParserException("ERROR. " + this.lexer.info());
                }

                sqlExpr = new SQLIdentifierExpr(this.lexer.stringVal());
                this.lexer.nextToken();
                break;
            default:
                throw new ParserException("ERROR. " + this.lexer.info());
        }

        SQLExpr expr = this.primaryRest(sqlExpr);
        if (beforeComments != null) {
            expr.addBeforeComment(beforeComments);
        }

        return expr;
    }

    protected SQLExpr parseAll() {
        String str = this.lexer.stringVal();
        this.lexer.nextToken();
        if (this.lexer.token != Token.DOT && this.lexer.token != Token.SLASH) {
            if (this.lexer.token == Token.COMMA) {
                return new SQLIdentifierExpr(str);
            } else {
                SQLAllExpr allExpr = new SQLAllExpr();
                this.accept(Token.LPAREN);
                SQLSelect allSubQuery = this.createSelectParser().select();
                allExpr.setSubQuery(allSubQuery);
                this.accept(Token.RPAREN);
                allSubQuery.setParent(allExpr);
                return allExpr;
            }
        } else {
            return this.primaryRest(new SQLIdentifierExpr(str));
        }
    }

    protected SQLExpr parseSome() {
        String str = this.lexer.stringVal();
        this.lexer.nextToken();
        if (this.lexer.token != Token.LPAREN) {
            return new SQLIdentifierExpr(str);
        } else {
            this.lexer.nextToken();
            SQLSomeExpr someExpr = new SQLSomeExpr();
            SQLSelect someSubQuery = this.createSelectParser().select();
            someExpr.setSubQuery(someSubQuery);
            this.accept(Token.RPAREN);
            someSubQuery.setParent(someExpr);
            return someExpr;
        }
    }

    protected SQLExpr parseAny() {
        this.lexer.nextToken();
        SQLExpr sqlExpr;
        if (this.lexer.token == Token.LPAREN) {
            this.accept(Token.LPAREN);
            if (this.lexer.token == Token.ARRAY || this.lexer.token == Token.IDENTIFIER) {
                SQLExpr expr = this.expr();
                SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("ANY");
                methodInvokeExpr.addArgument(expr);
                this.accept(Token.RPAREN);
                return methodInvokeExpr;
            }

            SQLSelect anySubQuery = this.createSelectParser().select();
            SQLAnyExpr anyExpr = new SQLAnyExpr(anySubQuery);
            this.accept(Token.RPAREN);
            sqlExpr = anyExpr;
        } else {
            sqlExpr = new SQLIdentifierExpr("ANY");
        }

        return sqlExpr;
    }

    protected SQLExpr parseAliasExpr(String alias) {
        return new SQLIdentifierExpr(alias);
    }

    protected SQLExpr parseInterval() {
        String str = this.lexer.stringVal();
        this.accept(Token.INTERVAL);
        switch (this.lexer.token) {
            case PLUS:
            case SUB:
            case END:
            case ORDER:
            case FROM:
            case IN:
            case COMMA:
            case IS:
            case BETWEEN:
            case RPAREN:
            case EQ:
            case BANGEQ:
            case LTGT:
            case LT:
            case LTEQ:
            case GT:
            case GTEQ:
            case STAR:
            case DIV:
            case SLASH:
            case DOT:
            case THEN:
                return new SQLIdentifierExpr(str);
            default:
                SQLExpr value = this.expr();
                if (this.lexer.token() != Token.IDENTIFIER) {
                    throw new ParserException("Syntax error. " + this.lexer.info());
                } else {
                    String unit = this.lexer.stringVal();
                    this.lexer.nextToken();
                    SQLIntervalExpr intervalExpr = new SQLIntervalExpr();
                    intervalExpr.setValue(value);
                    intervalExpr.setUnit(SQLIntervalUnit.valueOf(unit.toUpperCase()));
                    return intervalExpr;
                }
        }
    }

    public SQLSelectParser createSelectParser() {
        return new SQLSelectParser(this);
    }

    public SQLExpr primaryRest(SQLExpr expr) {
        if (expr == null) {
            throw new IllegalArgumentException("expr");
        } else {
            Token token = this.lexer.token;
            if (token == Token.OF) {
                if (expr instanceof SQLIdentifierExpr) {
                    long hashCode64 = ((SQLIdentifierExpr) expr).hashCode64();
                    if (hashCode64 == FnvHash.Constants.CURRENT) {
                        this.lexer.nextToken();
                        SQLName cursorName = this.name();
                        return new SQLCurrentOfCursorExpr(cursorName);
                    }
                }
            } else if (token == Token.FOR && expr instanceof SQLIdentifierExpr) {
                SQLIdentifierExpr idenExpr = (SQLIdentifierExpr) expr;
                if (idenExpr.hashCode64() == FnvHash.Constants.NEXTVAL) {
                    this.lexer.nextToken();
                    SQLName seqName = this.name();
                    SQLSequenceExpr seqExpr = new SQLSequenceExpr(seqName, SQLSequenceExpr.Function.NextVal);
                    return seqExpr;
                }

                if (idenExpr.hashCode64() == FnvHash.Constants.CURRVAL) {
                    this.lexer.nextToken();
                    SQLName seqName = this.name();
                    SQLSequenceExpr seqExpr = new SQLSequenceExpr(seqName, SQLSequenceExpr.Function.CurrVal);
                    return seqExpr;
                }

                if (idenExpr.hashCode64() == FnvHash.Constants.PREVVAL) {
                    this.lexer.nextToken();
                    SQLName seqName = this.name();
                    SQLSequenceExpr seqExpr = new SQLSequenceExpr(seqName, SQLSequenceExpr.Function.PrevVal);
                    return seqExpr;
                }
            }

            if (token == Token.DOT) {
                this.lexer.nextToken();
                if (expr instanceof SQLCharExpr) {
                    String text = ((SQLCharExpr) expr).getText();
                    expr = new SQLIdentifierExpr(text);
                }

                expr = this.dotRest(expr);
                return this.primaryRest(expr);
            } else if (this.lexer.identifierEquals(FnvHash.Constants.SETS) && expr.getClass() == SQLIdentifierExpr.class && "GROUPING".equalsIgnoreCase(((SQLIdentifierExpr) expr).getName())) {
                SQLGroupingSetExpr groupingSets = new SQLGroupingSetExpr();
                this.lexer.nextToken();
                this.accept(Token.LPAREN);

                while (true) {
                    SQLExpr item;
                    if (this.lexer.token == Token.LPAREN) {
                        this.lexer.nextToken();
                        SQLListExpr listExpr = new SQLListExpr();
                        this.exprList(listExpr.getItems(), listExpr);
                        item = listExpr;
                        this.accept(Token.RPAREN);
                    } else {
                        item = this.expr();
                    }

                    item.setParent(groupingSets);
                    groupingSets.addParameter(item);
                    if (this.lexer.token == Token.RPAREN) {
                        this.exprList(groupingSets.getParameters(), groupingSets);
                        this.accept(Token.RPAREN);
                        return groupingSets;
                    }

                    this.accept(Token.COMMA);
                }
            } else if (this.lexer.token == Token.LPAREN && !(expr instanceof SQLIntegerExpr) && !(expr instanceof SQLHexExpr)) {
                SQLExpr method = this.methodRest(expr, true);
                if (this.lexer.token == Token.LBRACKET || this.lexer.token == Token.DOT) {
                    method = this.primaryRest(method);
                }

                return method;
            } else {
                return expr;
            }
        }
    }

    protected SQLExpr parseExtract() {
        throw new ParserException("not supported.");
    }

    protected SQLExpr parsePosition() {
        throw new ParserException("not supported.");
    }

    protected SQLExpr parseMatch() {
        SQLMatchAgainstExpr matchAgainstExpr = new SQLMatchAgainstExpr();
        if (this.lexer.token() == Token.RPAREN) {
            this.lexer.nextToken();
        } else {
            this.exprList(matchAgainstExpr.getColumns(), matchAgainstExpr);
            this.accept(Token.RPAREN);
        }

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

        this.accept(Token.LPAREN);
        SQLExpr against = this.primary();
        matchAgainstExpr.setAgainst(against);
        if (this.lexer.token() == Token.IN) {
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.NATURAL)) {
                this.lexer.nextToken();
                this.acceptIdentifier("LANGUAGE");
                this.acceptIdentifier("MODE");
                if (this.lexer.token() == Token.WITH) {
                    this.lexer.nextToken();
                    this.acceptIdentifier("QUERY");
                    this.acceptIdentifier("EXPANSION");
                    matchAgainstExpr.setSearchModifier(SQLMatchAgainstExpr.SearchModifier.IN_NATURAL_LANGUAGE_MODE_WITH_QUERY_EXPANSION);
                } else {
                    matchAgainstExpr.setSearchModifier(SQLMatchAgainstExpr.SearchModifier.IN_NATURAL_LANGUAGE_MODE);
                }
            } else {
                if (!this.lexer.identifierEquals(FnvHash.Constants.BOOLEAN)) {
                    throw new ParserException("syntax error. " + this.lexer.info());
                }

                this.lexer.nextToken();
                this.acceptIdentifier("MODE");
                matchAgainstExpr.setSearchModifier(SQLMatchAgainstExpr.SearchModifier.IN_BOOLEAN_MODE);
            }
        } else if (this.lexer.token() == Token.WITH) {
            this.lexer.nextToken();
            this.acceptIdentifier("QUERY");
            this.acceptIdentifier("EXPANSION");
            matchAgainstExpr.setSearchModifier(SQLMatchAgainstExpr.SearchModifier.WITH_QUERY_EXPANSION);
        }

        this.accept(Token.RPAREN);
        return this.primaryRest(matchAgainstExpr);
    }

    protected SQLExpr methodRest(SQLExpr expr, boolean acceptLPAREN) {
        if (acceptLPAREN) {
            this.accept(Token.LPAREN);
        }

        boolean distinct = false;
        if (this.lexer.token == Token.DISTINCT) {
            this.lexer.nextToken();
            distinct = true;
            if (this.lexer.token == Token.RPAREN || this.lexer.token == Token.COMMA) {
                throw new ParserException(this.lexer.info());
            }
        }

        String methodName = null;
        String aggMethodName = null;
        SQLExpr owner = null;
        String trimOption = null;
        long hash_lower = 0L;
        if (expr instanceof SQLIdentifierExpr) {
            SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr) expr;
            methodName = identifierExpr.getName();
            hash_lower = identifierExpr.nameHashCode64();
            if (this.allowIdentifierMethod) {
                if (hash_lower == FnvHash.Constants.TRIM) {
                    if (this.lexer.identifierEquals(FnvHash.Constants.LEADING)) {
                        trimOption = this.lexer.stringVal();
                        this.lexer.nextToken();
                    } else if (this.lexer.identifierEquals(FnvHash.Constants.BOTH)) {
                        trimOption = this.lexer.stringVal();
                        this.lexer.nextToken();
                    } else if (this.lexer.identifierEquals(FnvHash.Constants.TRAILING)) {
                        trimOption = this.lexer.stringVal();
                        this.lexer.nextToken();
                    }
                } else {
                    if (hash_lower == FnvHash.Constants.MATCH && (DbType.mysql == this.dbType || DbType.ads == this.dbType)) {
                        return this.parseMatch();
                    }

                    if (hash_lower == FnvHash.Constants.EXTRACT && DbType.mysql == this.dbType) {
                        return this.parseExtract();
                    }

                    if (hash_lower == FnvHash.Constants.POSITION && DbType.mysql == this.dbType) {
                        return this.parsePosition();
                    }

                    if (hash_lower == FnvHash.Constants.TRY_CAST) {
                        SQLCastExpr cast = new SQLCastExpr();
                        cast.setTry(true);
                        cast.setExpr(this.expr());
                        this.accept(Token.AS);
                        cast.setDataType(this.parseDataType(false));
                        this.accept(Token.RPAREN);
                        return cast;
                    }

                    if (hash_lower == FnvHash.Constants.CONVERT && DbType.mysql == this.dbType) {
                        SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr(methodName, hash_lower);
                        SQLExpr arg0 = this.expr();
                        Object exprUsing = arg0.getAttributes().get("USING");
                        if (exprUsing instanceof String) {
                            String charset = (String) exprUsing;
                            methodInvokeExpr.setUsing(new SQLIdentifierExpr(charset));
                            arg0.getAttributes().remove("USING");
                        }

                        methodInvokeExpr.addArgument(arg0);
                        if (this.lexer.token == Token.COMMA) {
                            this.lexer.nextToken();
                            SQLDataType dataType = this.parseDataType();
                            SQLDataTypeRefExpr dataTypeRefExpr = new SQLDataTypeRefExpr(dataType);
                            methodInvokeExpr.addArgument(dataTypeRefExpr);
                        }

                        if (this.lexer.token == Token.USING || this.lexer.identifierEquals(FnvHash.Constants.USING)) {
                            this.lexer.nextToken();
                            SQLExpr using;
                            if (this.lexer.token == Token.STAR) {
                                this.lexer.nextToken();
                                using = new SQLAllColumnExpr();
                            } else if (this.lexer.token == Token.BINARY) {
                                using = new SQLIdentifierExpr(this.lexer.stringVal());
                                this.lexer.nextToken();
                            } else {
                                using = this.primary();
                            }

                            methodInvokeExpr.setUsing(using);
                        }

                        this.accept(Token.RPAREN);
                        return this.primaryRest(methodInvokeExpr);
                    }
                }
            }

            if (distinct) {
                aggMethodName = methodName;
            } else {
                aggMethodName = this.getAggregateFunction(hash_lower);
            }
        } else if (expr instanceof SQLPropertyExpr) {
            methodName = ((SQLPropertyExpr) expr).getSimpleName();
            aggMethodName = SQLUtils.normalize(methodName);
            hash_lower = FnvHash.fnv1a_64_lower(aggMethodName);
            aggMethodName = this.getAggregateFunction(hash_lower);
            owner = ((SQLPropertyExpr) expr).getOwner();
        } else if (expr instanceof SQLDefaultExpr) {
            methodName = "DEFAULT";
        } else if (expr instanceof SQLCharExpr) {
            methodName = ((SQLCharExpr) expr).getText();
            if (this.isAggregateFunction(methodName)) {
                aggMethodName = methodName;
            }
        } else if (expr instanceof SQLDbLinkExpr) {
            SQLDbLinkExpr dbLinkExpr = (SQLDbLinkExpr) expr;
            methodName = dbLinkExpr.toString();
        }

        if (aggMethodName != null) {
            SQLAggregateExpr aggregateExpr = this.parseAggregateExpr(methodName);
            if (distinct) {
                aggregateExpr.setOption(SQLAggregateOption.DISTINCT);
            }

            if (this.lexer.token == Token.COLONCOLON) {
                return this.primaryRest(aggregateExpr);
            } else {
                return aggregateExpr;
            }
        } else {
            SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr(methodName, hash_lower);
            if (owner != null) {
                methodInvokeExpr.setOwner(owner);
            }

            if (trimOption != null) {
                methodInvokeExpr.setTrimOption(trimOption);
            }

            Token token = this.lexer.token;
            if (token != Token.RPAREN && token != Token.FROM) {
                this.exprList(methodInvokeExpr.getArguments(), methodInvokeExpr);
            }

            if (hash_lower == FnvHash.Constants.EXIST && methodInvokeExpr.getArguments().size() == 1 && methodInvokeExpr.getArguments().get(0) instanceof SQLQueryExpr) {
                throw new ParserException("exists syntax error.");
            } else {
                if (this.lexer.token == Token.FROM) {
                    this.lexer.nextToken();
                    SQLExpr from = this.expr();
                    methodInvokeExpr.setFrom(from);
                    if (this.lexer.token == Token.FOR) {
                        this.lexer.nextToken();
                        SQLExpr forExpr = this.expr();
                        methodInvokeExpr.setFor(forExpr);
                    }
                }

                if (this.lexer.token == Token.USING || this.lexer.identifierEquals(FnvHash.Constants.USING)) {
                    this.lexer.nextToken();
                    SQLExpr using;
                    if (this.lexer.token == Token.STAR) {
                        this.lexer.nextToken();
                        using = new SQLAllColumnExpr();
                    } else if (this.lexer.token == Token.BINARY) {
                        using = new SQLIdentifierExpr(this.lexer.stringVal());
                        this.lexer.nextToken();
                    } else {
                        using = this.primary();
                    }

                    methodInvokeExpr.setUsing(using);
                }

                if (hash_lower == FnvHash.Constants.WEIGHT_STRING) {
                    if (this.lexer.token == Token.AS) {
                        this.lexer.nextToken();
                        SQLDataType as = this.parseDataType();
                        methodInvokeExpr.putAttribute("as", as);
                    }

                    if (this.lexer.identifierEquals(FnvHash.Constants.LEVEL)) {
                        this.lexer.nextToken();
                        List<SQLSelectOrderByItem> levels = new ArrayList();

                        while (true) {
                            SQLSelectOrderByItem level = this.parseSelectOrderByItem();
                            levels.add(level);
                            if (this.lexer.token != Token.COMMA) {
                                methodInvokeExpr.putAttribute("levels", levels);
                                break;
                            }

                            this.lexer.nextToken();
                        }
                    }

                    if (this.lexer.identifierEquals(FnvHash.Constants.REVERSE)) {
                        this.lexer.nextToken();
                        methodInvokeExpr.putAttribute("reverse", true);
                    }
                }

                SQLAggregateExpr aggregateExpr = null;
                if (this.lexer.token == Token.ORDER) {
                    this.lexer.nextToken();
                    this.accept(Token.BY);
                    aggregateExpr = new SQLAggregateExpr(methodName);
                    aggregateExpr.getArguments().addAll(methodInvokeExpr.getArguments());
                    SQLOrderBy orderBy = new SQLOrderBy();
                    this.orderBy(orderBy.getItems(), orderBy);
                    aggregateExpr.setOrderBy(orderBy);
                }

                this.accept(Token.RPAREN);
                if (this.lexer.identifierEquals(FnvHash.Constants.USING) && this.dbType == DbType.odps) {
                    this.lexer.nextToken();
                    SQLExpr using = this.primary();
                    methodInvokeExpr.setUsing(using);
                }

                if (this.lexer.identifierEquals(FnvHash.Constants.FILTER)) {
                    if (aggregateExpr == null) {
                        Lexer.SavePoint mark = this.lexer.mark();
                        this.lexer.nextToken();
                        Token nextToken = this.lexer.token;
                        this.lexer.reset(mark);
                        if (nextToken == Token.LPAREN) {
                            aggregateExpr = new SQLAggregateExpr(methodName);
                            aggregateExpr.getArguments().addAll(methodInvokeExpr.getArguments());
                            this.filter(aggregateExpr);
                        }
                    } else {
                        this.filter(aggregateExpr);
                    }
                }

                if (this.lexer.token == Token.OVER || this.lexer.identifierEquals("NULLS") || this.lexer.identifierEquals("IGNORE")) {
                    if (aggregateExpr == null) {
                        aggregateExpr = new SQLAggregateExpr(methodName);
                        this.ignoreNulls(aggregateExpr);
                        aggregateExpr.getArguments().addAll(methodInvokeExpr.getArguments());
                    }

                    this.over(aggregateExpr);
                }

                if (aggregateExpr != null) {
                    return this.primaryRest(aggregateExpr);
                } else if (this.lexer.token == Token.LPAREN) {
                    return methodInvokeExpr;
                } else {
                    return this.primaryRest(methodInvokeExpr);
                }
            }
        }
    }

    public SQLExpr ignoreNulls(SQLAggregateExpr e) {
        this.lexer.mark();
        if (this.lexer.identifierEquals("IGNORE")) {
            this.lexer.nextToken();
            if (this.lexer.identifierEquals("NULLS")) {
                e.setIgnoreNulls(true);
                this.lexer.nextToken();
            } else {
                this.lexer.reset();
            }
        } else if (this.lexer.identifierEquals("NULLS")) {
            this.lexer.nextToken();
        }

        return e;
    }

    protected SQLExpr dotRest(SQLExpr expr) {
        Object var9;
        if (this.lexer.token == Token.STAR) {
            this.lexer.nextToken();
            var9 = new SQLPropertyExpr(expr, "*");
        } else {
            long hash_lower = 0L;
            String name;
            if (this.lexer.token == Token.IDENTIFIER) {
                name = this.lexer.stringVal();
                hash_lower = this.lexer.hash_lower;
                this.lexer.nextToken();
                if (hash_lower == FnvHash.Constants.NEXTVAL) {
                    SQLExpr var8 = new SQLSequenceExpr((SQLName) expr, SQLSequenceExpr.Function.NextVal);
                    return this.primaryRest(var8);
                }

                if (hash_lower == FnvHash.Constants.CURRVAL) {
                    SQLExpr var7 = new SQLSequenceExpr((SQLName) expr, SQLSequenceExpr.Function.CurrVal);
                    return this.primaryRest(var7);
                }

                if (hash_lower == FnvHash.Constants.PREVVAL) {
                    SQLExpr var6 = new SQLSequenceExpr((SQLName) expr, SQLSequenceExpr.Function.PrevVal);
                    return this.primaryRest(var6);
                }
            } else if (this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.LITERAL_ALIAS) {
                if (this.lexer.getKeywords().containsValue(this.lexer.token)) {
                    name = this.lexer.stringVal();
                    this.lexer.nextToken();
                } else if (this.lexer.token == Token.VARIANT && this.lexer.stringVal().startsWith("$")) {
                    name = this.lexer.stringVal();
                    this.lexer.nextToken();
                } else if (this.lexer.token == Token.VARIANT && this.dbType == DbType.dm) {
                    name = this.lexer.stringVal();
                    this.lexer.nextToken();
                } else {
                    if (this.lexer.token != Token.LITERAL_INT || this.dbType != DbType.hive) {
                        throw new ParserException("error : " + this.lexer.info());
                    }

                    name = this.lexer.numberString();
                    this.lexer.nextToken();
                }
            } else {
                name = this.lexer.stringVal();
                this.lexer.nextToken();
            }

            if (this.lexer.token == Token.LPAREN) {
                boolean aggregate = hash_lower == FnvHash.Constants.WM_CONCAT && expr instanceof SQLIdentifierExpr && ((SQLIdentifierExpr) expr).nameHashCode64() == FnvHash.Constants.WMSYS;
                var9 = this.methodRest(expr, name, aggregate);
            } else {
                if (name.charAt(0) == '`') {
                    if (this.lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                        name = name.substring(1, name.length() - 1);
                    }

                    hash_lower = FnvHash.hashCode64(name);
                }

                var9 = new SQLPropertyExpr(expr, name, hash_lower);
            }
        }

        var9 = this.primaryRest((SQLExpr) var9);
        return (SQLExpr) var9;
    }

    private SQLExpr methodRest(SQLExpr expr, String name, boolean aggregate) {
        this.lexer.nextToken();
        if (this.lexer.token == Token.DISTINCT) {
            this.lexer.nextToken();
            SQLAggregateExpr aggregateExpr = new SQLAggregateExpr(name, SQLAggregateOption.DISTINCT);
            aggregateExpr.setOwner(expr);
            if (this.lexer.token == Token.RPAREN) {
                this.lexer.nextToken();
            } else {
                if (this.lexer.token == Token.PLUS) {
                    aggregateExpr.getArguments().add(new SQLIdentifierExpr("+"));
                    this.lexer.nextToken();
                } else {
                    this.exprList(aggregateExpr.getArguments(), aggregateExpr);
                }

                this.accept(Token.RPAREN);
            }

            expr = aggregateExpr;
        } else if (aggregate) {
            SQLAggregateExpr methodInvokeExpr = new SQLAggregateExpr(name);
            methodInvokeExpr.setMethodName(expr.toString() + "." + name);
            if (this.lexer.token == Token.RPAREN) {
                this.lexer.nextToken();
            } else {
                if (this.lexer.token == Token.PLUS) {
                    methodInvokeExpr.addArgument(new SQLIdentifierExpr("+"));
                    this.lexer.nextToken();
                } else {
                    this.exprList(methodInvokeExpr.getArguments(), methodInvokeExpr);
                }

                this.accept(Token.RPAREN);
            }

            if (this.lexer.token == Token.OVER) {
                this.over(methodInvokeExpr);
            }

            expr = methodInvokeExpr;
        } else {
            SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr(name);
            methodInvokeExpr.setOwner(expr);
            if (this.lexer.token == Token.RPAREN) {
                this.lexer.nextToken();
            } else {
                if (this.lexer.token == Token.PLUS) {
                    methodInvokeExpr.addArgument(new SQLIdentifierExpr("+"));
                    this.lexer.nextToken();
                } else {
                    this.exprList(methodInvokeExpr.getArguments(), methodInvokeExpr);
                }

                this.accept(Token.RPAREN);
            }

            if (this.lexer.token == Token.OVER) {
                SQLAggregateExpr aggregateExpr = new SQLAggregateExpr(methodInvokeExpr.getMethodName());
                aggregateExpr.setOwner(methodInvokeExpr.getOwner());
                aggregateExpr.getArguments().addAll(methodInvokeExpr.getArguments());
                this.over(aggregateExpr);
                methodInvokeExpr = aggregateExpr;
            }

            expr = methodInvokeExpr;
        }

        return expr;
    }

    public final SQLExpr groupComparisionRest(SQLExpr expr) {
        return expr;
    }

    public final void names(Collection<SQLName> exprCol) {
        this.names(exprCol, (SQLObject) null);
    }

    public final void names(Collection<SQLName> exprCol, SQLObject parent) {
        if (this.lexer.token != Token.RBRACE) {
            if (this.lexer.token != Token.EOF) {
                SQLName name = this.name();
                name.setParent(parent);
                exprCol.add(name);

                while (this.lexer.token == Token.COMMA) {
                    this.lexer.nextToken();
                    name = this.name();
                    name.setParent(parent);
                    exprCol.add(name);
                }

            }
        }
    }

    /**
     * @deprecated
     */
    @Deprecated
    public final void exprList(Collection<SQLExpr> exprCol) {
        this.exprList(exprCol, (SQLObject) null);
    }

    public final void exprList(Collection<SQLExpr> exprCol, SQLObject parent) {
        if (this.lexer.token != Token.RPAREN && this.lexer.token != Token.RBRACKET && this.lexer.token != Token.SEMI) {
            if (this.lexer.token != Token.EOF) {
                while (true) {
                    SQLExpr expr;
                    if (this.lexer.token == Token.ROW && parent instanceof SQLDataType) {
                        SQLDataType dataType = this.parseDataType();
                        expr = new SQLDataTypeRefExpr(dataType);
                    } else {
                        expr = this.expr();
                    }

                    String s = expr.toString();
                    int i = 0;
                    if (this.dbType == DbType.db2 && "DATE".equalsIgnoreCase(s)) {
                        SQLExpr expr1 = this.expr();
                        DB2CurrentTimeExpr dB2CurrentTimeExpr = new DB2CurrentTimeExpr();
                        dB2CurrentTimeExpr.setText("DATE " + expr1.toString());
                        dB2CurrentTimeExpr.setParent(parent);
                        exprCol.add(dB2CurrentTimeExpr);
                    } else if (this.dbType == DbType.db2 && "CURRENT".equalsIgnoreCase(s)) {
                        SQLExpr expr1 = this.expr();
                        DB2CurrentTimeExpr dB2CurrentTimeExpr = new DB2CurrentTimeExpr();
                        dB2CurrentTimeExpr.setText("CURRENT " + expr1.toString());
                        dB2CurrentTimeExpr.setParent(parent);
                        exprCol.add(dB2CurrentTimeExpr);
                    } else if (this.dbType == DbType.db2 && this.lexer.token() == Token.START) {
                        this.lexer.nextToken();
                        SQLExpr exprBeginTime = this.expr();
                        SQLExpr beginSecond = this.expr();
                        if (this.lexer.token() == Token.END) {
                            this.lexer.nextToken();
                        }

                        SQLExpr exprEndTime = this.expr();
                        SQLExpr endSecond = this.expr();
                        SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr(s + " START " + exprBeginTime.toString() + beginSecond.toString() + " END " + exprEndTime.toString() + endSecond.toString());
                        identifierExpr.setParent(parent);
                        exprCol.add(identifierExpr);
                    } else {
                        expr.setParent(parent);
                        exprCol.add(expr);
                    }

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

                    if (this.dbType == DbType.mysql) {
                        this.lexer.nextTokenValue();
                    } else {
                        this.lexer.nextToken();
                    }
                }
            }
        }
    }

    public SQLIdentifierExpr identifier() {
        SQLName name = this.name();
        if (name instanceof SQLIdentifierExpr) {
            return (SQLIdentifierExpr) name;
        } else {
            throw new ParserException("identifier excepted, " + this.lexer.info());
        }
    }

    public SQLName name() {
        long hash = 0L;
        String identName;
        if (this.lexer.token == Token.LITERAL_ALIAS) {
            identName = this.lexer.stringVal();
            this.lexer.nextToken();
        } else if (this.lexer.token == Token.IDENTIFIER) {
            identName = this.lexer.stringVal();
            char c0 = identName.charAt(0);
            if (c0 != '[') {
                hash = this.lexer.hash_lower();
            }

            this.lexer.nextToken();
        } else if (this.lexer.token == Token.LITERAL_CHARS) {
            identName = '\'' + this.lexer.stringVal() + '\'';
            this.lexer.nextToken();
        } else if (this.lexer.token == Token.VARIANT) {
            identName = this.lexer.stringVal();
            this.lexer.nextToken();
        } else {
            switch (this.lexer.token) {
                case CAST:
                case INTERVAL:
                case DEFAULT:
                case KEY:
                case SCHEMA:
                case END:
                case COMMENT:
                case COMPUTE:
                case ENABLE:
                case DISABLE:
                case INITIALLY:
                case SEQUENCE:
                case USER:
                case EXPLAIN:
                case WITH:
                case GRANT:
                case INDEX:
                case PCTFREE:
                case INITRANS:
                case MAXTRANS:
                case SEGMENT:
                case CREATION:
                case IMMEDIATE:
                case DEFERRED:
                case STORAGE:
                case NEXT:
                case MINEXTENTS:
                case MAXEXTENTS:
                case MAXSIZE:
                case PCTINCREASE:
                case FLASH_CACHE:
                case CELL_FLASH_CACHE:
                case NONE:
                case LOB:
                case STORE:
                case ROW:
                case CHUNK:
                case CACHE:
                case NOCACHE:
                case LOGGING:
                case NOCOMPRESS:
                case KEEP_DUPLICATES:
                case EXCEPTIONS:
                case PURGE:
                case FULL:
                case BINARY:
                case ERRORS:
                case BEGIN:
                case MODE:
                case VIEW:
                case ESCAPE:
                case OVER:
                case ORDER:
                case TYPE:
                case OPEN:
                case TRUNCATE:
                case FUNCTION:
                case DESC:
                case DO:
                case GROUP:
                case PARTITION:
                case LEAVE:
                case CLOSE:
                case EXCEPT:
                case INTERSECT:
                case MERGE:
                case MINUS:
                case UNTIL:
                case SHOW:
                case INOUT:
                case INNER:
                case ANY:
                case SOME:
                case ALL:
                case SET:
                case FOR:
                case NOWAIT:
                case COMMIT:
                case ANALYZE:
                case OPTIMIZE:
                case REVOKE:
                case JOIN:
                case UNIQUE:
                case TABLESPACE:
                case GOTO:
                case FULLTEXT:
                    identName = this.lexer.stringVal();
                    this.lexer.nextToken();
                    break;
                case NULL:
                case LBRACE:
                case IF:
                case VARIANT:
                case DAY:
                case INSERT:
                case NEW:
                case LITERAL_NCHARS:
                case DUAL:
                case COLUMN:
                case REPLACE:
                case TO:
                case IDENTIFIED:
                case PASSWORD:
                case WINDOW:
                case OFFSET:
                case SHARE:
                case START:
                case CONNECT:
                case MATCHED:
                case REJECT:
                case UNLIMITED:
                case EXCLUSIVE:
                case ADVISE:
                case REPEAT:
                case TABLE:
                case EXCEPTION:
                case IDENTITY:
                case EXTRACT:
                case MOD:
                case CONCAT:
                case PRIMARY:
                case CONDITION:
                case TOP:
                case MINUTE:
                case SECOND:
                case HOUR:
                case YEAR:
                case MONTH:
                case MICROSECOND:
                case WEEK:
                case QUARTER:
                case SECOND_MICROSECOND:
                case MINUTE_MICROSECOND:
                case MINUTE_SECOND:
                case HOUR_MICROSECOND:
                case HOUR_SECOND:
                case HOUR_MINUTE:
                case DAY_MICROSECOND:
                case DAY_SECOND:
                case DAY_MINUTE:
                case DAY_HOUR:
                case YEAR_MONTH:
                case EXISTS:
                case NOT:
                case FROM:
                case SELECT:
                case TILDE:
                case LEFT:
                case RIGHT:
                case DATABASE:
                case LOCK:
                case BANGBANG:
                case BANG_TILDE:
                case LITERAL_HEX:
                case COLON:
                case EOF:
                case TRUE:
                case FALSE:
                case BITS:
                case GLOBAL:
                case CONTAINS:
                case DELETE:
                case BY:
                case UPDATE:
                case LOOP:
                case UNION:
                case CREATE:
                case DISTINCT:
                case LBRACKET:
                case ON:
                case COLONCOLON:
                case SYSDATE:
                case COMMA:
                case IS:
                case BETWEEN:
                case RPAREN:
                case EQ:
                case BANGEQ:
                case LTGT:
                case LT:
                case LTEQ:
                case GT:
                case GTEQ:
                case STAR:
                case DIV:
                case SLASH:
                case DOT:
                case THEN:
                default:
                    throw new ParserException("illegal name, " + this.lexer.info());
                case LIMIT:
                case CONSTRAINT:
                case OUT:
                case USE:
                case VALUES:
                case TRIGGER:
                case CHECK:
                case LIKE:
                case IN:
                    if (this.dbType != DbType.odps) {
                        throw new ParserException("illegal name, " + this.lexer.info());
                    }

                    identName = this.lexer.stringVal();
                    this.lexer.nextToken();
            }
        }

        if (this.lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
            identName = SQLUtils.forcedNormalize(identName, this.dbType);
        }

        SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr(identName, hash);
        if (this.lexer.keepSourceLocation) {
            this.lexer.computeRowAndColumn();
            identifierExpr.setSourceLine(this.lexer.posLine);
            identifierExpr.setSourceColumn(this.lexer.posColumn);
        }

        SQLName name = this.nameRest(identifierExpr);
        return name;
    }

    public SQLName nameRest(SQLName name) {
        if (this.lexer.token == Token.DOT) {
            this.lexer.nextToken();
            if (this.lexer.token == Token.KEY) {
                name = new SQLPropertyExpr(name, "KEY");
                this.lexer.nextToken();
                return name;
            }

            if (this.lexer.token != Token.LITERAL_ALIAS && this.lexer.token != Token.IDENTIFIER && !this.lexer.getKeywords().containsValue(this.lexer.token)) {
                throw new ParserException("error, " + this.lexer.info());
            }

            String propertyName = this.lexer.stringVal();
            if (this.lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                propertyName = SQLUtils.forcedNormalize(propertyName, this.dbType);
            }

            name = new SQLPropertyExpr(name, propertyName);
            this.lexer.nextToken();
            name = this.nameRest(name);
        }

        return name;
    }

    public boolean isAggregateFunction(String word) {
        long hash_lower = FnvHash.fnv1a_64_lower(word);
        return this.isAggregateFunction(hash_lower);
    }

    protected boolean isAggregateFunction(long hash_lower) {
        return Arrays.binarySearch(this.aggregateFunctionHashCodes, hash_lower) >= 0;
    }

    protected String getAggregateFunction(long hash_lower) {
        int index = Arrays.binarySearch(this.aggregateFunctionHashCodes, hash_lower);
        return index < 0 ? null : this.aggregateFunctions[index];
    }

    protected SQLAggregateExpr parseAggregateExpr(String methodName) {
        SQLAggregateExpr aggregateExpr;
        if (this.lexer.token == Token.ALL) {
            Lexer.SavePoint mark = this.lexer.mark();
            this.lexer.nextToken();
            if (this.lexer.token == Token.DOT) {
                aggregateExpr = new SQLAggregateExpr(methodName);
                this.lexer.reset(mark);
            } else {
                aggregateExpr = new SQLAggregateExpr(methodName, SQLAggregateOption.ALL);
            }
        } else if (this.lexer.token == Token.DISTINCT) {
            aggregateExpr = new SQLAggregateExpr(methodName, SQLAggregateOption.DISTINCT);
            this.lexer.nextToken();
        } else if (this.lexer.identifierEquals(FnvHash.Constants.DEDUPLICATION)) {
            aggregateExpr = new SQLAggregateExpr(methodName, SQLAggregateOption.DEDUPLICATION);
            this.lexer.nextToken();
        } else {
            aggregateExpr = new SQLAggregateExpr(methodName);
        }

        this.exprList(aggregateExpr.getArguments(), aggregateExpr);
        if (this.lexer.token != Token.RPAREN) {
            this.parseAggregateExprRest(aggregateExpr);
        }

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

        this.accept(Token.RPAREN);
        if (this.lexer.identifierEquals(FnvHash.Constants.WITHIN)) {
            this.lexer.nextToken();
            this.accept(Token.GROUP);
            this.accept(Token.LPAREN);
            SQLOrderBy orderBy = this.parseOrderBy();
            aggregateExpr.setWithinGroup(true);
            aggregateExpr.setOrderBy(orderBy);
            this.accept(Token.RPAREN);
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.FILTER)) {
            this.filter(aggregateExpr);
        }

        if (this.lexer.token == Token.OVER) {
            this.over(aggregateExpr);
        }

        return aggregateExpr;
    }

    protected void filter(SQLAggregateExpr x) {
        this.lexer.nextToken();
        this.accept(Token.LPAREN);
        this.accept(Token.WHERE);
        SQLExpr filter = this.expr();
        this.accept(Token.RPAREN);
        x.setFilter(filter);
    }

    protected void over(SQLAggregateExpr aggregateExpr) {
        this.lexer.nextToken();
        if (this.lexer.token != Token.LPAREN) {
            SQLName overRef = this.name();
            aggregateExpr.setOverRef(overRef);
        } else {
            SQLOver over = new SQLOver();
            this.over(over);
            aggregateExpr.setOver(over);
        }
    }

    public void over(SQLOver over) {
        this.lexer.nextToken();
        if (this.lexer.token == Token.PARTITION || this.lexer.identifierEquals("PARTITION")) {
            this.lexer.nextToken();
            this.accept(Token.BY);
            if (this.lexer.token == Token.LPAREN) {
                this.lexer.nextToken();
                this.exprList(over.getPartitionBy(), over);
                this.accept(Token.RPAREN);
                if (over.getPartitionBy().size() == 1 && this.lexer.token == Token.COMMA) {
                    this.lexer.nextToken();
                    this.exprList(over.getPartitionBy(), over);
                }
            } else {
                this.exprList(over.getPartitionBy(), over);
            }
        }

        over.setOrderBy(this.parseOrderBy());
        over.setDistributeBy(this.parseDistributeBy());
        over.setSortBy(this.parseSortBy());
        if (this.lexer.token == Token.OF) {
            this.lexer.nextToken();
            SQLName of = this.name();
            over.setOf(of);
        }

        SQLOver.WindowingType windowingType = null;
        if (!this.lexer.identifierEquals(FnvHash.Constants.ROWS) && this.lexer.token != Token.ROWS) {
            if (this.lexer.identifierEquals(FnvHash.Constants.RANGE)) {
                windowingType = SQLOver.WindowingType.RANGE;
            }
        } else {
            windowingType = SQLOver.WindowingType.ROWS;
        }

        if (windowingType != null) {
            over.setWindowingType(windowingType);
            this.lexer.nextToken();
            if (this.lexer.token == Token.BETWEEN) {
                this.lexer.nextToken();
                if (this.lexer.token != Token.LITERAL_INT && this.lexer.token != Token.LITERAL_FLOAT && this.lexer.token != Token.LITERAL_CHARS) {
                    if (this.lexer.token == Token.IDENTIFIER) {
                        long hash = this.lexer.hash_lower();
                        if (hash != FnvHash.Constants.PRECEDING && hash != FnvHash.Constants.FOLLOWING && hash != FnvHash.Constants.CURRENT && hash != FnvHash.Constants.UNBOUNDED) {
                            SQLExpr betweenBegin = this.primary();
                            over.setWindowingBetweenBegin(betweenBegin);
                        }
                    }
                } else {
                    SQLExpr betweenBegin = this.additive();
                    over.setWindowingBetweenBegin(betweenBegin);
                }

                SQLOver.WindowingBound beginBound = this.parseWindowingBound();
                if (beginBound != null) {
                    over.setWindowingBetweenBeginBound(beginBound);
                }

                this.accept(Token.AND);
                if (this.lexer.token != Token.LITERAL_INT && this.lexer.token != Token.LITERAL_FLOAT && this.lexer.token != Token.LITERAL_CHARS) {
                    if (this.lexer.token == Token.IDENTIFIER) {
                        long hash = this.lexer.hash_lower();
                        if (hash != FnvHash.Constants.PRECEDING && hash != FnvHash.Constants.FOLLOWING && hash != FnvHash.Constants.CURRENT && hash != FnvHash.Constants.UNBOUNDED) {
                            SQLExpr betweenBegin = this.additive();
                            over.setWindowingBetweenEnd(betweenBegin);
                        }
                    }
                } else {
                    SQLExpr betweenEnd = this.additive();
                    over.setWindowingBetweenEnd(betweenEnd);
                }

                SQLOver.WindowingBound endBound = this.parseWindowingBound();
                if (endBound != null) {
                    over.setWindowingBetweenEndBound(endBound);
                }
            } else {
                if (this.lexer.token != Token.LITERAL_INT && this.lexer.token != Token.LITERAL_FLOAT && this.lexer.token != Token.LITERAL_CHARS) {
                    if (this.lexer.token == Token.IDENTIFIER) {
                        long hash = this.lexer.hash_lower();
                        if (hash != FnvHash.Constants.PRECEDING && hash != FnvHash.Constants.FOLLOWING && hash != FnvHash.Constants.CURRENT && hash != FnvHash.Constants.UNBOUNDED) {
                            SQLExpr betweenBegin = this.additive();
                            over.setWindowingBetweenBegin(betweenBegin);
                        }
                    }
                } else {
                    SQLExpr betweenBegin = this.additive();
                    over.setWindowingBetweenBegin(betweenBegin);
                }

                SQLOver.WindowingBound beginBound = this.parseWindowingBound();
                if (beginBound != null) {
                    over.setWindowingBetweenBeginBound(beginBound);
                }
            }
        }

        this.accept(Token.RPAREN);
    }

    protected SQLOver.WindowingBound parseWindowingBound() {
        if (this.lexer.identifierEquals(FnvHash.Constants.PRECEDING)) {
            this.lexer.nextToken();
            return SQLOver.WindowingBound.PRECEDING;
        } else if (this.lexer.identifierEquals(FnvHash.Constants.FOLLOWING)) {
            this.lexer.nextToken();
            return SQLOver.WindowingBound.FOLLOWING;
        } else if (!this.lexer.identifierEquals(FnvHash.Constants.CURRENT) && this.lexer.token != Token.CURRENT) {
            if (this.lexer.identifierEquals(FnvHash.Constants.UNBOUNDED)) {
                this.lexer.nextToken();
                if (this.lexer.identifierEquals(FnvHash.Constants.PRECEDING)) {
                    this.lexer.nextToken();
                    return SQLOver.WindowingBound.UNBOUNDED_PRECEDING;
                } else {
                    this.acceptIdentifier("FOLLOWING");
                    return SQLOver.WindowingBound.UNBOUNDED_FOLLOWING;
                }
            } else {
                return null;
            }
        } else {
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.ROW)) {
                this.lexer.nextToken();
            } else {
                this.accept(Token.ROW);
            }

            return SQLOver.WindowingBound.CURRENT_ROW;
        }
    }

    protected SQLAggregateExpr parseAggregateExprRest(SQLAggregateExpr aggregateExpr) {
        return aggregateExpr;
    }

    public SQLOrderBy parseOrderBy() {
        if (this.lexer.token == Token.ORDER) {
            SQLOrderBy orderBy = new SQLOrderBy();
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.SIBLINGS)) {
                this.lexer.nextToken();
                orderBy.setSibings(true);
            }

            this.accept(Token.BY);
            this.orderBy(orderBy.getItems(), orderBy);
            if (this.lexer.token == Token.ORDER) {
                throw new ParserException(this.lexer.info());
            } else {
                return orderBy;
            }
        } else {
            return null;
        }
    }

    public SQLOrderBy parseDistributeBy() {
        if (this.lexer.token != Token.DISTRIBUTE && !this.lexer.identifierEquals("DISTRIBUTE")) {
            return null;
        } else {
            SQLOrderBy orderBy = new SQLOrderBy();
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.SIBLINGS)) {
                this.lexer.nextToken();
                orderBy.setSibings(true);
            }

            this.accept(Token.BY);
            this.orderBy(orderBy.getItems(), orderBy);
            if (this.lexer.token == Token.ORDER) {
                throw new ParserException(this.lexer.info());
            } else {
                return orderBy;
            }
        }
    }

    public SQLOrderBy parseSortBy() {
        if (this.lexer.token != Token.SORT && !this.lexer.identifierEquals(FnvHash.Constants.SORT)) {
            return null;
        } else {
            SQLOrderBy orderBy = new SQLOrderBy();
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.SIBLINGS)) {
                this.lexer.nextToken();
                orderBy.setSibings(true);
            }

            this.accept(Token.BY);
            this.orderBy(orderBy.getItems(), orderBy);
            if (this.lexer.token == Token.ORDER) {
                throw new ParserException(this.lexer.info());
            } else {
                return orderBy;
            }
        }
    }

    public void orderBy(List<SQLSelectOrderByItem> items, SQLObject parent) {
        SQLSelectOrderByItem item = this.parseSelectOrderByItem();
        item.setParent(parent);
        items.add(item);

        while (this.lexer.token == Token.COMMA) {
            this.lexer.nextToken();
            item = this.parseSelectOrderByItem();
            item.setParent(parent);
            items.add(item);
        }

    }

    public SQLSelectOrderByItem parseSelectOrderByItem() {
        SQLSelectOrderByItem item = new SQLSelectOrderByItem();
        this.setAllowIdentifierMethod(false);

        try {
            SQLExpr expr;
            if (this.lexer.token() == Token.LITERAL_ALIAS) {
                expr = this.name();
                expr = this.primaryRest(expr);
            } else {
                expr = this.expr();
            }

            if (this.isEnabled(SQLParserFeature.IgnoreNameQuotes) && expr instanceof SQLPropertyExpr) {
                SQLPropertyExpr propertyExpr = (SQLPropertyExpr) expr;
                SQLExpr owner = propertyExpr.getOwner();
                if (owner != null) {
                    String ownerStr = SQLUtils.toSQLString(owner);
                    if (ownerStr.length() > 1) {
                        ownerStr = StringUtils.removeNameQuotes(ownerStr);
                    }

                    propertyExpr.setOwner(ownerStr);
                }

                String name = propertyExpr.getName();
                if (name.length() > 1) {
                    name = StringUtils.removeNameQuotes(name);
                    propertyExpr.setName(name);
                }

                expr = propertyExpr;
            }

            item.setExpr(expr);
        } finally {
            this.setAllowIdentifierMethod(true);
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
            this.lexer.nextToken();
            String collate = this.lexer.stringVal();
            item.setCollate(collate);
            this.lexer.nextToken();
        }

        if (this.lexer.token == Token.ASC) {
            this.lexer.nextToken();
            item.setType(SQLOrderingSpecification.ASC);
        } else if (this.lexer.token == Token.DESC) {
            this.lexer.nextToken();
            item.setType(SQLOrderingSpecification.DESC);
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.NULLS)) {
            this.lexer.nextToken();
            if (!this.lexer.identifierEquals(FnvHash.Constants.FIRST) && this.lexer.token != Token.FIRST) {
                if (!this.lexer.identifierEquals(FnvHash.Constants.LAST)) {
                    throw new ParserException("TODO " + this.lexer.info());
                }

                this.lexer.nextToken();
                item.setNullsOrderType(SQLSelectOrderByItem.NullsOrderType.NullsLast);
            } else {
                this.lexer.nextToken();
                item.setNullsOrderType(SQLSelectOrderByItem.NullsOrderType.NullsFirst);
            }
        }

        if (this.lexer.token == Token.HINT) {
            item.setHint(this.parseHint());
        }

        return item;
    }

    public SQLUpdateSetItem parseUpdateSetItem() {
        SQLUpdateSetItem item = new SQLUpdateSetItem();
        if (this.lexer.token == Token.LPAREN) {
            this.lexer.nextToken();
            SQLListExpr list = new SQLListExpr();
            this.exprList(list.getItems(), list);
            this.accept(Token.RPAREN);
            item.setColumn(list);
        } else {
            Token token = this.lexer.token();
            long hash;
            String identName;
            if (token == Token.IDENTIFIER) {
                identName = this.lexer.stringVal();
                hash = this.lexer.hash_lower();
            } else if (token == Token.LITERAL_CHARS) {
                identName = '\'' + this.lexer.stringVal() + '\'';
                hash = 0L;
            } else {
                identName = this.lexer.stringVal();
                hash = 0L;
            }

            this.lexer.nextTokenEq();

            SQLExpr expr;
            String propertyName;
            for (expr = new SQLIdentifierExpr(identName, hash); this.lexer.token() == Token.DOT; expr = new SQLPropertyExpr(expr, propertyName)) {
                this.lexer.nextToken();
                propertyName = this.lexer.stringVal();
                this.lexer.nextTokenEq();
            }

            item.setColumn(expr);
        }

        if (this.lexer.token == Token.LBRACKET && this.dbType == DbType.postgresql) {
            SQLExpr column = item.getColumn();
            column = this.primaryRest(column);
            item.setColumn(column);
        }

        if (this.lexer.token == Token.COLONEQ) {
            this.lexer.nextTokenValue();
        } else {
            if (this.lexer.token != Token.EQ) {
                throw new ParserException("syntax error, expect EQ, actual " + this.lexer.token + " " + this.lexer.info());
            }

            this.lexer.nextTokenValue();
        }

        if ((this.lexer.identifierEquals("CURRENT") || this.lexer.identifierEquals("DATE")) && this.dbType == DbType.db2) {
            String stringVal = this.lexer.stringVal;
            this.lexer.nextToken();
            SQLExpr expr1 = this.expr();
            DB2CurrentTimeExpr dB2CurrentTimeExpr = new DB2CurrentTimeExpr();
            dB2CurrentTimeExpr.setText(stringVal + " " + expr1.toString());
            item.setValue(dB2CurrentTimeExpr);
        } else {
            item.setValue(this.expr());
        }

        return item;
    }

    public final SQLExpr bitAnd() {
        SQLExpr expr = this.shift();
        if (this.lexer.token == Token.AMP) {
            expr = this.bitAndRest(expr);
        }

        return expr;
    }

    public final SQLExpr bitAndRest(SQLExpr expr) {
        while (this.lexer.token == Token.AMP) {
            this.lexer.nextToken();
            SQLExpr rightExp = this.shift();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BitwiseAnd, rightExp, this.getDbType());
        }

        return expr;
    }

    public final SQLExpr bitOr() {
        SQLExpr expr = this.bitAnd();
        if (this.lexer.token == Token.BAR) {
            expr = this.bitOrRest(expr);
        }

        return expr;
    }

    public final SQLExpr bitOrRest(SQLExpr expr) {
        while (this.lexer.token == Token.BAR) {
            this.lexer.nextToken();
            SQLBinaryOperator op = SQLBinaryOperator.BitwiseOr;
            if (this.lexer.token == Token.BAR) {
                this.lexer.nextToken();
                op = SQLBinaryOperator.Concat;
            }

            SQLExpr rightExp = this.bitAnd();
            expr = new SQLBinaryOpExpr(expr, op, rightExp, this.getDbType());
            expr = this.bitAndRest(expr);
        }

        return expr;
    }

    public final SQLExpr inRest(SQLExpr expr) {
        boolean global = false;
        if (this.lexer.token == Token.GLOBAL) {
            global = true;
            this.lexer.nextToken();
        }

        if (this.lexer.token == Token.IN) {
            this.lexer.nextTokenLParen();
            SQLInListExpr inListExpr = new SQLInListExpr(expr);
            List<SQLExpr> targetList = inListExpr.getTargetList();
            if (this.lexer.token != Token.LPAREN) {
                SQLExpr itemExpr = this.primary();
                itemExpr.setParent(inListExpr);
                targetList.add(itemExpr);
            } else {
                this.lexer.nextTokenValue();
                List<SQLCommentHint> hints = null;
                if (this.lexer.token == Token.HINT) {
                    hints = this.parseHints();
                }

                if (this.lexer.token == Token.WITH) {
                    SQLSelect select = this.createSelectParser().select();
                    SQLInSubQueryExpr queryExpr = new SQLInSubQueryExpr(select);
                    queryExpr.setExpr(expr);
                    this.accept(Token.RPAREN);
                    return queryExpr;
                }

                if (this.lexer.token != Token.RPAREN) {
                    label90:
                    while (true) {
                        SQLExpr item;
                        if (this.lexer.token == Token.LITERAL_INT) {
                            item = new SQLIntegerExpr(this.lexer.integerValue());
                            this.lexer.nextToken();
                            if (this.lexer.token != Token.COMMA && this.lexer.token != Token.RPAREN) {
                                item = this.primaryRest(item);
                                item = this.exprRest(item);
                            }
                        } else {
                            item = this.expr();
                        }

                        item.setParent(inListExpr);
                        targetList.add(item);
                        if (!(item instanceof SQLCharExpr) || this.lexer.token != Token.LITERAL_CHARS || this.dbType != DbType.odps) {
                            if (this.lexer.token != Token.COMMA) {
                                switch (this.lexer.token) {
                                    case EXCEPT:
                                    case MINUS:
                                    case UNION:
                                        if (targetList.size() == 1 && targetList.get(0) instanceof SQLQueryExpr) {
                                            item = (SQLQueryExpr) targetList.get(0);
                                            SQLSelectQuery query = this.createSelectParser().queryRest(((SQLQueryExpr) item).getSubQuery().getQuery(), true);
                                            if (query != ((SQLQueryExpr) item).getSubQuery()) {
                                                ((SQLQueryExpr) item).getSubQuery().setQuery(query);
                                            }

                                            if (hints != null && hints.size() > 0) {
                                                ((SQLQueryExpr) item).getSubQuery().setHeadHint((SQLHint) hints.get(0));
                                            }
                                        }
                                    default:
                                        break label90;
                                }
                            }

                            this.lexer.nextTokenValue();
                        }
                    }
                }

                this.accept(Token.RPAREN);
            }

            this.parseQueryPlanHint(inListExpr);
            expr = inListExpr;
            if (targetList.size() == 1) {
                SQLExpr targetExpr = (SQLExpr) targetList.get(0);
                if (targetExpr instanceof SQLQueryExpr) {
                    SQLInSubQueryExpr inSubQueryExpr = new SQLInSubQueryExpr();
                    inSubQueryExpr.setExpr(inListExpr.getExpr());
                    inSubQueryExpr.setSubQuery(((SQLQueryExpr) targetExpr).getSubQuery());
                    inSubQueryExpr.setHint(inListExpr.getHint());
                    if (global) {
                        inSubQueryExpr.setGlobal(true);
                    }

                    expr = inSubQueryExpr;
                }
            }
        } else if (this.lexer.token == Token.CONTAINS) {
            this.lexer.nextTokenLParen();
            SQLContainsExpr containsExpr = new SQLContainsExpr(expr);
            List<SQLExpr> targetList = containsExpr.getTargetList();
            if (this.lexer.token == Token.LPAREN) {
                this.lexer.nextTokenValue();
                if (this.lexer.token == Token.WITH) {
                    SQLSelect select = this.createSelectParser().select();
                    SQLInSubQueryExpr queryExpr = new SQLInSubQueryExpr(select);
                    queryExpr.setExpr(expr);
                    this.accept(Token.RPAREN);
                    return queryExpr;
                }

                while (true) {
                    SQLExpr item;
                    if (this.lexer.token == Token.LITERAL_INT) {
                        item = new SQLIntegerExpr(this.lexer.integerValue());
                        this.lexer.nextToken();
                        if (this.lexer.token != Token.COMMA && this.lexer.token != Token.RPAREN) {
                            item = this.primaryRest(item);
                            item = this.exprRest(item);
                        }
                    } else {
                        item = this.expr();
                    }

                    item.setParent(containsExpr);
                    targetList.add(item);
                    if (this.lexer.token != Token.COMMA) {
                        this.accept(Token.RPAREN);
                        break;
                    }

                    this.lexer.nextTokenValue();
                }
            } else {
                SQLExpr itemExpr = this.primary();
                itemExpr.setParent(containsExpr);
                targetList.add(itemExpr);
            }

            expr = containsExpr;
        }

        return expr;
    }

    public final SQLExpr additive() {
        SQLExpr expr = this.multiplicative();
        if (this.lexer.token == Token.PLUS || this.lexer.token == Token.BARBAR || this.lexer.token == Token.CONCAT || this.lexer.token == Token.SUB) {
            expr = this.additiveRest(expr);
        }

        return expr;
    }

    public SQLExpr additiveRest(SQLExpr expr) {
        Token token = this.lexer.token;
        if (token == Token.PLUS) {
            this.lexer.nextToken();

            while (this.lexer.token == Token.HINT) {
                SQLCommentHint hint = this.parseHint();
                if (expr instanceof SQLObjectImpl) {
                    ((SQLObjectImpl) expr).setHint(hint);
                }
            }

            SQLExpr rightExp = this.multiplicative();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Add, rightExp, this.dbType);
            expr = this.additiveRest(expr);
        } else if (token != Token.BARBAR && token != Token.CONCAT || !this.isEnabled(SQLParserFeature.PipesAsConcat) && DbType.mysql == this.dbType) {
            if (token == Token.SUB) {
                this.lexer.nextToken();
                SQLExpr rightExp = this.multiplicative();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Subtract, rightExp, this.dbType);
                expr = this.additiveRest(expr);
            }
        } else {
            this.lexer.nextToken();
            SQLExpr rightExp = this.multiplicative();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Concat, rightExp, this.dbType);
            expr = this.additiveRest(expr);
        }

        return expr;
    }

    public final SQLExpr shift() {
        SQLExpr expr = this.additive();
        if (this.lexer.token == Token.LTLT || this.lexer.token == Token.GTGT) {
            expr = this.shiftRest(expr);
        }

        return expr;
    }

    public SQLExpr shiftRest(SQLExpr expr) {
        if (this.lexer.token == Token.LTLT) {
            this.lexer.nextToken();
            SQLExpr rightExp = this.additive();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.LeftShift, rightExp, this.dbType);
            expr = this.shiftRest(expr);
        } else if (this.lexer.token == Token.GTGT) {
            this.lexer.nextToken();
            SQLExpr rightExp = this.additive();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.RightShift, rightExp, this.dbType);
            expr = this.shiftRest(expr);
        }

        return expr;
    }

    public SQLExpr and() {
        SQLExpr expr = this.relational();
        if (this.lexer.token == Token.AND || this.lexer.token == Token.AMPAMP) {
            expr = this.andRest(expr);
        }

        return expr;
    }

    public void parseQueryPlanHint(SQLExpr expr) {
        if (this.lexer.token == Token.HINT && (expr instanceof SQLInListExpr || expr instanceof SQLBinaryOpExpr || expr instanceof SQLInSubQueryExpr || expr instanceof SQLExistsExpr || expr instanceof SQLNotExpr || expr instanceof SQLBetweenExpr)) {
            String text = this.lexer.stringVal().trim();
            Lexer hintLex = SQLParserUtils.createLexer(text, this.dbType);
            hintLex.nextToken();
            if (hintLex.token == Token.PLUS) {
                if (expr instanceof SQLBinaryOpExpr) {
                    SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) expr;
                    SQLBinaryOperator operator = binaryOpExpr.getOperator();
                    if (operator != SQLBinaryOperator.BooleanAnd && operator != SQLBinaryOperator.BooleanOr) {
                        binaryOpExpr.setHint(new SQLCommentHint(text));
                    } else if (binaryOpExpr.isBracket()) {
                        binaryOpExpr.setHint(new SQLCommentHint(text));
                    } else {
                        SQLExpr right = binaryOpExpr.getRight();
                        if (right instanceof SQLBinaryOpExpr || right instanceof SQLBetweenExpr) {
                            ((SQLExprImpl) right).setHint(new SQLCommentHint(text));
                        }
                    }
                } else {
                    if (!(expr instanceof SQLObjectImpl)) {
                        throw new ParserException("TODO : " + this.lexer.info());
                    }

                    ((SQLExprImpl) expr).setHint(new SQLCommentHint(text));
                }

                this.lexer.nextToken();
            }
        }

    }

    public SQLExpr andRest(SQLExpr expr) {
        while (true) {
            if (expr instanceof SQLBinaryOpExpr) {
                this.parseQueryPlanHint(expr);
            }

            Token token = this.lexer.token;
            if (token != Token.AND) {
                if (token != Token.AMPAMP) {
                    return expr;
                }

                if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
                    expr.addAfterComment(this.lexer.readAndResetComments());
                }

                this.lexer.nextToken();
                SQLExpr rightExp = this.relational();
                SQLBinaryOperator operator = DbType.postgresql == this.dbType ? SQLBinaryOperator.PG_And : SQLBinaryOperator.BooleanAnd;
                expr = new SQLBinaryOpExpr(expr, operator, rightExp, this.dbType);
            } else {
                if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
                    expr.addAfterComment(this.lexer.readAndResetComments());
                }

                this.lexer.nextToken();
                SQLExpr rightExp = this.relational();
                if (expr instanceof SQLBinaryOpExpr) {
                    this.parseQueryPlanHint(rightExp);
                }

                if (this.lexer.token == Token.AND && this.lexer.isEnabled(SQLParserFeature.EnableSQLBinaryOpExprGroup)) {
                    SQLBinaryOpExprGroup group = new SQLBinaryOpExprGroup(SQLBinaryOperator.BooleanAnd, this.dbType);
                    group.add(expr);
                    group.add(rightExp);
                    if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
                        rightExp.addAfterComment(this.lexer.readAndResetComments());
                    }

                    while (true) {
                        this.lexer.nextToken();
                        SQLExpr more = this.relational();
                        if (more instanceof SQLBinaryOpExpr) {
                            this.parseQueryPlanHint(more);
                        }

                        group.add(more);
                        if (this.lexer.token != Token.AND) {
                            expr = group;
                            break;
                        }

                        if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
                            more.addAfterComment(this.lexer.readAndResetComments());
                        }
                    }
                } else {
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BooleanAnd, rightExp, this.dbType);
                }
            }
        }
    }

    public SQLExpr xor() {
        SQLExpr expr = this.and();
        if (this.lexer.token == Token.XOR) {
            expr = this.xorRest(expr);
        }

        return expr;
    }

    public SQLExpr xorRest(SQLExpr expr) {
        while (this.lexer.token == Token.XOR) {
            this.lexer.nextToken();
            SQLExpr rightExp = this.and();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BooleanXor, rightExp, this.dbType);
        }

        return expr;
    }

    public SQLExpr or() {
        SQLExpr expr = this.xor();
        if (this.lexer.token == Token.OR || this.lexer.token == Token.BARBAR) {
            expr = this.orRest(expr);
        }

        return expr;
    }

    public SQLExpr orRest(SQLExpr expr) {
        while (true) {
            if (this.lexer.token == Token.OR) {
                this.lexer.nextToken();
                SQLExpr rightExp = this.xor();
                if (this.lexer.token == Token.OR && this.lexer.isEnabled(SQLParserFeature.EnableSQLBinaryOpExprGroup)) {
                    SQLBinaryOpExprGroup group = new SQLBinaryOpExprGroup(SQLBinaryOperator.BooleanOr, this.dbType);
                    group.add(expr);
                    group.add(rightExp);
                    if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
                        rightExp.addAfterComment(this.lexer.readAndResetComments());
                    }

                    while (true) {
                        this.lexer.nextToken();
                        SQLExpr more = this.xor();
                        group.add(more);
                        if (this.lexer.token != Token.OR) {
                            expr = group;
                            break;
                        }

                        if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
                            more.addAfterComment(this.lexer.readAndResetComments());
                        }
                    }
                } else {
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BooleanOr, rightExp, this.dbType);
                }
            } else {
                if (this.lexer.token != Token.BARBAR) {
                    return expr;
                }

                this.lexer.nextToken();
                SQLExpr rightExp = this.xor();
                SQLBinaryOperator op = DbType.mysql == this.dbType && !this.isEnabled(SQLParserFeature.PipesAsConcat) ? SQLBinaryOperator.BooleanOr : SQLBinaryOperator.Concat;
                expr = new SQLBinaryOpExpr(expr, op, rightExp, this.dbType);
            }
        }
    }

    public SQLExpr relational() {
        SQLExpr expr = this.bitOr();
        return this.relationalRest(expr);
    }

    public SQLExpr relationalRest(SQLExpr expr) {
        Token token = this.lexer.token;
        switch (token) {
            case QUES: {
                if (this.dbType == DbType.postgresql) {
                    this.lexer.nextToken();
                    SQLExpr rightExp = this.bitOr();
                    rightExp = this.relationalRest(rightExp);
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.JSONContains, rightExp, this.getDbType());
                }
                break;
            }
            case IDENTIFIER: {
                long hash = this.lexer.hash_lower;
                if (hash == FnvHash.Constants.SOUNDS) {
                    this.lexer.nextToken();
                    this.accept(Token.LIKE);
                    SQLExpr rightExp = this.bitOr();
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.SoudsLike, rightExp, this.getDbType());
                } else if (hash == FnvHash.Constants.REGEXP) {
                    this.lexer.nextToken();
                    SQLExpr rightExp = this.bitOr();
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.RegExp, rightExp, DbType.mysql);
                } else {
                    if (hash != FnvHash.Constants.SIMILAR || DbType.postgresql != this.lexer.dbType) {
                        return expr;
                    }

                    this.lexer.nextToken();
                    this.accept(Token.TO);
                    SQLExpr rightExp = this.bitOr();
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.SIMILAR_TO, rightExp, this.getDbType());
                }
                break;
            }
            case BANG: {
                if (this.dbType == DbType.odps) {
                    this.lexer.nextToken();
                    expr = this.notRationalRest(expr);
                }
                break;
            }
            case NOT: {
                this.lexer.nextToken();
                expr = this.notRationalRest(expr);
                break;
            }
            case TILDE: {
                if (DbType.postgresql == this.lexer.dbType) {
                    this.lexer.nextToken();
                    SQLExpr rightExp = this.relational();
                    rightExp = this.relationalRest(rightExp);
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.POSIX_Regular_Match, rightExp, this.getDbType());
                }
                break;
            }
            case BANG_TILDE: {
                if (DbType.postgresql != this.lexer.dbType) {
                    return expr;
                }

                this.lexer.nextToken();
                SQLExpr rightExp = this.relational();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.POSIX_Regular_Not_Match, rightExp, this.getDbType());
                break;
            }
            case GLOBAL:
            case CONTAINS:
            case IN:
                expr = this.inRest(expr);
                break;
            case LIKE:
            case LIKEC:
            case LIKE2:
            case LIKE4: {
                String like = this.lexer.token().toString();
                this.lexer.nextTokenValue();
                SQLExpr rightExp = this.bitOr();
                if (rightExp.getClass() == SQLIdentifierExpr.class) {
                    String name = ((SQLIdentifierExpr) rightExp).getName();
                    int length = name.length();
                    if (length > 1 && name.charAt(0) == name.charAt(length - 1) && name.charAt(0) != '`') {
                        rightExp = new SQLCharExpr(name.substring(1, length - 1));
                    }
                }

                if ("LIKE".equals(like)) {
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Like, rightExp, this.getDbType());
                }

                if ("LIKE2".equals(like)) {
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Like2, rightExp, this.getDbType());
                }

                if ("LIKE4".equals(like)) {
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Like4, rightExp, this.getDbType());
                }

                if ("LIKEC".equals(like)) {
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Likec, rightExp, this.getDbType());
                }

                if (this.lexer.token == Token.ESCAPE) {
                    this.lexer.nextToken();
                    SQLExpr var41 = this.primary();
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Escape, var41, this.getDbType());
                }
                break;
            }
            case IS: {
                this.lexer.nextTokenNotOrNull();
                SQLBinaryOperator op;
                if (this.lexer.token == Token.NOT) {
                    op = SQLBinaryOperator.IsNot;
                    this.lexer.nextTokenNotOrNull();
                } else {
                    op = SQLBinaryOperator.Is;
                }

                SQLExpr rightExp;
                if (this.lexer.identifierEquals(FnvHash.Constants.JSON)) {
                    this.lexer.nextToken();
                    String name = "JSON";
                    if (this.lexer.identifierEquals(FnvHash.Constants.VALUE)) {
                        this.lexer.nextToken();
                        name = "JSON VALUE";
                    } else if (this.lexer.identifierEquals(FnvHash.Constants.OBJECT)) {
                        this.lexer.nextToken();
                        name = "JSON OBJECT";
                    } else if (this.lexer.identifierEquals(FnvHash.Constants.ARRAY)) {
                        this.lexer.nextToken();
                        name = "JSON ARRAY";
                    } else if (this.lexer.identifierEquals(FnvHash.Constants.SCALAR)) {
                        this.lexer.nextToken();
                        name = "JSON SCALAR";
                    }

                    rightExp = new SQLIdentifierExpr(name);
                } else if (this.lexer.token == Token.DISTINCT) {
                    this.lexer.nextToken();
                    this.accept(Token.FROM);
                    if (op == SQLBinaryOperator.Is) {
                        op = SQLBinaryOperator.IsDistinctFrom;
                    } else {
                        op = SQLBinaryOperator.IsNotDistinctFrom;
                    }

                    rightExp = this.bitOr();
                } else {
                    rightExp = this.primary();
                }

                expr = new SQLBinaryOpExpr(expr, op, rightExp, this.dbType);
                break;
            }
            case BETWEEN: {
                this.lexer.nextToken();
                SQLExpr beginExpr = this.relational();
                this.accept(Token.AND);
                SQLExpr endExpr = this.relational();
                expr = new SQLBetweenExpr(expr, beginExpr, endExpr);
                this.parseQueryPlanHint(expr);
                break;
            }
            case EQ: {
                this.lexer.nextToken();

                SQLExpr rightExp;
                try {
                    rightExp = this.bitOr();
                } catch (EOFParserException e) {
                    throw new ParserException("EOF, " + expr + "=", e);
                }

                if (this.lexer.token == Token.COLONEQ) {
                    this.lexer.nextToken();
                    SQLExpr colonExpr = this.expr();
                    rightExp = new SQLBinaryOpExpr(rightExp, SQLBinaryOperator.Assignment, colonExpr, this.dbType);
                }

                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Equality, rightExp, this.dbType);
                break;
            }
            case BANGEQ:
            case CARETEQ: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                if (this.dbType == DbType.sqlserver) {
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BitwiseXorEQ, rightExp, this.dbType);
                } else {
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.NotEqual, rightExp, this.dbType);
                }
                break;
            }
            case LTGT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.LessThanOrGreater, rightExp, this.getDbType());
                break;
            }
            case LT: {
                SQLBinaryOperator op = SQLBinaryOperator.LessThan;
                this.lexer.nextToken();
                if (this.lexer.token == Token.EQ) {
                    this.lexer.nextToken();
                    op = SQLBinaryOperator.LessThanOrEqual;
                }

                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, op, rightExp, this.getDbType());
                break;
            }
            case LTEQ: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.LessThanOrEqual, rightExp, this.getDbType());
                break;
            }
            case GT: {
                SQLBinaryOperator op = SQLBinaryOperator.GreaterThan;
                this.lexer.nextToken();
                if (this.lexer.token == Token.EQ) {
                    this.lexer.nextToken();
                    op = SQLBinaryOperator.GreaterThanOrEqual;
                }

                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, op, rightExp, this.getDbType());
                break;
            }
            case GTEQ: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.GreaterThanOrEqual, rightExp, this.getDbType());
                break;
            }
            case NOTNULL:
            case ISNULL: {
                SQLBinaryOperator op;
                if (this.lexer.token == Token.NOTNULL) {
                    op = SQLBinaryOperator.NotNull;
                } else {
                    op = SQLBinaryOperator.IsNull;
                }

                expr = new SQLBinaryOpExpr(expr, op, new SQLIdentifierExpr(""), this.dbType);
                this.lexer.nextToken();
                break;
            }
            case EQGT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.expr();
                String argumentName = ((SQLIdentifierExpr) expr).getName();
                expr = new OracleArgumentExpr(argumentName, rightExp);
                break;
            }
            case COLONEQ: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.expr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Assignment, rightExp, this.dbType);
                break;
            }
            case LTEQGT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.LessThanOrEqualOrGreaterThan, rightExp, this.getDbType());
                break;
            }
            case BANGLT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.NotLessThan, rightExp, this.getDbType());
                break;
            }
            case BANGGT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.NotGreaterThan, rightExp, this.getDbType());
                break;
            }
            case ILIKE: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.ILike, rightExp, this.getDbType());
                break;
            }
            case MONKEYS_AT_AT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.AT_AT, rightExp, this.getDbType());
                break;
            }
            case MONKEYS_AT_GT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Array_Contains, rightExp, this.getDbType());
                break;
            }
            case LT_MONKEYS_AT: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                rightExp = this.relationalRest(rightExp);
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Array_ContainedBy, rightExp, this.getDbType());
                break;
            }
            case EQEQ: {
                if (this.dbType == DbType.odps || this.dbType == DbType.hive) {
                    this.lexer.nextToken();

                    SQLExpr rightExp;
                    try {
                        rightExp = this.bitOr();
                    } catch (EOFParserException e) {
                        throw new ParserException("EOF, " + expr + "=", e);
                    }

                    if (this.lexer.token == Token.COLONEQ) {
                        this.lexer.nextToken();
                        SQLExpr colonExpr = this.expr();
                        rightExp = new SQLBinaryOpExpr(rightExp, SQLBinaryOperator.Assignment, colonExpr, this.dbType);
                    }

                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Equality, rightExp, this.dbType);
                }
                break;
            }
            case TILDE_STAR: {
                if (DbType.postgresql != this.lexer.dbType) {
                    return expr;
                }

                this.lexer.nextToken();
                SQLExpr rightExp = this.relational();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.POSIX_Regular_Match_Insensitive, rightExp, this.getDbType());
                break;
            }
            case BANG_TILDE_STAR: {
                if (DbType.postgresql != this.lexer.dbType) {
                    return expr;
                }

                this.lexer.nextToken();
                SQLExpr rightExp = this.relational();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.POSIX_Regular_Not_Match_POSIX_Regular_Match_Insensitive, rightExp, this.getDbType());
                break;
            }
            case TILDE_EQ: {
                if (DbType.postgresql != this.lexer.dbType) {
                    return expr;
                }

                this.lexer.nextToken();
                SQLExpr rightExp = this.relational();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.SAME_AS, rightExp, this.getDbType());
                break;
            }
            case RLIKE: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.RLike, rightExp, this.getDbType());
                break;
            }
            case AddAssignment: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.AddAssignment, rightExp, this.getDbType());
                break;
            }
            case SubtractAssignment: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.SubtractAssignment, rightExp, this.getDbType());
                break;
            }
            case MultiplyAssignment: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.MultiplyAssignment, rightExp, this.getDbType());
                break;
            }
            case DivideAssignment: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.DivideAssignment, rightExp, this.getDbType());
                break;
            }
            case ModulusAssignment: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.ModulusAssignment, rightExp, this.getDbType());
                break;
            }
            case BitwiseAndAssignment: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BitwiseAndAssignment, rightExp, this.getDbType());
                break;
            }
            case BitwiseOrAssignment: {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BitwiseOrAssignment, rightExp, this.getDbType());
                break;
            }
            default:
                return expr;
        }

        switch (this.lexer.token) {
            case NOT:
            case CONTAINS:
            case LIKE:
            case IN:
            case IS:
            case BETWEEN:
            case EQ:
            case BANGEQ:
            case LTGT:
            case LT:
            case LTEQ:
            case GT:
            case GTEQ:
            case LTEQGT:
            case BANG_TILDE_STAR:
            case TILDE_EQ:
                expr = this.relationalRest(expr);
            default:
                return expr;
        }
    }

    public SQLExpr notRationalRest(SQLExpr expr) {
        switch (this.lexer.token) {
            case LPAREN:
                expr = this.primary();
                break;
            case IDENTIFIER:
                long hash = this.lexer.hash_lower();
                if (hash == FnvHash.Constants.REGEXP) {
                    this.lexer.nextToken();
                    SQLExpr rightExp = this.bitOr();
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.NotRegExp, rightExp, this.getDbType());
                    expr = this.relationalRest(expr);
                }
                break;
            case CONTAINS:
                this.lexer.nextToken();
                SQLContainsExpr containsExpr = new SQLContainsExpr(expr, true);
                if (this.lexer.token == Token.LPAREN) {
                    this.lexer.nextToken();
                    this.exprList(containsExpr.getTargetList(), containsExpr);
                    expr = containsExpr;
                    switch (this.lexer.token) {
                        case MINUS:
                        case UNION:
                            List<SQLExpr> targetList = containsExpr.getTargetList();
                            if (targetList.size() == 1 && targetList.get(0) instanceof SQLQueryExpr) {
                                SQLQueryExpr queryExpr = (SQLQueryExpr) targetList.get(0);
                                SQLSelectQuery query = this.createSelectParser().queryRest(queryExpr.getSubQuery().getQuery(), true);
                                if (query != queryExpr.getSubQuery()) {
                                    queryExpr.getSubQuery().setQuery(query);
                                }
                            }
                        default:
                            this.accept(Token.RPAREN);
                    }
                } else {
                    SQLExpr valueExpr = this.primary();
                    valueExpr.setParent(containsExpr);
                    containsExpr.getTargetList().add(valueExpr);
                    expr = containsExpr;
                }

                if (containsExpr.getTargetList().size() == 1) {
                    SQLExpr targetExpr = (SQLExpr) containsExpr.getTargetList().get(0);
                    if (targetExpr instanceof SQLQueryExpr) {
                        SQLInSubQueryExpr inSubQueryExpr = new SQLInSubQueryExpr();
                        inSubQueryExpr.setNot(true);
                        inSubQueryExpr.setExpr(containsExpr.getExpr());
                        inSubQueryExpr.setSubQuery(((SQLQueryExpr) targetExpr).getSubQuery());
                        expr = inSubQueryExpr;
                    }
                }
                break;
            case LIKE:
                this.lexer.nextTokenValue();
                SQLExpr rightExp = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.NotLike, rightExp, this.getDbType());
                if (this.lexer.token == Token.ESCAPE) {
                    this.lexer.nextToken();
                    rightExp = this.bitOr();
                    expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Escape, rightExp, this.getDbType());
                }
                break;
            case IN:
                this.lexer.nextToken();
                SQLInListExpr inListExpr = new SQLInListExpr(expr, true);
                if (this.lexer.token == Token.LPAREN) {
                    this.lexer.nextToken();
                    this.exprList(inListExpr.getTargetList(), inListExpr);
                    expr = inListExpr;
                    switch (this.lexer.token) {
                        case MINUS:
                        case UNION:
                            List<SQLExpr> targetList = inListExpr.getTargetList();
                            if (targetList.size() == 1 && targetList.get(0) instanceof SQLQueryExpr) {
                                SQLQueryExpr queryExpr = (SQLQueryExpr) targetList.get(0);
                                SQLSelectQuery query = this.createSelectParser().queryRest(queryExpr.getSubQuery().getQuery(), true);
                                if (query != queryExpr.getSubQuery()) {
                                    queryExpr.getSubQuery().setQuery(query);
                                }
                            }
                        default:
                            this.accept(Token.RPAREN);
                    }
                } else {
                    SQLExpr valueExpr = this.primary();
                    valueExpr.setParent(inListExpr);
                    inListExpr.getTargetList().add(valueExpr);
                    expr = inListExpr;
                }

                this.parseQueryPlanHint(inListExpr);
                if (inListExpr.getTargetList().size() == 1) {
                    SQLExpr targetExpr = (SQLExpr) inListExpr.getTargetList().get(0);
                    if (targetExpr instanceof SQLQueryExpr) {
                        SQLInSubQueryExpr inSubQueryExpr = new SQLInSubQueryExpr();
                        inSubQueryExpr.setNot(true);
                        inSubQueryExpr.setExpr(inListExpr.getExpr());
                        inSubQueryExpr.setSubQuery(((SQLQueryExpr) targetExpr).getSubQuery());
                        expr = inSubQueryExpr;
                    }
                }
                break;
            case BETWEEN:
                this.lexer.nextToken();
                SQLExpr beginExpr = this.relational();
                this.accept(Token.AND);
                SQLExpr endExpr = this.relational();
                expr = new SQLBetweenExpr(expr, true, beginExpr, endExpr);
                break;
            case ILIKE:
                this.lexer.nextToken();
                SQLExpr rightExp2 = this.bitOr();
                return new SQLBinaryOpExpr(expr, SQLBinaryOperator.NotILike, rightExp2, this.getDbType());
            case RLIKE:
                this.lexer.nextToken();
                SQLExpr rightExp3 = this.bitOr();
                expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.NotRLike, rightExp3, this.getDbType());
                expr = this.relationalRest(expr);
                break;
            default:
                throw new ParserException("TODO " + this.lexer.info());
        }

        return expr;
    }

    public SQLDataType parseDataType() {
        return this.parseDataType(true);
    }

    public SQLDataType parseDataType(boolean restrict) {
        Token token = this.lexer.token;
        if (token != Token.DEFAULT && token != Token.NOT && token != Token.NULL) {
            if (this.lexer.identifierEquals(FnvHash.Constants.ARRAY)) {
                this.lexer.nextToken();
                if (this.lexer.token == Token.LPAREN) {
                    this.lexer.nextToken();
                    SQLArrayDataType array = new SQLArrayDataType((SQLDataType) null, this.dbType);
                    this.exprList(array.getArguments(), array);
                    this.accept(Token.RPAREN);
                    return array;
                } else {
                    this.accept(Token.LT);
                    SQLDataType itemType = this.parseDataType();
                    if (this.lexer.token == Token.GTGT) {
                        this.lexer.token = Token.GT;
                    } else {
                        this.accept(Token.GT);
                    }

                    SQLArrayDataType array = new SQLArrayDataType(itemType, this.dbType);
                    if (this.lexer.token == Token.LPAREN) {
                        this.lexer.nextToken();
                        this.exprList(array.getArguments(), array);
                        this.accept(Token.RPAREN);
                    }

                    return array;
                }
            } else if (this.lexer.identifierEquals(FnvHash.Constants.MAP)) {
                this.lexer.nextToken();
                if (this.lexer.token == Token.LPAREN) {
                    this.lexer.nextToken();
                    SQLDataType keyType = this.parseDataType();
                    this.accept(Token.COMMA);
                    SQLDataType valueType = this.parseDataType();
                    this.accept(Token.RPAREN);
                    return new SQLMapDataType(keyType, valueType, this.dbType);
                } else {
                    this.accept(Token.LT);
                    SQLDataType keyType = this.parseDataType();
                    this.accept(Token.COMMA);
                    SQLDataType valueType = this.parseDataType();
                    if (this.lexer.token == Token.GTGT) {
                        this.lexer.token = Token.GT;
                    } else {
                        this.accept(Token.GT);
                    }

                    return new SQLMapDataType(keyType, valueType, this.dbType);
                }
            } else if (this.lexer.identifierEquals(FnvHash.Constants.STRUCT)) {
                this.lexer.nextToken();
                SQLStructDataType struct = new SQLStructDataType(this.dbType);
                this.accept(Token.LT);

                while (true) {
                    SQLName name;
                    switch (this.lexer.token) {
                        case TO:
                        case ORDER:
                        case GROUP:
                        case FROM:
                            name = new SQLIdentifierExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                            break;
                        default:
                            name = this.name();
                    }

                    this.accept(Token.COLON);
                    SQLDataType dataType = this.parseDataType();
                    SQLStructDataType.Field field = struct.addField(name, dataType);
                    if (this.lexer.token == Token.COMMENT) {
                        this.lexer.nextToken();
                        SQLCharExpr chars = (SQLCharExpr) this.primary();
                        field.setComment(chars.getText());
                    }

                    if (this.lexer.token() != Token.COMMA) {
                        if (this.lexer.token == Token.GTGT) {
                            this.lexer.token = Token.GT;
                        } else {
                            this.accept(Token.GT);
                        }

                        return struct;
                    }

                    this.lexer.nextToken();
                }
            } else if (!this.lexer.identifierEquals(FnvHash.Constants.ROW) && this.lexer.token != Token.ROW) {
                if (this.lexer.identifierEquals(FnvHash.Constants.NESTED) && this.dbType == DbType.clickhouse) {
                    this.lexer.nextToken();
                    this.accept(Token.LPAREN);
                    SQLStructDataType struct = new SQLStructDataType(this.dbType);

                    while (true) {
                        SQLName name;
                        switch (this.lexer.token) {
                            case TO:
                            case ORDER:
                            case GROUP:
                            case FROM:
                                name = new SQLIdentifierExpr(this.lexer.stringVal());
                                this.lexer.nextToken();
                                break;
                            default:
                                name = this.name();
                        }

                        SQLDataType dataType = this.parseDataType();
                        SQLStructDataType.Field field = struct.addField(name, dataType);
                        if (this.lexer.token == Token.COMMENT) {
                            this.lexer.nextToken();
                            SQLCharExpr chars = (SQLCharExpr) this.primary();
                            field.setComment(chars.getText());
                        }

                        if (this.lexer.token() != Token.COMMA) {
                            this.accept(Token.RPAREN);
                            return struct;
                        }

                        this.lexer.nextToken();
                    }
                } else if (this.lexer.identifierEquals(FnvHash.Constants.UNIONTYPE)) {
                    this.lexer.nextToken();
                    this.accept(Token.LT);
                    SQLUnionDataType unionType = new SQLUnionDataType();

                    while (true) {
                        SQLDataType item = this.parseDataType();
                        unionType.add(item);
                        if (this.lexer.token != Token.COMMA) {
                            this.accept(Token.GT);
                            return unionType;
                        }

                        this.lexer.nextToken();
                    }
                } else if (!this.lexer.identifierEquals(FnvHash.Constants.GENERATED) && !this.lexer.identifierEquals(FnvHash.Constants.RENAME)) {
                    if (this.lexer.token == Token.INTERVAL) {
                        String name = this.lexer.token.name;
                        SQLDB2TimeIntervalDataType sqldb2TimeIntervalDataType = new SQLDB2TimeIntervalDataType(name);
                        this.accept(Token.INTERVAL);
                        String intervalTime = this.lexer.stringVal;
                        sqldb2TimeIntervalDataType.setIntervalTime(intervalTime);
                        this.lexer.nextToken();
                        this.accept(Token.TO);
                        sqldb2TimeIntervalDataType.setTo(true);
                        String toTime = this.lexer.stringVal;
                        sqldb2TimeIntervalDataType.setToTime(this.lexer.stringVal);
                        this.lexer.nextToken();
                        sqldb2TimeIntervalDataType.setName(name + " " + intervalTime + " TO " + toTime);
                        return sqldb2TimeIntervalDataType;
                    } else {
                        SQLName typeExpr = this.name();
                        long typeNameHashCode = typeExpr.nameHashCode64();
                        String typeName = typeExpr.toString();
                        if (typeNameHashCode == FnvHash.Constants.LONG && this.lexer.identifierEquals(FnvHash.Constants.BYTE) && DbType.mysql == this.dbType) {
                            typeName = typeName + ' ' + this.lexer.stringVal();
                            this.lexer.nextToken();
                        } else if (typeNameHashCode == FnvHash.Constants.DOUBLE) {
                            if (DbType.postgresql == this.dbType) {
                                typeName = typeName + ' ' + this.lexer.stringVal();
                                this.lexer.nextToken();
                            } else if (DbType.mysql == this.dbType && this.lexer.identifierEquals(FnvHash.Constants.PRECISION)) {
                                typeName = typeName + ' ' + this.lexer.stringVal();
                                this.lexer.nextToken();
                            }
                        }

                        if (typeNameHashCode == FnvHash.Constants.UNSIGNED) {
                            if (this.lexer.token == Token.IDENTIFIER) {
                                typeName = typeName + ' ' + this.lexer.stringVal();
                                this.lexer.nextToken();
                            }
                        } else if (typeNameHashCode == FnvHash.Constants.SIGNED) {
                            if (this.lexer.token == Token.IDENTIFIER) {
                                typeName = typeName + ' ' + this.lexer.stringVal();
                                this.lexer.nextToken();
                            }
                        } else if (this.isCharType(typeNameHashCode)) {
                            SQLCharacterDataType charType = new SQLCharacterDataType(typeName);
                            if (this.lexer.token == Token.LBRACKET) {
                                SQLArrayDataType arrayDataType = new SQLArrayDataType(charType, this.dbType);
                                this.lexer.nextToken();
                                this.accept(Token.RBRACKET);
                                arrayDataType.putAttribute("ads.arrayDataType", Boolean.TRUE);
                                return arrayDataType;
                            }

                            if (this.lexer.token == Token.LPAREN) {
                                this.lexer.nextToken();
                                if (typeNameHashCode == FnvHash.Constants.ENUM) {
                                    this.exprList(charType.getArguments(), charType);
                                } else {
                                    SQLExpr arg = this.expr();
                                    arg.setParent(charType);
                                    charType.addArgument(arg);
                                }

                                this.accept(Token.RPAREN);
                            }

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

                            if (this.lexer.identifierEquals(FnvHash.Constants.ARRAY)) {
                                return this.parseDataTypeRest(charType);
                            }

                            if (this.lexer.token == Token.LBRACKET) {
                                return this.parseDataTypeRest(charType);
                            }

                            return charType;
                        }

                        if (!"national".equalsIgnoreCase(typeName) || !this.lexer.identifierEquals(FnvHash.Constants.CHAR) && !this.lexer.identifierEquals(FnvHash.Constants.VARCHAR)) {
                            if ("character".equalsIgnoreCase(typeName) && "varying".equalsIgnoreCase(this.lexer.stringVal())) {
                                typeName = typeName + ' ' + this.lexer.stringVal();
                                this.lexer.nextToken();
                            }

                            SQLDataType dataType = new SQLDataTypeImpl(typeName);
                            dataType.setDbType(this.dbType);
                            if (this.lexer.token == Token.LBRACKET) {
                                dataType = new SQLArrayDataType(dataType, this.dbType);
                                this.lexer.nextToken();
                                if (this.lexer.token == Token.LITERAL_INT) {
                                    SQLExpr arg = this.expr();
                                    arg.setParent(dataType);
                                    dataType.getArguments().add(arg);
                                }

                                this.accept(Token.RBRACKET);
                                dataType.putAttribute("ads.arrayDataType", Boolean.TRUE);
                            }

                            return this.parseDataTypeRest(dataType);
                        } else {
                            typeName = typeName + ' ' + this.lexer.stringVal();
                            this.lexer.nextToken();
                            SQLCharacterDataType charType = new SQLCharacterDataType(typeName);
                            if (this.lexer.token == Token.LPAREN) {
                                this.lexer.nextToken();
                                SQLExpr arg = this.expr();
                                arg.setParent(charType);
                                charType.addArgument(arg);
                                this.accept(Token.RPAREN);
                            }

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

                            return (SQLDataType) (this.lexer.identifierEquals(FnvHash.Constants.ARRAY) ? this.parseDataTypeRest(charType) : charType);
                        }
                    }
                } else {
                    return null;
                }
            } else {
                this.lexer.nextToken();
                return this.parseSqlRowDataType();
            }
        } else {
            return null;
        }
    }

    private SQLRowDataType parseSqlRowDataType() {
        SQLRowDataType struct = new SQLRowDataType(this.dbType);
        this.accept(Token.LPAREN);

        while (true) {
            SQLDataType dataType = null;
            Lexer.SavePoint mark = this.lexer.mark();
            SQLName name;
            switch (this.lexer.token) {
                case ROW:
                    this.lexer.nextToken();
                    name = null;
                    dataType = this.parseSqlRowDataType();
                    break;
                case TO:
                case ORDER:
                case GROUP:
                case FROM:
                    name = new SQLIdentifierExpr(this.lexer.stringVal());
                    this.lexer.nextToken();
                    break;
                default:
                    name = this.name();
            }

            if (this.lexer.token == Token.COMMA) {
                this.lexer.reset(mark);
                SQLDataType var7 = this.parseDataType();
                struct.addField((SQLName) null, var7);
                this.lexer.nextToken();
            } else {
                if (this.lexer.token != Token.RPAREN) {
                    dataType = this.parseDataType();
                }

                SQLStructDataType.Field field = struct.addField(name, dataType);
                if (this.lexer.token == Token.COMMENT) {
                    this.lexer.nextToken();
                    SQLCharExpr chars = (SQLCharExpr) this.primary();
                    field.setComment(chars.getText());
                }

                if (this.lexer.token() != Token.COMMA) {
                    this.accept(Token.RPAREN);
                    return struct;
                }

                this.lexer.nextToken();
            }
        }
    }

    protected SQLDataType parseDataTypeRest(SQLDataType dataType) {
        if (this.lexer.token == Token.LPAREN) {
            this.lexer.nextToken();
            this.exprList(dataType.getArguments(), dataType);
            this.accept(Token.RPAREN);
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.PRECISION) && dataType.nameHashCode64() == FnvHash.Constants.DOUBLE) {
            this.lexer.nextToken();
            dataType.setName("DOUBLE PRECISION");
        }

        if (FnvHash.Constants.TIMESTAMP == dataType.nameHashCode64() || FnvHash.Constants.TIME == dataType.nameHashCode64()) {
            if (this.lexer.identifierEquals(FnvHash.Constants.WITHOUT)) {
                this.lexer.nextToken();
                this.acceptIdentifier("TIME");
                this.acceptIdentifier("ZONE");
                dataType.setWithTimeZone(false);
            } else if (this.lexer.token == Token.WITH) {
                Lexer.SavePoint mark = this.lexer.mark();
                this.lexer.nextToken();
                if (this.lexer.identifierEquals(FnvHash.Constants.TIME)) {
                    this.lexer.nextToken();
                    this.acceptIdentifier("ZONE");
                    dataType.setWithTimeZone(true);
                } else {
                    this.lexer.reset(mark);
                }
            }
        }

        return dataType;
    }

    protected boolean isCharType(String dataTypeName) {
        long hash = FnvHash.hashCode64(dataTypeName);
        return this.isCharType(hash);
    }

    protected boolean isCharType(long hash) {
        return hash == FnvHash.Constants.CHAR || hash == FnvHash.Constants.VARCHAR || hash == FnvHash.Constants.NCHAR || hash == FnvHash.Constants.NVARCHAR || hash == FnvHash.Constants.TINYTEXT || hash == FnvHash.Constants.TEXT || hash == FnvHash.Constants.MEDIUMTEXT || hash == FnvHash.Constants.LONGTEXT || hash == FnvHash.Constants.ENUM;
    }

    protected SQLDataType parseCharTypeRest(SQLCharacterDataType charType) {
        if (this.lexer.token == Token.BINARY) {
            charType.setHasBinary(true);
            this.lexer.nextToken();
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
            this.lexer.nextToken();
            this.accept(Token.SET);
            if (this.lexer.token != Token.IDENTIFIER && this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.BINARY) {
                throw new ParserException(this.lexer.info());
            }

            charType.setCharSetName(this.lexer.stringVal());
            this.lexer.nextToken();
        } else if (this.lexer.identifierEquals(FnvHash.Constants.CHARSET)) {
            this.lexer.nextToken();
            if (this.lexer.token != Token.IDENTIFIER && this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.BINARY) {
                throw new ParserException(this.lexer.info());
            }

            charType.setCharSetName(this.lexer.stringVal());
            this.lexer.nextToken();
        }

        if (this.lexer.token == Token.BINARY) {
            charType.setHasBinary(true);
            this.lexer.nextToken();
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
            this.lexer.nextToken();
            if (this.lexer.token != Token.LITERAL_ALIAS && this.lexer.token != Token.IDENTIFIER && this.lexer.token != Token.LITERAL_CHARS) {
                throw new ParserException(this.lexer.info());
            }

            charType.setCollate(this.lexer.stringVal());
            this.lexer.nextToken();
        }

        return charType;
    }

    public void accept(Token token) {
        if (this.lexer.token == token) {
            this.lexer.nextToken();
        } else {
            StringBuilder sb = new StringBuilder();
            sb.append("syntax error, expect ");
            sb.append(token.name != null ? token.name : token.toString());
            sb.append(", actual ");
            sb.append(this.lexer.token.name != null ? this.lexer.token.name : this.lexer.token.toString());
            sb.append(" ");
            sb.append(this.lexer.info());
            throw new ParserException(sb.toString());
        }
    }

    public SQLColumnDefinition parseColumn() {
        return this.parseColumn((SQLObject) null);
    }

    public SQLColumnDefinition parseColumn(SQLObject parent) {
        SQLColumnDefinition column = this.createColumnDefinition();
        column.setName(this.name());
        Token token = this.lexer.token;
        if (token != Token.SET && token != Token.DROP && token != Token.PRIMARY && token != Token.RPAREN && token != Token.COMMA) {
            column.setDataType(this.parseDataType());
        }

        return this.parseColumnRest(column);
    }

    public SQLColumnDefinition createColumnDefinition() {
        SQLColumnDefinition column = new SQLColumnDefinition();
        column.setDbType(this.dbType);
        return column;
    }

    public SQLColumnDefinition parseColumnRest(SQLColumnDefinition column) {
        switch (this.lexer.token) {
            case IDENTIFIER:
                long hash = this.lexer.hash_lower();
                if (hash == FnvHash.Constants.AUTO_INCREMENT) {
                    this.lexer.nextToken();
                    column.setAutoIncrement(true);
                    if (this.lexer.token == Token.BY) {
                        this.lexer.nextToken();
                        if (this.lexer.hash_lower() == FnvHash.Constants.GROUP) {
                            this.lexer.nextToken();
                            column.setSequenceType(AutoIncrementType.GROUP);
                            if (!this.lexer.identifierEquals(FnvHash.Constants.UNIT)) {
                                return this.parseColumnRest(column);
                            }

                            this.lexer.nextToken();
                            if (this.lexer.identifierEquals(FnvHash.Constants.COUNT)) {
                                this.lexer.nextToken();
                                SQLExpr unitCount = this.primary();
                                column.setUnitCount(unitCount);
                            }

                            if (this.lexer.token == Token.INDEX) {
                                this.lexer.nextToken();
                                SQLExpr unitIndex = this.primary();
                                column.setUnitIndex(unitIndex);
                            }

                            if (this.lexer.hash_lower() == FnvHash.Constants.STEP) {
                                this.lexer.nextToken();
                                SQLExpr step = this.primary();
                                column.setStep(step);
                            }
                        } else {
                            if (this.lexer.hash_lower() == FnvHash.Constants.TIME) {
                                this.lexer.nextToken();
                                column.setSequenceType(AutoIncrementType.TIME);
                                return this.parseColumnRest(column);
                            }

                            if (this.lexer.hash_lower() == FnvHash.Constants.SIMPLE) {
                                this.lexer.nextToken();
                                if (this.lexer.hash_lower() == FnvHash.Constants.WITH) {
                                    this.lexer.nextToken();
                                    if (this.lexer.hash_lower() == FnvHash.Constants.CACHE) {
                                        column.setSequenceType(AutoIncrementType.SIMPLE_CACHE);
                                        this.lexer.nextToken();
                                        return this.parseColumnRest(column);
                                    }

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

                                column.setSequenceType(AutoIncrementType.SIMPLE);
                                return this.parseColumnRest(column);
                            }
                        }

                        return this.parseColumnRest(column);
                    } else {
                        if (this.lexer.identifierEquals(FnvHash.Constants.UNIT)) {
                            this.lexer.nextToken();
                            if (this.lexer.identifierEquals(FnvHash.Constants.COUNT)) {
                                this.lexer.nextToken();
                                SQLExpr unitCount = this.primary();
                                column.setUnitCount(unitCount);
                            }

                            if (this.lexer.token == Token.INDEX) {
                                this.lexer.nextToken();
                                SQLExpr unitIndex = this.primary();
                                column.setUnitIndex(unitIndex);
                            }

                            if (this.lexer.hash_lower() == FnvHash.Constants.STEP) {
                                this.lexer.nextToken();
                                SQLExpr unitIndex = this.primary();
                                column.setStep(unitIndex);
                            }
                        } else if (this.lexer.token == Token.LPAREN) {
                            this.lexer.nextToken();
                            SQLColumnDefinition.Identity ident = new SQLColumnDefinition.Identity();
                            if (this.lexer.token != Token.LITERAL_INT) {
                                throw new ParserException("TODO : " + this.lexer.info());
                            }

                            ident.setSeed(this.lexer.integerValue().intValue());
                            this.lexer.nextToken();
                            if (this.lexer.token == Token.COMMA) {
                                this.lexer.nextToken();
                                if (this.lexer.token != Token.LITERAL_INT) {
                                    throw new ParserException("TODO : " + this.lexer.info());
                                }

                                ident.setIncrement(this.lexer.integerValue().intValue());
                                this.lexer.nextToken();
                            }

                            column.setIdentity(ident);
                            this.accept(Token.RPAREN);
                        }

                        return this.parseColumnRest(column);
                    }
                }
            default:
                return column;
            case NULL:
                this.lexer.nextToken();
                column.getConstraints().add(new SQLNullConstraint());
                return this.parseColumnRest(column);
            case DEFAULT:
                this.lexer.nextToken();
                SQLExpr defaultExpr = null;
                if (this.lexer.token == Token.LITERAL_CHARS && this.dbType == DbType.mysql) {
                    defaultExpr = new SQLCharExpr(this.lexer.stringVal());
                    this.lexer.nextToken();
                } else {
                    defaultExpr = this.bitOr();
                }

                column.setDefaultExpr(defaultExpr);
                return this.parseColumnRest(column);
            case KEY:
                this.lexer.nextToken();
                column.addConstraint(new SQLColumnPrimaryKey());
                return this.parseColumnRest(column);
            case COMMENT:
                this.lexer.nextToken();
                if (this.lexer.token == Token.LITERAL_ALIAS) {
                    String alias = this.lexer.stringVal();
                    if (alias.length() > 2 && alias.charAt(0) == '"' && alias.charAt(alias.length() - 1) == '"') {
                        alias = alias.substring(1, alias.length() - 1);
                    }

                    column.setComment(alias);
                    this.lexer.nextToken();
                } else if (this.lexer.token == Token.LITERAL_CHARS) {
                    String stringVal = this.lexer.stringVal();
                    this.lexer.nextToken();
                    if (this.dbType == DbType.odps) {
                        label158:
                        while (true) {
                            while (this.lexer.token != Token.LITERAL_ALIAS) {
                                if (this.lexer.token != Token.LITERAL_CHARS) {
                                    break label158;
                                }

                                stringVal = stringVal + this.lexer.stringVal();
                                this.lexer.nextToken();
                            }

                            String tmp = this.lexer.stringVal();
                            if (tmp.length() > 2 && tmp.charAt(0) == '"' && tmp.charAt(tmp.length() - 1) == '"') {
                                tmp = tmp.substring(1, tmp.length() - 1);
                            }

                            stringVal = stringVal + tmp;
                            this.lexer.nextToken();
                        }
                    }

                    column.setComment(stringVal);
                } else {
                    column.setComment(this.primary());
                }

                return this.parseColumnRest(column);
            case CONSTRAINT:
                this.lexer.nextToken();
                SQLName name = this.name();
                if (this.lexer.token == Token.PRIMARY) {
                    this.lexer.nextToken();
                    this.accept(Token.KEY);
                    SQLColumnPrimaryKey pk = new SQLColumnPrimaryKey();
                    pk.setName(name);
                    column.addConstraint(pk);
                    return this.parseColumnRest(column);
                } else if (this.lexer.token == Token.UNIQUE) {
                    this.lexer.nextToken();
                    SQLColumnUniqueKey uk = new SQLColumnUniqueKey();
                    uk.setName(name);
                    column.addConstraint(uk);
                    return this.parseColumnRest(column);
                } else if (this.lexer.token == Token.REFERENCES) {
                    SQLColumnReference ref = this.parseReference();
                    ref.setName(name);
                    column.addConstraint(ref);
                    return this.parseColumnRest(column);
                } else if (this.lexer.token == Token.NOT) {
                    this.lexer.nextToken();
                    this.accept(Token.NULL);
                    SQLNotNullConstraint notNull = new SQLNotNullConstraint();
                    notNull.setName(name);
                    column.addConstraint(notNull);
                    return this.parseColumnRest(column);
                } else if (this.lexer.token == Token.CHECK) {
                    SQLColumnCheck check = this.parseColumnCheck();
                    check.setName(name);
                    check.setParent(column);
                    column.addConstraint(check);
                    return this.parseColumnRest(column);
                } else {
                    if (this.lexer.token == Token.DEFAULT) {
                        this.lexer.nextToken();
                        SQLExpr expr = this.expr();
                        column.setDefaultExpr(expr);
                        return this.parseColumnRest(column);
                    }

                    throw new ParserException("TODO : " + this.lexer.info());
                }
            case PRIMARY:
                this.lexer.nextToken();
                this.accept(Token.KEY);
                column.addConstraint(new SQLColumnPrimaryKey());
                return this.parseColumnRest(column);
            case NOT:
                this.lexer.nextToken();
                this.accept(Token.NULL);
                SQLNotNullConstraint notNull = new SQLNotNullConstraint();
                if (this.lexer.token == Token.HINT) {
                    List<SQLCommentHint> hints = this.parseHints();
                    notNull.setHints(hints);
                }

                column.addConstraint(notNull);
                return this.parseColumnRest(column);
            case CHECK:
                SQLColumnCheck check = this.parseColumnCheck();
                column.addConstraint(check);
                return this.parseColumnRest(column);
            case UNIQUE:
                this.lexer.nextToken();
                if (this.lexer.token == Token.KEY) {
                    this.lexer.nextToken();
                }

                column.addConstraint(new SQLColumnUniqueKey());
                return this.parseColumnRest(column);
            case REFERENCES:
                SQLColumnReference ref = this.parseReference();
                column.addConstraint(ref);
                return this.parseColumnRest(column);
        }
    }

    public SQLColumnReference parseReference() {
        SQLColumnReference fk = new SQLColumnReference();
        this.lexer.nextToken();
        fk.setTable(this.name());
        this.accept(Token.LPAREN);
        this.names(fk.getColumns(), fk);
        this.accept(Token.RPAREN);
        if (this.lexer.identifierEquals(FnvHash.Constants.MATCH)) {
            this.lexer.nextToken();
            if (!this.lexer.identifierEquals("FULL") && this.lexer.token() != Token.FULL) {
                if (this.lexer.identifierEquals(FnvHash.Constants.PARTIAL)) {
                    fk.setReferenceMatch(SQLForeignKeyImpl.Match.PARTIAL);
                    this.lexer.nextToken();
                } else {
                    if (!this.lexer.identifierEquals(FnvHash.Constants.SIMPLE)) {
                        throw new ParserException("TODO : " + this.lexer.info());
                    }

                    fk.setReferenceMatch(SQLForeignKeyImpl.Match.SIMPLE);
                    this.lexer.nextToken();
                }
            } else {
                fk.setReferenceMatch(SQLForeignKeyImpl.Match.FULL);
                this.lexer.nextToken();
            }
        }

        while (this.lexer.token() == Token.ON) {
            this.lexer.nextToken();
            if (this.lexer.token() == Token.DELETE) {
                this.lexer.nextToken();
                SQLForeignKeyImpl.Option option = this.parseReferenceOption();
                fk.setOnDelete(option);
            } else {
                if (this.lexer.token() != Token.UPDATE) {
                    throw new ParserException("syntax error, expect DELETE or UPDATE, actual " + this.lexer.token() + " " + this.lexer.info());
                }

                this.lexer.nextToken();
                SQLForeignKeyImpl.Option option = this.parseReferenceOption();
                fk.setOnUpdate(option);
            }
        }

        return fk;
    }

    protected SQLForeignKeyImpl.Option parseReferenceOption() {
        SQLForeignKeyImpl.Option option;
        if (this.lexer.token() != Token.RESTRICT && !this.lexer.identifierEquals(FnvHash.Constants.RESTRICT)) {
            if (this.lexer.identifierEquals(FnvHash.Constants.CASCADE)) {
                option = SQLForeignKeyImpl.Option.CASCADE;
                this.lexer.nextToken();
            } else if (this.lexer.token() == Token.SET) {
                this.lexer.nextToken();
                if (this.lexer.token() == Token.NULL) {
                    this.accept(Token.NULL);
                    option = SQLForeignKeyImpl.Option.SET_NULL;
                } else {
                    if (this.lexer.token != Token.DEFAULT) {
                        throw new ParserException("syntax error," + this.lexer.info());
                    }

                    this.accept(Token.DEFAULT);
                    option = SQLForeignKeyImpl.Option.SET_DEFAULT;
                }
            } else {
                if (!this.lexer.identifierEquals(FnvHash.Constants.NO)) {
                    throw new ParserException("syntax error, expect ACTION, actual " + this.lexer.token() + " " + this.lexer.info());
                }

                this.lexer.nextToken();
                if (!this.lexer.identifierEquals(FnvHash.Constants.ACTION)) {
                    throw new ParserException("syntax error, expect ACTION, actual " + this.lexer.token() + " " + this.lexer.info());
                }

                option = SQLForeignKeyImpl.Option.NO_ACTION;
                this.lexer.nextToken();
            }
        } else {
            option = SQLForeignKeyImpl.Option.RESTRICT;
            this.lexer.nextToken();
        }

        return option;
    }

    protected SQLColumnCheck parseColumnCheck() {
        this.lexer.nextToken();
        SQLExpr expr = this.expr();
        SQLColumnCheck check = new SQLColumnCheck(expr);
        if (this.lexer.token == Token.DISABLE) {
            this.lexer.nextToken();
            check.setEnable(false);
        } else if (this.lexer.token == Token.ENABLE) {
            this.lexer.nextToken();
            check.setEnable(true);
        } else if (this.lexer.identifierEquals(FnvHash.Constants.VALIDATE)) {
            this.lexer.nextToken();
            check.setValidate(Boolean.TRUE);
        } else if (this.lexer.identifierEquals(FnvHash.Constants.NOVALIDATE)) {
            this.lexer.nextToken();
            check.setValidate(Boolean.FALSE);
        } else if (this.lexer.identifierEquals(FnvHash.Constants.RELY)) {
            this.lexer.nextToken();
            check.setRely(Boolean.TRUE);
        } else if (this.lexer.identifierEquals(FnvHash.Constants.NORELY)) {
            this.lexer.nextToken();
            check.setRely(Boolean.FALSE);
        }

        return check;
    }

    public SQLPrimaryKey parsePrimaryKey() {
        this.accept(Token.PRIMARY);
        this.accept(Token.KEY);
        SQLPrimaryKeyImpl pk = new SQLPrimaryKeyImpl();
        if (this.lexer.identifierEquals(FnvHash.Constants.CLUSTERED)) {
            this.lexer.nextToken();
            pk.setClustered(true);
        }

        this.accept(Token.LPAREN);
        this.orderBy(pk.getColumns(), pk);
        this.accept(Token.RPAREN);
        if (this.lexer.token == Token.DISABLE) {
            this.lexer.nextToken();
            this.acceptIdentifier("NOVALIDATE");
            pk.setDisableNovalidate(true);
        }

        return pk;
    }

    public SQLUnique parseUnique() {
        this.accept(Token.UNIQUE);
        SQLUnique unique = new SQLUnique();
        this.accept(Token.LPAREN);
        this.orderBy(unique.getColumns(), unique);
        this.accept(Token.RPAREN);
        if (this.lexer.token == Token.DISABLE) {
            this.lexer.nextToken();
            unique.setEnable(false);
        } else if (this.lexer.token == Token.ENABLE) {
            this.lexer.nextToken();
            unique.setEnable(true);
        } else if (this.lexer.identifierEquals(FnvHash.Constants.VALIDATE)) {
            this.lexer.nextToken();
            unique.setValidate(Boolean.TRUE);
        } else if (this.lexer.identifierEquals(FnvHash.Constants.NOVALIDATE)) {
            this.lexer.nextToken();
            unique.setValidate(Boolean.FALSE);
        } else if (this.lexer.identifierEquals(FnvHash.Constants.RELY)) {
            this.lexer.nextToken();
            unique.setRely(Boolean.TRUE);
        } else if (this.lexer.identifierEquals(FnvHash.Constants.NORELY)) {
            this.lexer.nextToken();
            unique.setRely(Boolean.FALSE);
        }

        return unique;
    }

    public void parseAssignItem(List<SQLAssignItem> outList, SQLObject parent) {
        this.accept(Token.LPAREN);

        while (true) {
            SQLAssignItem item = this.parseAssignItem();
            item.setParent(parent);
            outList.add(item);
            if (this.lexer.token() != Token.COMMA) {
                this.accept(Token.RPAREN);
                return;
            }

            this.lexer.nextToken();
        }
    }

    public SQLAssignItem parseAssignItem() {
        return this.parseAssignItem(true);
    }

    public SQLAssignItem parseAssignItem(boolean variant) {
        SQLAssignItem item = new SQLAssignItem();
        SQLExpr var = this.primary();
        if (variant && var instanceof SQLIdentifierExpr) {
            SQLIdentifierExpr ident = (SQLIdentifierExpr) var;
            if (this.lexer.identifierEquals(FnvHash.Constants.CLUSTER) && ident.nameHashCode64() == FnvHash.Constants.RUNNING && this.dbType == DbType.odps) {
                String str = ident.getName() + " " + this.lexer.stringVal();
                this.lexer.nextToken();
                ident.setName(str);
            }

            var = new SQLVariantRefExpr(ident.getName());
        }

        item.setTarget(var);
        if (this.lexer.token == Token.COLONEQ) {
            this.lexer.nextToken();
        } else {
            if (this.lexer.token == Token.TRUE || this.lexer.identifierEquals(FnvHash.Constants.TRUE)) {
                this.lexer.nextToken();
                item.setValue(new SQLBooleanExpr(true));
                return item;
            }

            if (this.lexer.token == Token.ON) {
                this.lexer.nextToken();
                item.setValue(new SQLIdentifierExpr("ON"));
                return item;
            }

            if (this.lexer.token == Token.RPAREN || this.lexer.token == Token.COMMA) {
                return item;
            }

            if (this.lexer.token == Token.EQ) {
                this.lexer.nextToken();
                if (this.lexer.token == Token.SEMI && this.dbType == DbType.odps) {
                    return item;
                }
            } else if (this.dbType != DbType.db2 && this.lexer.token != Token.QUES && this.lexer.token != Token.LITERAL_CHARS && this.lexer.token != Token.LITERAL_ALIAS && !this.lexer.identifierEquals("utf8mb4")) {
                this.accept(Token.EQ);
            }
        }

        if (this.lexer.token == Token.ON) {
            item.setValue(new SQLIdentifierExpr(this.lexer.stringVal()));
            this.lexer.nextToken();
        } else if (this.lexer.token == Token.ALL) {
            item.setValue(new SQLIdentifierExpr(this.lexer.stringVal()));
            this.lexer.nextToken();
        } else {
            SQLExpr expr = this.expr();
            if (this.lexer.token == Token.COLON && (this.dbType == DbType.hive || this.dbType == DbType.odps)) {
                Lexer.SavePoint mark = this.lexer.mark();
                this.lexer.nextToken();
                String str = expr.toString() + ':';
                str = str + this.lexer.numberString();
                this.lexer.nextToken();
                expr = new SQLIdentifierExpr(str);
            }

            if (this.lexer.token == Token.COMMA && DbType.postgresql == this.dbType) {
                SQLListExpr listExpr = new SQLListExpr();
                listExpr.addItem(expr);
                expr.setParent(listExpr);

                do {
                    this.lexer.nextToken();
                    SQLExpr listItem = this.expr();
                    listItem.setParent(listExpr);
                    listExpr.addItem(listItem);
                } while (this.lexer.token == Token.COMMA);

                item.setValue(listExpr);
            } else {
                item.setValue(expr);
            }
        }

        return item;
    }

    public List<SQLCommentHint> parseHints() {
        List<SQLCommentHint> hints = new ArrayList();
        this.parseHints(hints);
        return hints;
    }

    public void parseHints(List hints) {
        while (this.lexer.token == Token.HINT) {
            String text = this.lexer.stringVal();
            SQLCommentHint hint;
            if (!this.lexer.isEnabled(SQLParserFeature.TDDLHint) || !text.startsWith("+ TDDL") && !text.startsWith("+TDDL") && !text.startsWith("!TDDL") && !text.startsWith("TDDL")) {
                hint = new SQLCommentHint(text);
            } else {
                hint = new TDDLHint(text);
            }

            if (this.lexer.commentCount > 0) {
                hint.addBeforeComment(this.lexer.comments);
            }

            hints.add(hint);
            this.lexer.nextToken();
        }

    }

    public SQLCommentHint parseHint() {
        String text = this.lexer.stringVal();
        SQLCommentHint hint;
        if (!this.lexer.isEnabled(SQLParserFeature.TDDLHint) || !text.startsWith("+ TDDL") && !text.startsWith("+TDDL") && !text.startsWith("!TDDL") && !text.startsWith("TDDL")) {
            hint = new SQLCommentHint(text);
        } else {
            hint = new TDDLHint(text);
        }

        if (this.lexer.commentCount > 0) {
            hint.addBeforeComment(this.lexer.comments);
        }

        this.lexer.nextToken();
        return hint;
    }

    public void parseIndex(SQLIndexDefinition indexDefinition) {
        if (this.lexer.token() == Token.CONSTRAINT) {
            indexDefinition.setHasConstraint(true);
            this.lexer.nextToken();
            if (this.lexer.token() == Token.IDENTIFIER && !this.lexer.identifierEquals(FnvHash.Constants.GLOBAL) && !this.lexer.identifierEquals(FnvHash.Constants.LOCAL) && !this.lexer.identifierEquals(FnvHash.Constants.SPATIAL)) {
                indexDefinition.setSymbol(this.name());
            }
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.GLOBAL)) {
            indexDefinition.setGlobal(true);
            this.lexer.nextToken();
        } else if (this.lexer.identifierEquals(FnvHash.Constants.LOCAL)) {
            indexDefinition.setLocal(true);
            this.lexer.nextToken();
        }

        if (this.lexer.token() == Token.FULLTEXT || this.lexer.token() == Token.UNIQUE || this.lexer.token() == Token.PRIMARY || this.lexer.identifierEquals(FnvHash.Constants.SPATIAL) || this.lexer.identifierEquals(FnvHash.Constants.CLUSTERED) || this.lexer.identifierEquals(FnvHash.Constants.CLUSTERING) || this.lexer.identifierEquals(FnvHash.Constants.ANN)) {
            indexDefinition.setType(this.lexer.stringVal());
            this.lexer.nextToken();
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.GLOBAL)) {
            indexDefinition.setGlobal(true);
            this.lexer.nextToken();
        } else if (this.lexer.identifierEquals(FnvHash.Constants.LOCAL)) {
            indexDefinition.setLocal(true);
            this.lexer.nextToken();
        }

        if (this.lexer.token() == Token.INDEX) {
            indexDefinition.setIndex(true);
            this.lexer.nextToken();
        } else if (this.lexer.token() == Token.KEY) {
            indexDefinition.setKey(true);
            this.lexer.nextToken();
        }

        while (this.lexer.token() != Token.LPAREN && this.lexer.token() != Token.ON) {
            if (DbType.mysql == this.dbType && this.lexer.identifierEquals(FnvHash.Constants.USING)) {
                this.lexer.nextToken();
                indexDefinition.getOptions().setIndexType(this.lexer.stringVal());
                this.lexer.nextToken();
            } else if ((DbType.mysql == this.dbType || DbType.ads == this.dbType) && this.lexer.identifierEquals("HASHMAP")) {
                this.lexer.nextToken();
                indexDefinition.setHashMapType(true);
                indexDefinition.getParent().putAttribute("ads.index", Boolean.TRUE);
            } else if ((DbType.mysql == this.dbType || DbType.ads == this.dbType) && this.lexer.identifierEquals(FnvHash.Constants.HASH)) {
                this.lexer.nextToken();
                indexDefinition.setHashType(true);
                indexDefinition.getParent().putAttribute("ads.index", Boolean.TRUE);
            } else {
                indexDefinition.setName(this.name());
            }
        }

        if (this.lexer.token() == Token.ON) {
            this.lexer.nextToken();
            indexDefinition.setTable(new SQLExprTableSource(this.name()));
        }

        this.parseIndexRest(indexDefinition, indexDefinition.getParent());

        while (true) {
            while (this.lexer.token() == Token.COMMENT) {
                this.lexer.nextToken();
                indexDefinition.getOptions().setComment(this.primary());
            }

            if (DbType.mysql != this.dbType) {
                return;
            }

            switch (this.lexer.token()) {
                case IDENTIFIER:
                    if (!this.lexer.identifierEquals(FnvHash.Constants.KEY_BLOCK_SIZE) && !this.lexer.identifierEquals(FnvHash.Constants.BLOCK_SIZE)) {
                        if (this.lexer.identifierEquals(FnvHash.Constants.USING)) {
                            this.lexer.nextToken();
                            indexDefinition.getOptions().setIndexType(this.lexer.stringVal());
                            this.lexer.nextToken();
                            break;
                        }

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

                            indexDefinition.getOptions().setAlgorithm(this.lexer.stringVal());
                            this.lexer.nextToken();
                            break;
                        }

                        if (this.lexer.identifierEquals(FnvHash.Constants.DISTANCEMEASURE)) {
                            SQLExpr key = new SQLIdentifierExpr(this.lexer.stringVal());
                            this.lexer.nextToken();
                            if (this.lexer.token() == Token.EQ) {
                                this.lexer.nextToken();
                            }

                            SQLAssignItem item = new SQLAssignItem(key, this.primary());
                            if (indexDefinition.getParent() != null) {
                                item.setParent(indexDefinition.getParent());
                            } else {
                                item.setParent(indexDefinition);
                            }

                            indexDefinition.getOptions().getOtherOptions().add(item);
                            indexDefinition.getCompatibleOptions().add(item);
                            break;
                        }

                        if (this.lexer.identifierEquals(FnvHash.Constants.DBPARTITION)) {
                            this.lexer.nextToken();
                            this.accept(Token.BY);
                            indexDefinition.setDbPartitionBy(this.primary());
                            break;
                        }

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

                            indexDefinition.setTbPartitionBy(expr);
                            break;
                        }

                        if (this.lexer.identifierEquals(FnvHash.Constants.TBPARTITIONS)) {
                            this.lexer.nextToken();
                            indexDefinition.setTbPartitions(this.primary());
                            break;
                        }

                        if (this.lexer.identifierEquals(FnvHash.Constants.VISIBLE)) {
                            this.lexer.nextToken();
                            indexDefinition.getOptions().setVisible(true);
                            break;
                        }

                        if (!this.lexer.identifierEquals(FnvHash.Constants.INVISIBLE)) {
                            return;
                        }

                        this.lexer.nextToken();
                        indexDefinition.getOptions().setInvisible(true);
                        break;
                    }

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

                    indexDefinition.getOptions().setKeyBlockSize(this.expr());
                    break;
                case WITH:
                    Lexer.SavePoint mark = this.lexer.mark();
                    this.lexer.nextToken();
                    if (this.lexer.identifierEquals("PARSER")) {
                        this.lexer.nextToken();
                        indexDefinition.getOptions().setParserName(this.lexer.stringVal());
                        this.lexer.nextToken();
                    } else {
                        this.lexer.reset(mark);

                        while (this.lexer.token == Token.WITH) {
                            this.lexer.nextToken();
                            if (this.lexer.token() == Token.INDEX) {
                                this.lexer.nextToken();
                                this.acceptIdentifier("ANALYZER");
                                indexDefinition.setIndexAnalyzerName(this.name());
                            } else if (this.lexer.identifierEquals(FnvHash.Constants.QUERY)) {
                                this.lexer.nextToken();
                                this.acceptIdentifier("ANALYZER");
                                indexDefinition.setQueryAnalyzerName(this.name());
                            } else {
                                if (this.lexer.identifierEquals(FnvHash.Constants.ANALYZER)) {
                                    this.lexer.nextToken();
                                    SQLName name = this.name();
                                    indexDefinition.setAnalyzerName(name);
                                    break;
                                }

                                if (!this.lexer.identifierEquals("DICT")) {
                                    break;
                                }

                                this.lexer.nextToken();
                                indexDefinition.setWithDicName(this.name());
                            }
                        }
                    }
                    break;
                case LOCK:
                    this.lexer.nextToken();
                    if (this.lexer.token() == Token.EQ) {
                        this.lexer.nextToken();
                    }

                    indexDefinition.getOptions().setLock(this.lexer.stringVal());
                    this.lexer.nextToken();
                    break;
                default:
                    return;
            }
        }
    }

    public SQLConstraint parseConstaint() {
        SQLName name = null;
        if (this.lexer.token == Token.CONSTRAINT) {
            this.lexer.nextToken();
            name = this.name();
        }

        SQLConstraint constraint;
        switch (this.lexer.token) {
            case DEFAULT:
                constraint = this.parseDefault();
                break;
            case KEY:
                constraint = this.parseUnique();
                break;
            case PRIMARY:
                constraint = this.parsePrimaryKey();
                break;
            case CHECK:
                constraint = this.parseCheck();
                break;
            case UNIQUE:
                constraint = this.parseUnique();
                break;
            case FOREIGN:
                constraint = this.parseForeignKey();
                break;
            default:
                throw new ParserException("TODO : " + this.lexer.info());
        }

        constraint.setName(name);
        return constraint;
    }

    public SQLCheck parseCheck() {
        this.accept(Token.CHECK);
        SQLCheck check = this.createCheck();
        this.accept(Token.LPAREN);
        check.setExpr(this.expr());
        this.accept(Token.RPAREN);
        return check;
    }

    public SQLDefault parseDefault() {
        this.accept(Token.DEFAULT);
        SQLDefault sqlDefault = new SQLDefault();
        if (this.lexer.token != Token.LPAREN) {
            sqlDefault.setExpr(this.expr());
        } else {
            while (this.lexer.token == Token.LPAREN) {
                this.accept(Token.LPAREN);
            }

            sqlDefault.setExpr(this.expr());

            while (this.lexer.token == Token.RPAREN) {
                this.accept(Token.RPAREN);
            }
        }

        this.accept(Token.FOR);
        sqlDefault.setColumn(this.expr());
        if (this.lexer.token == Token.WITH) {
            this.lexer.nextToken();
            this.accept(Token.VALUES);
            sqlDefault.setWithValues(true);
        }

        return sqlDefault;
    }

    protected SQLCheck createCheck() {
        return new SQLCheck();
    }

    public SQLForeignKeyConstraint parseForeignKey() {
        this.accept(Token.FOREIGN);
        this.accept(Token.KEY);
        SQLForeignKeyImpl fk = this.createForeignKey();
        this.accept(Token.LPAREN);
        this.names(fk.getReferencingColumns(), fk);
        this.accept(Token.RPAREN);
        this.accept(Token.REFERENCES);
        fk.setReferencedTableName(this.name());
        if (this.lexer.token == Token.LPAREN) {
            this.lexer.nextToken();
            this.names(fk.getReferencedColumns(), fk);
            this.accept(Token.RPAREN);
        }

        if (this.lexer.token == Token.ON) {
            this.lexer.nextToken();
            this.accept(Token.DELETE);
            if (!this.lexer.identifierEquals(FnvHash.Constants.CASCADE) && this.lexer.token != Token.CASCADE) {
                this.accept(Token.SET);
                this.accept(Token.NULL);
                fk.setOnDeleteSetNull(true);
            } else {
                this.lexer.nextToken();
                fk.setOnDeleteCascade(true);
            }
        }

        if (this.lexer.token == Token.DISABLE) {
            Lexer.SavePoint mark = this.lexer.mark();
            this.lexer.nextToken();
            if (this.lexer.identifierEquals(FnvHash.Constants.NOVALIDATE)) {
                this.lexer.nextToken();
                fk.setDisableNovalidate(true);
            } else {
                this.lexer.reset(mark);
            }
        }

        return fk;
    }

    protected SQLForeignKeyImpl createForeignKey() {
        return new SQLForeignKeyImpl();
    }

    public SQLSelectItem parseSelectItem() {
        boolean connectByRoot = false;
        Token token = this.lexer.token;
        int startPos = this.lexer.startPos;
        SQLExpr expr;
        if (token != Token.IDENTIFIER || this.lexer.hash_lower() == -5808529385363204345L && this.lexer.charAt(this.lexer.pos) == '\'' && this.dbType == DbType.mysql) {
            if (token == Token.STAR) {
                expr = new SQLAllColumnExpr();
                this.lexer.nextToken();
                return new SQLSelectItem(expr, (String) null, connectByRoot);
            }

            if (token != Token.DO && token != Token.JOIN && token != Token.TABLESPACE) {
                if (this.lexer.token == Token.DISTINCT && this.dbType == DbType.elastic_search) {
                    this.lexer.nextToken();
                }

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

                expr = this.expr();
            } else {
                expr = this.name();
                expr = this.exprRest(expr);
            }
        } else {
            String ident = this.lexer.stringVal();
            long hash_lower = this.lexer.hash_lower();
            this.lexer.nextTokenComma();
            if (hash_lower == FnvHash.Constants.CONNECT_BY_ROOT) {
                connectByRoot = this.lexer.token != Token.LPAREN;
                if (connectByRoot) {
                    expr = new SQLIdentifierExpr(this.lexer.stringVal());
                    this.lexer.nextToken();
                } else {
                    expr = new SQLIdentifierExpr(ident);
                }
            } else if (this.lexer.identifierEquals(FnvHash.Constants.COLLATE) && this.dbType == DbType.mysql && this.lexer.stringVal().charAt(0) != '`') {
                this.lexer.nextToken();
                String collate = this.lexer.stringVal();
                this.lexer.nextToken();
                SQLBinaryOpExpr binaryExpr = new SQLBinaryOpExpr(new SQLIdentifierExpr(ident), SQLBinaryOperator.COLLATE, new SQLIdentifierExpr(collate), DbType.mysql);
                expr = binaryExpr;
            } else if (this.lexer.identifierEquals(FnvHash.Constants.REGEXP) && this.lexer.stringVal().charAt(0) != '`' && this.dbType == DbType.mysql) {
                this.lexer.nextToken();
                SQLExpr rightExp = this.bitOr();
                SQLBinaryOpExpr binaryExpr = new SQLBinaryOpExpr(new SQLIdentifierExpr(ident), SQLBinaryOperator.RegExp, rightExp, DbType.mysql);
                expr = this.relationalRest(binaryExpr);
            } else if (FnvHash.Constants.DATE == hash_lower && this.lexer.stringVal().charAt(0) != '`' && this.lexer.token == Token.LITERAL_CHARS && SQLDateExpr.isSupport(this.dbType)) {
                String literal = this.lexer.stringVal();
                this.lexer.nextToken();
                SQLDateExpr dateExpr = new SQLDateExpr();
                dateExpr.setLiteral(literal);
                expr = dateExpr;
            } else if (FnvHash.Constants.TIMESTAMP == hash_lower && this.lexer.stringVal().charAt(0) != '`' && this.lexer.token == Token.LITERAL_CHARS && this.dbType != DbType.oracle) {
                String literal = this.lexer.stringVal();
                this.lexer.nextToken();
                SQLTimestampExpr ts = new SQLTimestampExpr(literal);
                expr = ts;
                if (this.lexer.identifierEquals(FnvHash.Constants.AT)) {
                    Lexer.SavePoint mark = this.lexer.mark();
                    this.lexer.nextToken();
                    String timeZone = null;
                    if (this.lexer.identifierEquals(FnvHash.Constants.TIME)) {
                        this.lexer.nextToken();
                        if (this.lexer.identifierEquals(FnvHash.Constants.ZONE)) {
                            this.lexer.nextToken();
                            timeZone = this.lexer.stringVal();
                            this.lexer.nextToken();
                        }
                    }

                    if (timeZone == null) {
                        this.lexer.reset(mark);
                    } else {
                        ts.setTimeZone(timeZone);
                    }
                }
            } else if (FnvHash.Constants.DATETIME == hash_lower && this.lexer.stringVal().charAt(0) != '`' && this.lexer.token == Token.LITERAL_CHARS && this.dbType != DbType.oracle) {
                String literal = this.lexer.stringVal();
                this.lexer.nextToken();
                SQLDateTimeExpr ts = new SQLDateTimeExpr(literal);
                expr = ts;
            } else if (FnvHash.Constants.BOOLEAN == hash_lower && this.lexer.stringVal().charAt(0) != '`' && this.lexer.token == Token.LITERAL_CHARS && this.dbType == DbType.mysql) {
                String literal = this.lexer.stringVal();
                this.lexer.nextToken();
                SQLBooleanExpr ts = new SQLBooleanExpr(Boolean.valueOf(literal));
                expr = ts;
            } else if ((FnvHash.Constants.CHAR == hash_lower || FnvHash.Constants.VARCHAR == hash_lower) && this.lexer.token == Token.LITERAL_CHARS && this.dbType == DbType.mysql) {
                String literal = this.lexer.stringVal();
                this.lexer.nextToken();
                SQLCharExpr charExpr = new SQLCharExpr(literal);
                expr = charExpr;
            } else if (FnvHash.Constants.TIME == hash_lower && this.lexer.token == Token.LITERAL_CHARS) {
                String literal = this.lexer.stringVal();
                this.lexer.nextToken();
                expr = new SQLTimeExpr(literal);
            } else if (hash_lower == FnvHash.Constants.DECIMAL && this.lexer.token == Token.LITERAL_CHARS) {
                String decimal = this.lexer.stringVal();
                expr = new SQLDecimalExpr(decimal);
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.REAL && this.lexer.token == Token.LITERAL_CHARS) {
                String decimal = this.lexer.stringVal();
                expr = new SQLRealExpr(decimal);
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.DOUBLE && this.lexer.token == Token.LITERAL_CHARS) {
                String decimal = this.lexer.stringVal();
                expr = new SQLDoubleExpr(decimal);
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.FLOAT && this.lexer.token == Token.LITERAL_CHARS) {
                String decimal = this.lexer.stringVal();
                expr = new SQLFloatExpr(decimal);
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.BIGINT && this.lexer.token == Token.LITERAL_CHARS) {
                String strVal = this.lexer.stringVal();
                if (strVal.startsWith("--")) {
                    strVal = strVal.substring(2);
                }

                expr = new SQLBigIntExpr(strVal);
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.INTEGER && this.lexer.token == Token.LITERAL_CHARS) {
                String strVal = this.lexer.stringVal();
                if (strVal.startsWith("--")) {
                    strVal = strVal.substring(2);
                }

                SQLIntegerExpr integerExpr = SQLIntegerExpr.ofIntOrLong(Long.parseLong(strVal));
                integerExpr.setType("INTEGER");
                expr = integerExpr;
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.SMALLINT && this.lexer.token == Token.LITERAL_CHARS) {
                String decimal = this.lexer.stringVal();
                expr = new SQLSmallIntExpr(decimal);
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.TINYINT && this.lexer.token == Token.LITERAL_CHARS) {
                String decimal = this.lexer.stringVal();
                expr = new SQLTinyIntExpr(decimal);
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.JSON && this.lexer.token == Token.LITERAL_CHARS) {
                String decimal = this.lexer.stringVal();
                expr = new SQLJSONExpr(decimal);
                this.lexer.nextToken();
            } else if (hash_lower == FnvHash.Constants.TRY_CAST) {
                this.accept(Token.LPAREN);
                SQLCastExpr cast = new SQLCastExpr();
                cast.setTry(true);
                cast.setExpr(this.expr());
                this.accept(Token.AS);
                cast.setDataType(this.parseDataType(false));
                this.accept(Token.RPAREN);
                expr = cast;
            } else if (FnvHash.Constants.CURRENT_DATE != hash_lower || ident.charAt(0) == '`' || this.lexer.token == Token.LPAREN || this.dbType != DbType.mysql && this.dbType != DbType.hive) {
                if (FnvHash.Constants.CURRENT_TIMESTAMP != hash_lower || ident.charAt(0) == '`' || this.lexer.token == Token.LPAREN || this.dbType != DbType.mysql && this.dbType != DbType.hive) {
                    if (FnvHash.Constants.CURRENT_TIME != hash_lower || ident.charAt(0) == '`' || this.lexer.token == Token.LPAREN || this.dbType != DbType.mysql && this.dbType != DbType.hive) {
                        if (FnvHash.Constants.CURDATE != hash_lower || ident.charAt(0) == '`' || this.lexer.token == Token.LPAREN || this.dbType != DbType.mysql && this.dbType != DbType.hive) {
                            if (FnvHash.Constants.LOCALTIME != hash_lower || ident.charAt(0) == '`' || this.lexer.token == Token.LPAREN || this.dbType != DbType.mysql && this.dbType != DbType.hive) {
                                if (FnvHash.Constants.LOCALTIMESTAMP != hash_lower || ident.charAt(0) == '`' || this.lexer.token == Token.LPAREN || this.dbType != DbType.mysql && this.dbType != DbType.hive) {
                                    if (FnvHash.Constants.CURRENT_USER == hash_lower && ident.charAt(0) != '`' && this.lexer.token != Token.LPAREN && this.isEnabled(SQLParserFeature.EnableCurrentUserExpr)) {
                                        expr = new SQLCurrentUserExpr();
                                    } else if (FnvHash.Constants._LATIN1 == hash_lower && ident.charAt(0) != '`' && this.dbType == DbType.mysql) {
                                        String hexString;
                                        if (this.lexer.token == Token.LITERAL_HEX) {
                                            hexString = this.lexer.hexString();
                                            this.lexer.nextToken();
                                        } else if (this.lexer.token == Token.LITERAL_CHARS) {
                                            hexString = null;
                                        } else {
                                            this.acceptIdentifier("X");
                                            hexString = this.lexer.stringVal();
                                            this.accept(Token.LITERAL_CHARS);
                                        }

                                        if (hexString == null) {
                                            String str = this.lexer.stringVal();
                                            this.lexer.nextToken();
                                            String collate = null;
                                            if (this.lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
                                                this.lexer.nextToken();
                                                collate = this.lexer.stringVal();
                                                if (this.lexer.token() == Token.LITERAL_CHARS) {
                                                    this.lexer.nextToken();
                                                } else {
                                                    this.accept(Token.IDENTIFIER);
                                                }
                                            }

                                            expr = new MySqlCharExpr(str, "_latin1", collate);
                                        } else {
                                            expr = new MySqlCharExpr(hexString, "_latin1");
                                        }
                                    } else if ((FnvHash.Constants._UTF8 == hash_lower || FnvHash.Constants._UTF8MB4 == hash_lower) && ident.charAt(0) != '`' && this.dbType == DbType.mysql) {
                                        String hexString;
                                        if (this.lexer.token == Token.LITERAL_HEX) {
                                            hexString = this.lexer.hexString();
                                            this.lexer.nextToken();
                                        } else if (this.lexer.token == Token.LITERAL_CHARS) {
                                            hexString = null;
                                        } else {
                                            this.acceptIdentifier("X");
                                            hexString = this.lexer.stringVal();
                                            this.accept(Token.LITERAL_CHARS);
                                        }

                                        if (hexString == null) {
                                            String str = this.lexer.stringVal();
                                            this.lexer.nextToken();
                                            String collate = null;
                                            if (this.lexer.identifierEquals(FnvHash.Constants.COLLATE)) {
                                                this.lexer.nextToken();
                                                collate = this.lexer.stringVal();
                                                if (this.lexer.token() == Token.LITERAL_CHARS) {
                                                    this.lexer.nextToken();
                                                } else {
                                                    this.accept(Token.IDENTIFIER);
                                                }
                                            }

                                            expr = new MySqlCharExpr(str, "_utf8", collate);
                                        } else {
                                            expr = new SQLCharExpr(MySqlUtils.utf8(hexString));
                                        }
                                    } else if ((FnvHash.Constants._UTF16 == hash_lower || FnvHash.Constants._UCS2 == hash_lower) && ident.charAt(0) != '`' && this.dbType == DbType.mysql) {
                                        String hexString;
                                        if (this.lexer.token == Token.LITERAL_HEX) {
                                            hexString = this.lexer.hexString();
                                            this.lexer.nextToken();
                                        } else if (this.lexer.token == Token.LITERAL_CHARS) {
                                            hexString = null;
                                        } else {
                                            this.acceptIdentifier("X");
                                            hexString = this.lexer.stringVal();
                                            this.accept(Token.LITERAL_CHARS);
                                        }

                                        if (hexString == null) {
                                            String str = this.lexer.stringVal();
                                            hexString = HexBin.encode(str.getBytes(MySqlUtils.ASCII));
                                            this.lexer.nextToken();
                                        }

                                        expr = new MySqlCharExpr(hexString, "_utf16");
                                    } else if (FnvHash.Constants._UTF32 == hash_lower && ident.charAt(0) != '`' && this.dbType == DbType.mysql) {
                                        String hexString;
                                        if (this.lexer.token == Token.LITERAL_HEX) {
                                            hexString = this.lexer.hexString();
                                            this.lexer.nextToken();
                                        } else if (this.lexer.token == Token.LITERAL_CHARS) {
                                            hexString = null;
                                        } else {
                                            this.acceptIdentifier("X");
                                            hexString = this.lexer.stringVal();
                                            this.accept(Token.LITERAL_CHARS);
                                        }

                                        if (hexString == null) {
                                            String str = this.lexer.stringVal();
                                            this.lexer.nextToken();
                                            expr = new MySqlCharExpr(str, "_utf32");
                                        } else {
                                            expr = new SQLCharExpr(MySqlUtils.utf32(hexString));
                                        }
                                    } else if (FnvHash.Constants._GBK == hash_lower && ident.charAt(0) != '`' && this.dbType == DbType.mysql) {
                                        String hexString;
                                        if (this.lexer.token == Token.LITERAL_HEX) {
                                            hexString = this.lexer.hexString();
                                            this.lexer.nextToken();
                                        } else if (this.lexer.token == Token.LITERAL_CHARS) {
                                            hexString = null;
                                        } else {
                                            this.acceptIdentifier("X");
                                            hexString = this.lexer.stringVal();
                                            this.accept(Token.LITERAL_CHARS);
                                        }

                                        if (hexString == null) {
                                            String str = this.lexer.stringVal();
                                            this.lexer.nextToken();
                                            expr = new MySqlCharExpr(str, "_gbk");
                                        } else {
                                            expr = new SQLCharExpr(MySqlUtils.gbk(hexString));
                                        }
                                    } else if (FnvHash.Constants._BIG5 == hash_lower && ident.charAt(0) != '`' && this.dbType == DbType.mysql) {
                                        String hexString;
                                        if (this.lexer.token == Token.LITERAL_HEX) {
                                            hexString = this.lexer.hexString();
                                            this.lexer.nextToken();
                                        } else if (this.lexer.token == Token.LITERAL_CHARS) {
                                            hexString = null;
                                        } else {
                                            this.acceptIdentifier("X");
                                            hexString = this.lexer.stringVal();
                                            this.accept(Token.LITERAL_CHARS);
                                        }

                                        if (hexString == null) {
                                            String str = this.lexer.stringVal();
                                            this.lexer.nextToken();
                                            expr = new MySqlCharExpr(str, "_big5");
                                        } else {
                                            expr = new SQLCharExpr(MySqlUtils.big5(hexString));
                                        }
                                    } else {
                                        if (this.lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                                            ident = SQLUtils.normalize(ident, this.dbType);
                                        }

                                        if (ident.charAt(0) == '"' && ident.charAt(ident.length() - 1) == '"') {
                                            hash_lower = FnvHash.hashCode64(ident);
                                        }

                                        SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr(ident, hash_lower);
                                        if (this.lexer.keepSourceLocation) {
                                            this.lexer.computeRowAndColumn();
                                            identifierExpr.setSourceLine(this.lexer.posLine);
                                            identifierExpr.setSourceColumn(this.lexer.posColumn);
                                        }

                                        expr = identifierExpr;
                                    }
                                } else {
                                    expr = new SQLCurrentTimeExpr(SQLCurrentTimeExpr.Type.LOCALTIMESTAMP);
                                }
                            } else {
                                expr = new SQLCurrentTimeExpr(SQLCurrentTimeExpr.Type.LOCALTIME);
                            }
                        } else {
                            expr = new SQLCurrentTimeExpr(SQLCurrentTimeExpr.Type.CURDATE);
                        }
                    } else {
                        expr = new SQLCurrentTimeExpr(SQLCurrentTimeExpr.Type.CURRENT_TIME);
                    }
                } else {
                    expr = new SQLCurrentTimeExpr(SQLCurrentTimeExpr.Type.CURRENT_TIMESTAMP);
                }
            } else {
                expr = new SQLCurrentTimeExpr(SQLCurrentTimeExpr.Type.CURRENT_DATE);
            }

            token = this.lexer.token;
            if (token == Token.DOT) {
                this.lexer.nextTokenIdent();
                String name;
                long name_hash_lower;
                if (this.lexer.token == Token.STAR) {
                    name = "*";
                    name_hash_lower = FnvHash.Constants.STAR;
                } else {
                    name = this.lexer.stringVal();
                    name_hash_lower = this.lexer.hash_lower();
                }

                this.lexer.nextTokenComma();
                token = this.lexer.token;
                if (token != Token.LPAREN) {
                    if (name_hash_lower == FnvHash.Constants.NEXTVAL) {
                        expr = new SQLSequenceExpr((SQLIdentifierExpr) expr, SQLSequenceExpr.Function.NextVal);
                    } else if (name_hash_lower == FnvHash.Constants.CURRVAL) {
                        expr = new SQLSequenceExpr((SQLIdentifierExpr) expr, SQLSequenceExpr.Function.CurrVal);
                    } else if (name_hash_lower == FnvHash.Constants.PREVVAL) {
                        expr = new SQLSequenceExpr((SQLIdentifierExpr) expr, SQLSequenceExpr.Function.PrevVal);
                    } else {
                        if (this.lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                            name = SQLUtils.normalize(name, this.dbType);
                        }

                        if (name.charAt(0) == '"') {
                            name_hash_lower = FnvHash.hashCode64(name);
                        }

                        expr = new SQLPropertyExpr(expr, name, name_hash_lower);
                    }
                } else {
                    boolean aggregate = hash_lower == FnvHash.Constants.WMSYS && name_hash_lower == FnvHash.Constants.WM_CONCAT;
                    expr = this.methodRest(expr, name, aggregate);
                    token = this.lexer.token;
                }
            }

            if (token == Token.COMMA) {
                return new SQLSelectItem(expr, (String) null, connectByRoot);
            }

            if (token == Token.AS) {
                this.lexer.nextTokenAlias();
                String as = null;
                if (this.lexer.token != Token.COMMA && this.lexer.token != Token.FROM) {
                    as = this.lexer.stringVal();
                    if (this.isEnabled(SQLParserFeature.IgnoreNameQuotes) && as.length() > 1) {
                        as = StringUtils.removeNameQuotes(as);
                    }

                    this.lexer.nextTokenComma();
                    if (this.lexer.token == Token.DOT) {
                        this.lexer.nextToken();
                        as = as + '.' + this.lexer.stringVal();
                        this.lexer.nextToken();
                    }
                }

                return new SQLSelectItem(expr, as, connectByRoot);
            }

            if (token == Token.LITERAL_ALIAS) {
                String as = this.lexer.stringVal();
                if (this.isEnabled(SQLParserFeature.IgnoreNameQuotes) && as.length() > 1) {
                    as = StringUtils.removeNameQuotes(as);
                }

                this.lexer.nextTokenComma();
                return new SQLSelectItem(expr, as, connectByRoot);
            }

            if (this.lexer.token == Token.MULTISET && this.dbType == DbType.oracle) {
                OracleMultisetExpr oracleMultisetExpr = (OracleMultisetExpr) this.expr();
                oracleMultisetExpr.setLeftNestedTable(expr);
                String as = this.as();
                return new SQLSelectItem(oracleMultisetExpr, as, connectByRoot);
            }

            if (token == Token.IDENTIFIER && hash_lower != FnvHash.Constants.CURRENT) {
                String as;
                if (this.lexer.hash_lower == FnvHash.Constants.FORCE && DbType.mysql == this.dbType) {
                    String force = this.lexer.stringVal();
                    Lexer.SavePoint savePoint = this.lexer.mark();
                    this.lexer.nextToken();
                    if (this.lexer.token == Token.PARTITION) {
                        this.lexer.reset(savePoint);
                        as = null;
                    } else {
                        as = force;
                        if (this.isEnabled(SQLParserFeature.IgnoreNameQuotes) && force.length() > 1) {
                            as = StringUtils.removeNameQuotes(force);
                        }

                        this.lexer.nextTokenComma();
                    }
                } else if (this.lexer.hash_lower == FnvHash.Constants.SOUNDS && DbType.mysql == this.dbType) {
                    String sounds = this.lexer.stringVal();
                    Lexer.SavePoint savePoint = this.lexer.mark();
                    this.lexer.nextToken();
                    if (this.lexer.token == Token.LIKE) {
                        this.lexer.reset(savePoint);
                        expr = this.exprRest(expr);
                        as = this.as();
                    } else {
                        as = sounds;
                        if (this.isEnabled(SQLParserFeature.IgnoreNameQuotes) && sounds.length() > 1) {
                            as = StringUtils.removeNameQuotes(sounds);
                        }

                        this.lexer.nextTokenComma();
                    }
                } else if (this.lexer.hash_lower == FnvHash.Constants.COLLATE && this.lexer.stringVal().charAt(0) != '`' && DbType.mysql == this.dbType) {
                    expr = this.primaryRest(expr);
                    as = this.as();
                } else if (this.lexer.hash_lower == FnvHash.Constants.REGEXP && this.lexer.stringVal().charAt(0) != '`' && DbType.mysql == this.dbType) {
                    expr = this.exprRest(expr);
                    as = this.as();
                } else {
                    as = this.lexer.stringVal();
                    if (this.isEnabled(SQLParserFeature.IgnoreNameQuotes) && as.length() > 1) {
                        as = StringUtils.removeNameQuotes(as);
                    }

                    this.lexer.nextTokenComma();
                }

                return new SQLSelectItem(expr, as, connectByRoot);
            }

            if (token == Token.LPAREN) {
                if (this.dbType == DbType.mysql) {
                    this.lexer.nextTokenValue();
                } else {
                    this.lexer.nextToken();
                }

                expr = this.methodRest(expr, false);
            } else {
                expr = this.primaryRest(expr);
            }

            expr = this.exprRest(expr);
        }

        String alias;
        List<String> aliasList;
        aliasList = null;
        label519:
        switch (this.lexer.token) {
            case FULL:
            case TABLESPACE:
                alias = this.lexer.stringVal();
                this.lexer.nextToken();
                break;
            case EOF:
                alias = null;
                break;
            case AS:
                this.lexer.nextTokenAlias();
                if (this.lexer.token == Token.LITERAL_INT) {
                    alias = '"' + this.lexer.stringVal() + '"';
                    this.lexer.nextToken();
                } else if (this.lexer.token == Token.LPAREN) {
                    this.lexer.nextToken();
                    aliasList = new ArrayList();

                    while (true) {
                        String stringVal = this.lexer.stringVal();
                        this.lexer.nextToken();
                        aliasList.add(stringVal);
                        if (this.lexer.token() != Token.COMMA) {
                            this.accept(Token.RPAREN);
                            alias = null;
                            break label519;
                        }

                        this.lexer.nextToken();
                    }
                } else {
                    alias = this.alias();
                }
                break;
            default:
                alias = this.as();
        }

        if (alias == null && this.isEnabled(SQLParserFeature.SelectItemGenerateAlias) && !(expr instanceof SQLName) && !(expr instanceof SQLNumericLiteralExpr) && !(expr instanceof SQLCharExpr) && !(expr instanceof SQLNullExpr) && !(expr instanceof SQLBooleanExpr)) {
            alias = this.lexer.text.substring(startPos, this.lexer.startPos);
            if (this.lexer.comments != null) {
                for (int i = this.lexer.comments.size() - 1; i >= 0; --i) {
                    String comment = (String) this.lexer.comments.get(i);
                    int p = alias.lastIndexOf(comment);
                    if (p >= 0) {
                        alias = alias.substring(0, p - 1);
                    }
                }
            }

            alias = CharTypes.trim(alias);
            if (alias.length() > 0) {
                boolean specialChar = false;

                for (int i = 0; i < alias.length(); ++i) {
                    char ch = alias.charAt(i);
                    if (this.dbType == DbType.mysql) {
                        if (ch == '`') {
                            specialChar = true;
                            break;
                        }
                    } else if (!CharTypes.isIdentifierChar(ch)) {
                        specialChar = true;
                        break;
                    }
                }

                if (specialChar) {
                    if (this.dbType == DbType.mysql) {
                        alias = alias.replaceAll("`", "``");
                        alias = '`' + alias + '`';
                    } else {
                        alias = alias.replaceAll("\"", "\\\"");
                    }
                }
            }
        }

        SQLSelectItem selectItem;
        if (aliasList != null) {
            selectItem = new SQLSelectItem(expr, aliasList, connectByRoot);
        } else {
            selectItem = new SQLSelectItem(expr, alias, connectByRoot);
        }

        if (this.lexer.token == Token.HINT && !this.lexer.isEnabled(SQLParserFeature.StrictForWall)) {
            String comment = "/*" + this.lexer.stringVal() + "*/";
            selectItem.addAfterComment(comment);
            this.lexer.nextToken();
        }

        return selectItem;
    }

    public SQLPartition parsePartition() {
        throw new ParserException("TODO");
    }

    public SQLPartitionSpec parsePartitionSpec() {
        SQLPartitionSpec spec = new SQLPartitionSpec();
        this.accept(Token.PARTITION);
        this.accept(Token.LPAREN);

        while (true) {
            SQLPartitionSpec.Item item = new SQLPartitionSpec.Item();
            item.setColumn(this.name());
            this.accept(Token.EQ);
            item.setValue(this.expr());
            spec.addItem(item);
            if (this.lexer.token() != Token.COMMA) {
                this.accept(Token.RPAREN);
                return spec;
            }

            this.lexer.nextToken();
        }
    }

    protected SQLPartitionBy parsePartitionBy() {
        this.lexer.nextToken();
        this.accept(Token.BY);
        SQLPartitionBy partitionClause = null;
        if (this.lexer.identifierEquals("VALUE")) {
            partitionClause = new SQLPartitionByValue();
            if (this.lexer.identifierEquals(FnvHash.Constants.VALUE)) {
                this.lexer.nextToken();
                if (this.lexer.token() == Token.LPAREN) {
                    this.lexer.nextToken();
                    partitionClause.addColumn(this.expr());
                    this.accept(Token.RPAREN);
                }
            }

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

        return partitionClause;
    }

    public SQLExpr parseGroupingSet() {
        String tmp = this.lexer.stringVal();
        this.acceptIdentifier("GROUPING");
        SQLGroupingSetExpr expr = new SQLGroupingSetExpr();
        if (this.lexer.token != Token.SET && !this.lexer.identifierEquals(FnvHash.Constants.SET)) {
            return new SQLIdentifierExpr(tmp);
        } else {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            this.exprList(expr.getParameters(), expr);
            this.accept(Token.RPAREN);
            return expr;
        }
    }

    public SQLPartitionValue parsePartitionValues() {
        if (this.lexer.token != Token.VALUES) {
            return null;
        } else {
            this.lexer.nextToken();
            SQLPartitionValue values = null;
            if (this.lexer.token == Token.IN) {
                this.lexer.nextToken();
                values = new SQLPartitionValue(SQLPartitionValue.Operator.In);
                this.accept(Token.LPAREN);
                this.exprList(values.getItems(), values);
                this.accept(Token.RPAREN);
            } else if (this.lexer.identifierEquals(FnvHash.Constants.LESS)) {
                this.lexer.nextToken();
                this.acceptIdentifier("THAN");
                values = new SQLPartitionValue(SQLPartitionValue.Operator.LessThan);
                if (this.lexer.identifierEquals(FnvHash.Constants.MAXVALUE)) {
                    SQLIdentifierExpr maxValue = new SQLIdentifierExpr(this.lexer.stringVal());
                    this.lexer.nextToken();
                    maxValue.setParent(values);
                    values.addItem(maxValue);
                } else {
                    this.accept(Token.LPAREN);
                    this.exprList(values.getItems(), values);
                    this.accept(Token.RPAREN);
                }
            } else if (this.lexer.identifierEquals("EQU")) {
                this.lexer.nextToken();
                this.accept(Token.OR);
                this.acceptIdentifier("LESS");
                this.acceptIdentifier("THAN");
                values = new SQLPartitionValue(SQLPartitionValue.Operator.EquorLessThan);
                if (this.lexer.identifierEquals(FnvHash.Constants.MAXVALUE)) {
                    SQLIdentifierExpr maxValue = new SQLIdentifierExpr(this.lexer.stringVal());
                    this.lexer.nextToken();
                    maxValue.setParent(values);
                    values.addItem(maxValue);
                } else {
                    this.accept(Token.LPAREN);
                    this.exprList(values.getItems(), values);
                    this.accept(Token.RPAREN);
                }
            } else if (this.lexer.token == Token.LPAREN) {
                values = new SQLPartitionValue(SQLPartitionValue.Operator.List);
                this.lexer.nextToken();
                this.exprList(values.getItems(), values);
                this.accept(Token.RPAREN);
            }

            return values;
        }
    }

    protected static boolean isIdent(SQLExpr expr, String name) {
        if (expr instanceof SQLIdentifierExpr) {
            SQLIdentifierExpr identExpr = (SQLIdentifierExpr) expr;
            return identExpr.getName().equalsIgnoreCase(name);
        } else {
            return false;
        }
    }

    public SQLLimit parseLimit() {
        if (this.lexer.token != Token.LIMIT) {
            return null;
        } else {
            SQLLimit limit = new SQLLimit();
            limit.setType(SQLLimit.Type.LIMIT);
            this.lexer.nextTokenValue();
            SQLExpr temp;
            if (this.lexer.token == Token.LITERAL_INT) {
                temp = new SQLIntegerExpr(this.lexer.integerValue());
                this.lexer.nextTokenComma();
                if (this.lexer.token != Token.COMMA && this.lexer.token != Token.EOF && this.lexer.token != Token.IDENTIFIER) {
                    temp = this.primaryRest(temp);
                    temp = this.exprRest(temp);
                }
            } else {
                temp = this.expr();
            }

            if (this.lexer.token == Token.COMMA) {
                limit.setOffset(temp);
                this.lexer.nextTokenValue();
                SQLExpr rowCount;
                if (this.lexer.token == Token.LITERAL_INT) {
                    rowCount = new SQLIntegerExpr(this.lexer.integerValue());
                    this.lexer.nextToken();
                    if (this.lexer.token != Token.EOF && this.lexer.token != Token.IDENTIFIER) {
                        rowCount = this.primaryRest(rowCount);
                        rowCount = this.exprRest(rowCount);
                    }
                } else {
                    rowCount = this.expr();
                }

                limit.setRowCount(rowCount);
            } else if (!this.lexer.identifierEquals(FnvHash.Constants.OFFSET) && this.lexer.token() != Token.OFFSET) {
                limit.setRowCount(temp);
            } else {
                limit.setType(SQLLimit.Type.LIMIT_OFFSET);
                limit.setRowCount(temp);
                this.lexer.nextToken();
                limit.setOffset(this.expr());
            }

            if (this.lexer.token == Token.BY && this.dbType == DbType.clickhouse) {
                this.lexer.nextToken();

                while (true) {
                    SQLExpr item = this.expr();
                    limit.addBy(item);
                    if (this.lexer.token != Token.COMMA) {
                        break;
                    }

                    this.lexer.nextToken();
                }
            }

            return limit;
        }
    }

    public void parseIndexRest(SQLIndex idx) {
        this.parseIndexRest(idx, idx);
    }

    public void parseIndexRest(SQLIndex idx, SQLObject parent) {
        this.accept(Token.LPAREN);

        while (true) {
            SQLSelectOrderByItem selectOrderByItem = this.parseSelectOrderByItem();
            selectOrderByItem.setParent(parent);
            idx.getColumns().add(selectOrderByItem);
            if (this.lexer.token() != Token.COMMA) {
                this.accept(Token.RPAREN);
                if (this.lexer.identifierEquals(FnvHash.Constants.COVERING)) {
                    Lexer.SavePoint mark = this.lexer.mark();
                    this.lexer.nextToken();
                    if (this.lexer.token != Token.LPAREN) {
                        this.lexer.reset(mark);
                        return;
                    }

                    this.lexer.nextToken();

                    while (true) {
                        SQLName name = this.name();
                        name.setParent(parent);
                        idx.getCovering().add(name);
                        if (this.lexer.token() != Token.COMMA) {
                            this.accept(Token.RPAREN);
                            break;
                        }

                        this.lexer.nextToken();
                    }
                }

                return;
            }

            this.lexer.nextToken();
        }
    }

    public SQLExternalRecordFormat parseRowFormat() {
        this.lexer.nextToken();
        this.acceptIdentifier("FORMAT");
        if (this.lexer.identifierEquals(FnvHash.Constants.DELIMITED)) {
            this.lexer.nextToken();
        }

        SQLExternalRecordFormat format = new SQLExternalRecordFormat();
        if (this.lexer.identifierEquals(FnvHash.Constants.FIELDS)) {
            this.lexer.nextToken();
            this.acceptIdentifier("TERMINATED");
            this.accept(Token.BY);
            format.setTerminatedBy(this.expr());
        } else if (this.lexer.identifierEquals("FIELD")) {
            throw new ParserException("syntax error, expect FIELDS, " + this.lexer.info());
        }

        if (this.lexer.token() == Token.ESCAPE || this.lexer.identifierEquals(FnvHash.Constants.ESCAPED)) {
            this.lexer.nextToken();
            this.accept(Token.BY);
            format.setEscapedBy(this.expr());
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.LINES)) {
            this.lexer.nextToken();
            this.acceptIdentifier("TERMINATED");
            this.accept(Token.BY);
            format.setLinesTerminatedBy(this.expr());
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.COLLECTION)) {
            this.lexer.nextToken();
            this.acceptIdentifier("ITEMS");
            this.acceptIdentifier("TERMINATED");
            this.accept(Token.BY);
            format.setCollectionItemsTerminatedBy(this.expr());
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.MAP)) {
            this.lexer.nextToken();
            this.acceptIdentifier("KEYS");
            this.acceptIdentifier("TERMINATED");
            this.accept(Token.BY);
            format.setMapKeysTerminatedBy(this.expr());
        }

        if (this.lexer.identifierEquals(FnvHash.Constants.SERDE)) {
            this.lexer.nextToken();
            format.setSerde(this.expr());
        }

        return format;
    }

    static {
        String[] strings = new String[]{"AVG", "COUNT", "MAX", "MIN", "STDDEV", "SUM"};
        AGGREGATE_FUNCTIONS_CODES = FnvHash.fnv1a_64_lower(strings, true);
        AGGREGATE_FUNCTIONS = new String[AGGREGATE_FUNCTIONS_CODES.length];

        for (String str : strings) {
            long hash = FnvHash.fnv1a_64_lower(str);
            int index = Arrays.binarySearch(AGGREGATE_FUNCTIONS_CODES, hash);
            AGGREGATE_FUNCTIONS[index] = str;
        }

    }
}
