Package com.redspr.redquerybuilder.core.client.command

Source Code of com.redspr.redquerybuilder.core.client.command.Parser

/*
* Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*
* Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
* Support for the operator "&&" as an alias for SPATIAL_INTERSECTS
*/
package com.redspr.redquerybuilder.core.client.command;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;

import com.redspr.redquerybuilder.core.client.command.dml.Query;
import com.redspr.redquerybuilder.core.client.command.dml.Select;
import com.redspr.redquerybuilder.core.client.command.dml.SelectUnion;
import com.redspr.redquerybuilder.core.client.constant.ErrorCode;
import com.redspr.redquerybuilder.core.client.engine.Session;
import com.redspr.redquerybuilder.core.client.expression.Comparison;
import com.redspr.redquerybuilder.core.client.expression.ConditionAndOr;
import com.redspr.redquerybuilder.core.client.expression.Expression;
import com.redspr.redquerybuilder.core.client.expression.ExpressionColumn;
import com.redspr.redquerybuilder.core.client.expression.Null;
import com.redspr.redquerybuilder.core.client.expression.Parameter;
import com.redspr.redquerybuilder.core.client.table.TableFilter;
import com.redspr.redquerybuilder.core.client.util.ObjectArray;
import com.redspr.redquerybuilder.core.client.util.StatementBuilder;
import com.redspr.redquerybuilder.core.client.value.Value;
import com.redspr.redquerybuilder.core.shared.meta.Column;
import com.redspr.redquerybuilder.core.shared.meta.Database;
import com.redspr.redquerybuilder.core.shared.meta.Operator;
import com.redspr.redquerybuilder.core.shared.meta.Schema;
import com.redspr.redquerybuilder.core.shared.meta.Table;


/**
* The parser is used to convert a SQL statement string to an command object.
*/
public class Parser {

    // used during the tokenizer phase
    private static final int CHAR_END = -1, CHAR_VALUE = 2, CHAR_QUOTED = 3;
    private static final int CHAR_NAME = 4, CHAR_SPECIAL_1 = 5, CHAR_SPECIAL_2 = 6;
    private static final int CHAR_STRING = 7, CHAR_DECIMAL = 8, CHAR_DOLLAR_QUOTED_STRING = 9;

    // this are token types
    private static final int KEYWORD = 1, IDENTIFIER = 2, PARAMETER = 3, END = 4, VALUE = 5;
    private static final int EQUAL = 6, BIGGER_EQUAL = 7, BIGGER = 8;
    private static final int SMALLER = 9, SMALLER_EQUAL = 10, NOT_EQUAL = 11, AT = 12;
    private static final int MINUS = 17, PLUS = 18;
    private static final int STRING_CONCAT = 22;
    private static final int OPEN = 31, CLOSE = 32, NULL = 34, TRUE = 40, FALSE = 41;

    private static final int CURRENT_TIMESTAMP = 42, CURRENT_DATE = 43, CURRENT_TIME = 44, ROWNUM = 45;

    private final Database database;
    private final  Session session;

    private int[] characterTypes;
    private int currentTokenType;
    private String currentToken;
    private boolean currentTokenQuoted;
    private Value currentValue;
    private String sqlCommand;
    private String originalSQL;
    private char[] sqlCommandChars;
    private int lastParseIndex;
    private int parseIndex;
    //private CreateView createView;
    private Prepared currentPrepared;
    private Select currentSelect;
    private ObjectArray<Parameter> parameters;
    private String schemaName;
    private ObjectArray<String> expectedList;
    private boolean rightsChecked;
    private boolean recompileAlways;
    private ObjectArray<Parameter> indexedParameterList;

    public Parser(Session session2) {
        this.session = session2;
        database = session.getDatabase();
    }


    /**
     * Parse the statement, but don't prepare it for execution.
     *
     * @param sql the SQL statement to parse
     * @return the prepared object
     */
    public Prepared parseOnly(String sql) throws SQLException {
        return parse(sql);
    }



    private Prepared parse(String sql) throws SQLException {
        Prepared p = null;
       // try {
            // first, try the fast variant
            p = parse(sql, false);
//        }// catch (SQLException e) {
//            if (e.getErrorCode() == ErrorCode.SYNTAX_ERROR_1) {
//                // now, get the detailed exception
//                p = parse(sql, true);
//            } else {
//               throw Message.addSQL(e, sql);
//            }
        //}
       // p.setPrepareAlways(recompileAlways);
       // p.setParameterList(parameters);
        return p;
    }

    private Prepared parse(String sql, boolean withExpectedList) throws SQLException {
        initialize(sql);
        if (withExpectedList) {
            //expectedList = ObjectArray.newInstance();
        } else {
           // expectedList = null;
        }
        parameters = ObjectArray.newInstance();
        currentSelect = null;
        currentPrepared = null;
        //createView = null;
        recompileAlways = false;
        //indexedParameterList = null;
        read();
        return parsePrepared();
    }

    private Prepared parsePrepared() throws SQLException {
        int start = lastParseIndex;
        Prepared c = null;
        String token = currentToken;
        if (token.length() == 0) {
            c = new NoOperation();
        } else {
            char first = token.charAt(0);
            switch (first) {
            case '(':
                c = parseSelect();
                break;
            case 'A':
                if (readIf("ALTER")) {
                    c = parseAlter();
                } else if (readIf("ANALYZE")) {
                    c = parseAnalyze();
                }
                break;
            case 'F':
                if (isToken("FROM")) {
                    c = parseSelect();
                }
                break;
            case 'I':
                if (readIf("INSERT")) {
                    c = parseInsert();
                }
                break;
            case 'M':
                if (readIf("MERGE")) {
                    c = parseMerge();
                }
                break;
            case 'P':
                if (readIf("PREPARE")) {
                    c = parsePrepare();
                }
                break;
            case 'S':
                if (isToken("SELECT")) {
                    c = parseSelect();
                } else if (readIf("SET")) {
                    c = parseSet();
                } else if (readIf("SAVEPOINT")) {
                    c = parseSavepoint();
                } else if (readIf("SCRIPT")) {
                    c = parseScript();
                } else if (readIf("SHUTDOWN")) {
                    c = parseShutdown();
                } else if (readIf("SHOW")) {
                    c = parseShow();
                }
                break;
            case 'T':
                if (readIf("TRUNCATE")) {
                    c = parseTruncate();
                }
                break;
            case 'U':
                if (readIf("UPDATE")) {
                    c = parseUpdate();
                }
                break;
            case 'V':
                if (readIf("VALUES")) {
                    c = parserCall();
                }
                break;
            case 'W':
                if (readIf("WITH")) {
                    c = parserWith();
                }
                break;
            default:
                throw getSyntaxError();
            }
            if (indexedParameterList != null) {
                for (int i = 0; i < indexedParameterList.size(); i++) {
                    if (indexedParameterList.get(i) == null) {
                        indexedParameterList.set(i, new Parameter(session, i));
                    }
                }
                parameters = indexedParameterList;
            }
            if (readIf("{")) {
                do {
                    int index = (int) readLong() - 1;
                    if (index < 0 || index >= parameters.size()) {
                        throw getSyntaxError();
                    }
                    Parameter p = parameters.get(index);
                    if (p == null) {
                        throw getSyntaxError();
                    }
                    read(":");
                    Expression expr = readExpression();
                  //  expr = expr.optimize(session);
                    //p.setValue(expr.getValue(session));
                } while (readIf(","));
                read("}");
                for (Parameter p : parameters) {
                    //p.checkSet();
                }
                parameters.clear();
            }
        }
        if (c == null) {
            throw getSyntaxError();
        }
        setSQL(c, null, start);
        return c;
    }

    private SQLException getSyntaxError() {
        if (expectedList == null || expectedList.size() == 0) {
            return Message.getSyntaxError(sqlCommand, parseIndex);
        }
        StatementBuilder buff = new StatementBuilder();
        for (String e : expectedList) {
            buff.appendExceptFirst(", ");
            buff.append(e);
        }
        return Message.getSyntaxError(sqlCommand, parseIndex, buff.toString());
    }

    private Prepared parseAnalyze() throws SQLException {
//        Analyze command = new Analyze(session);
//        if (readIf("SAMPLE_SIZE")) {
//            command.setTop(getPositiveInt());
//        }
//        return command;
        return null;
    }

    private Prepared parseShutdown() throws SQLException {
        return null;
    }

    private Prepared parsePrepare() throws SQLException {
        return null;
    }

    private Prepared parseSavepoint() throws SQLException {
        return null;
    }

    private Schema getSchema() throws SQLException {
//        if (schemaName == null) {
//            return null;
//        }
//        Schema schema = database.findSchema(schemaName);
//        if (schema == null) {
//            if ("SESSION".equals(schemaName)) {
//                // for local temporary tables
//                schema = database.getSchema(session.getCurrentSchemaName());
//            } else {
//                throw Message.getSQLException(ErrorCode.SCHEMA_NOT_FOUND_1, schemaName);
//            }
//        }
//        return schema;
        return null;
    }

    private Column readTableColumn(TableFilter filter) throws SQLException {
//        String tableAlias = null;
//        String columnName = readColumnIdentifier();
//        if (readIf(".")) {
//            tableAlias = columnName;
//            columnName = readColumnIdentifier();
//            if (readIf(".")) {
//                String schema = tableAlias;
//                tableAlias = columnName;
//                columnName = readColumnIdentifier();
//                if (readIf(".")) {
//                    String catalogName = schema;
//                    schema = tableAlias;
//                    tableAlias = columnName;
//                    columnName = readColumnIdentifier();
//                    if (!catalogName.equals(database.getShortName())) {
//                        throw Message.getSQLException(ErrorCode.DATABASE_NOT_FOUND_1, catalogName);
//                    }
//                }
//                if (!schema.equals(filter.getTable().getSchema().getName())) {
//                    throw Message.getSQLException(ErrorCode.SCHEMA_NOT_FOUND_1, schema);
//                }
//            }
//            if (!tableAlias.equals(filter.getTableAlias())) {
//                throw Message.getSQLException(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableAlias);
//            }
//        }
//        return filter.getTable().getColumn(columnName);
        return null;
    }

    private Prepared parseUpdate() throws SQLException {
        return null;
    }

    private TableFilter readSimpleTableFilter() throws SQLException {
//        Table table = readTableOrView();
//        String alias = null;
//        if (readIf("AS")) {
//            alias = readAliasIdentifier();
//        } else if (currentTokenType == IDENTIFIER) {
//            if (!"SET".equals(currentToken)) {
//                // SET is not a keyword (PostgreSQL supports it as a table name)
//                alias = readAliasIdentifier();
//            }
//        }
//        return new TableFilter(session, table, alias, rightsChecked, currentSelect);
        return null;
    }

    private IndexColumn[] parseIndexColumnList() throws SQLException {
//        ObjectArray<IndexColumn> columns = ObjectArray.newInstance();
//        do {
//            IndexColumn column = new IndexColumn();
//            column.columnName = readColumnIdentifier();
//            columns.add(column);
//            if (readIf("ASC")) {
//                // ignore
//            } else if (readIf("DESC")) {
//                column.sortType = SortOrder.DESCENDING;
//            }
//            if (readIf("NULLS")) {
//                if (readIf("FIRST")) {
//                    column.sortType |= SortOrder.NULLS_FIRST;
//                } else {
//                    read("LAST");
//                    column.sortType |= SortOrder.NULLS_LAST;
//                }
//            }
//        } while (readIf(","));
//        read(")");
//        return columns.toArray(new IndexColumn[columns.size()]);
        return null;
    }

    private String[] parseColumnList() throws SQLException {
        ObjectArray<String> columns = ObjectArray.newInstance();
        do {
            String columnName = readColumnIdentifier();
            columns.add(columnName);
        } while (readIfMore());
        return columns.toArray(new String[columns.size()]);
    }

    private Column[] parseColumnList(Table table) throws SQLException {
//        ObjectArray<Column> columns = ObjectArray.newInstance();
//        HashSet<Column> set = New.hashSet();
//        if (!readIf(")")) {
//            do {
//                Column column = table.getColumn(readColumnIdentifier());
//                if (!set.add(column)) {
//                    throw Message.getSQLException(ErrorCode.DUPLICATE_COLUMN_NAME_1, column.getSQL());
//                }
//                columns.add(column);
//            } while (readIfMore());
//        }
//        return columns.toArray(new Column[columns.size()]);
        return null;
    }

    private boolean readIfMore() throws SQLException {
        if (readIf(",")) {
            return !readIf(")");
        }
        read(")");
        return false;
    }

    private Prepared parseHelp() throws SQLException {
        return null;
    }

    private Prepared parseShow() throws SQLException {
        return null;
//        ObjectArray<Value> paramValues = ObjectArray.newInstance();
//        StringBuilder buff = new StringBuilder("SELECT ");
//        if (readIf("CLIENT_ENCODING")) {
//            // for PostgreSQL compatibility
//            buff.append("'UNICODE' AS CLIENT_ENCODING FROM DUAL");
//        } else if (readIf("DATESTYLE")) {
//            // for PostgreSQL compatibility
//            buff.append("'ISO' AS DATESTYLE FROM DUAL");
//        } else if (readIf("SERVER_VERSION")) {
//            // for PostgreSQL compatibility
//            buff.append("'8.1.4' AS SERVER_VERSION FROM DUAL");
//        } else if (readIf("SERVER_ENCODING")) {
//            // for PostgreSQL compatibility
//            buff.append("'UTF8' AS SERVER_ENCODING FROM DUAL");
//        } else if (readIf("TABLES")) {
//            // for MySQL compatibility
//            String schema = Constants.SCHEMA_MAIN;
//            if (readIf("FROM")) {
//                schema = readUniqueIdentifier();
//            }
//            buff.append("TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=? ORDER BY TABLE_NAME");
//            paramValues.add(ValueString.get(schema));
//        } else if (readIf("COLUMNS")) {
//            // for MySQL compatibility
//            read("FROM");
//            String tableName = readUniqueIdentifier();
//            paramValues.add(ValueString.get(tableName));
//            String schema = Constants.SCHEMA_MAIN;
//            if (readIf("FROM")) {
//                schema = readUniqueIdentifier();
//            }
//            buff.append("C.COLUMN_NAME FIELD, " +
//                    "C.TYPE_NAME || '(' || C.NUMERIC_PRECISION || ')' TYPE, " +
//                    "C.IS_NULLABLE \"NULL\", " +
//                    "CASE I.INDEX_TYPE_NAME WHEN 'PRIMARY KEY' THEN 'PRI' WHEN 'UNIQUE INDEX' THEN 'UNI' ELSE '' END KEY, " +
//                    "IFNULL(COLUMN_DEFAULT, 'NULL') DEFAULT " +
//                    "FROM INFORMATION_SCHEMA.COLUMNS C LEFT OUTER JOIN INFORMATION_SCHEMA.INDEXES I " +
//                    "ON I.TABLE_SCHEMA=C.TABLE_SCHEMA " +
//                    "AND I.TABLE_NAME=C.TABLE_NAME " +
//                    "AND I.COLUMN_NAME=C.COLUMN_NAME " +
//                    "WHERE C.TABLE_NAME=? AND C.TABLE_SCHEMA=? " +
//                    "ORDER BY C.ORDINAL_POSITION");
//            //paramValues.add(ValueString.get(schema));
//        } else if (readIf("DATABASES") || readIf("SCHEMAS")) {
//            // for MySQL compatibility
//            buff.append("SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA");
//        }
//        return prepare(session, buff.toString(), paramValues);
    }



    private Prepared parseMerge() throws SQLException {
        return null;
    }

    private Prepared parseInsert() throws SQLException {
       return null;
    }

    private TableFilter readTableFilter(boolean fromOuter) throws SQLException {
        Table table;
        String alias = null;
//        if (readIf("(")) {
//            if (isToken("SELECT") || isToken("FROM")) {
//                int start = lastParseIndex;
//                int paramIndex = parameters.size();
//                Query query = parseSelectUnion();
//                read(")");
//                query = parseSelectUnionExtension(query, start, true);
//                ObjectArray<Parameter> params = ObjectArray.newInstance();
//                for (int i = paramIndex; i < parameters.size(); i++) {
//                    params.add(parameters.get(i));
//                }
//                query.setParameterList(params);
//                query.init();
//                Session s;
//                if (createView != null) {
//                    s = database.getSystemSession();
//                } else {
//                    s = session;
//                }
//                alias = session.getNextSystemIdentifier(sqlCommand);
//                table = TableView.createTempView(s, session.getUser(), alias, query, currentSelect);
//            } else {
//                TableFilter top = readTableFilter(fromOuter);
//                top = readJoin(top, currentSelect, fromOuter);
//                read(")");
//                alias = readFromAlias(null);
//                if (alias != null) {
//                    top.setAlias(alias);
//                }
//                return top;
//            }
//        } else {
            String tableName = readIdentifierWithSchema(null);
//            if (readIf("(")) {
//                Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN);
//                if (tableName.equals(RangeTable.NAME)) {
//                    Expression min = readExpression();
//                    read(",");
//                    Expression max = readExpression();
//                    read(")");
//                    table = new RangeTable(mainSchema, min, max);
//                } else {
//                    Expression func = readFunction(tableName);
//                    if (!(func instanceof FunctionCall)) {
//                        throw getSyntaxError();
//                    }
//                    table = new FunctionTable(mainSchema, session, func, (FunctionCall) func);
//                }
//            } else if ("DUAL".equals(tableName)) {
//                table = getDualTable();
//            } else {
               table = readTableOrView(tableName);
//            }
//        }
        alias = readFromAlias(alias);
        return new TableFilter(session, table, alias, currentSelect);

    }

    private String readFromAlias(String alias) throws SQLException {
        if (readIf("AS")) {
            alias = readAliasIdentifier();
        } else if (currentTokenType == IDENTIFIER) {
            // left and right are not keywords (because they are functions as
            // well)
            if (!isToken("LEFT") && !isToken("RIGHT") && !isToken("FULL")) {
                alias = readAliasIdentifier();
            }
        }
        return alias;
    }

    private Prepared parseTruncate() throws SQLException {
        return null;
    }

    private boolean readIfExists(boolean ifExists) throws SQLException {
        if (readIf("IF")) {
            read("EXISTS");
            ifExists = true;
        }
        return ifExists;
    }

    private Prepared parseComment() throws SQLException {
       return null;
    }

    private Prepared parseDropUserDataType() throws SQLException {
        return null;
    }

    private Prepared parseDropAggregate() throws SQLException {
        return null;
    }

    private TableFilter readJoin(TableFilter top, Select command, boolean fromOuter) throws SQLException {
        TableFilter last = top;
        while (true) {
            if (readIf("RIGHT")) {
                readIf("OUTER");
                read("JOIN");
                // the right hand side is the 'inner' table usually
                TableFilter newTop = readTableFilter(fromOuter);
                newTop = readJoin(newTop, command, true);
                Expression on = null;
                if (readIf("ON")) {
                    on = readExpression();
                }
                newTop.addJoin(top, true, on);
                top = newTop;
                last = newTop;
            } else if (readIf("LEFT")) {
                readIf("OUTER");
                read("JOIN");
                TableFilter join = readTableFilter(true);
                top = readJoin(top, command, true);
                Expression on = null;
                if (readIf("ON")) {
                    on = readExpression();
                }
                top.addJoin(join, true, on);
                last = join;
            } else if (readIf("FULL")) {
                throw this.getSyntaxError();
            } else if (readIf("INNER")) {
                read("JOIN");
                TableFilter join = readTableFilter(fromOuter);
                top = readJoin(top, command, false);
                Expression on = null;
                if (readIf("ON")) {
                    on = readExpression();
                }
                top.addJoin(join, fromOuter, on);
                last = join;
            } else if (readIf("JOIN")) {
                TableFilter join = readTableFilter(fromOuter);
                top = readJoin(top, command, false);
                Expression on = null;
                if (readIf("ON")) {
                    on = readExpression();
                }
                top.addJoin(join, fromOuter, on);
                last = join;
            } else if (readIf("CROSS")) {
                read("JOIN");
                TableFilter join = readTableFilter(fromOuter);
                top.addJoin(join, fromOuter, null);
                last = join;
            } else if (readIf("NATURAL")) {
                read("JOIN");
                TableFilter join = readTableFilter(fromOuter);
                Column[] tableCols = last.getTable().getColumns().toArray(new Column[0]);
                Column[] joinCols = join.getTable().getColumns().toArray(new Column[0]);
                String tableSchema = last.getTable().getSchema().getName();
                String joinSchema = join.getTable().getSchema().getName();
                Expression on = null;
                for (Column tc : tableCols) {
                    String tableColumnName = tc.getName();
                    for (Column c : joinCols) {
                        String joinColumnName = c.getName();
                        if (tableColumnName.equals(joinColumnName)) {
                            // XXX join.addNaturalJoinColumn(c);
                            Expression tableExpr = new ExpressionColumn(session, tableSchema, last
                                    .getTableAlias(), tableColumnName);
                            Expression joinExpr = new ExpressionColumn(session, joinSchema, join
                                    .getTableAlias(), joinColumnName);
                            Expression equal = new Comparison(session, Operator.EQUAL, tableExpr, joinExpr);
                            if (on == null) {
                                on = equal;
                            } else {
                                on = new ConditionAndOr(session, ConditionAndOr.AND, on, equal);
                            }
                        }
                    }
                }
                top.addJoin(join, fromOuter, on);
                last = join;
            } else {
                break;
            }
        }
        return top;
    }

    private Query parseSelect() throws SQLException {
      //  int paramIndex = parameters.size();
        Query command = parseSelectUnion();
//        ObjectArray<Parameter> params = ObjectArray.newInstance();
//        for (int i = paramIndex; i < parameters.size(); i++) {
//            params.add(parameters.get(i));
//        }
//        command.setParameterList(params);
//        command.init();

        return command;
    }

    private Query parseSelectUnion() throws SQLException {
        int start = lastParseIndex;
        Query command = parseSelectSub();
        return parseSelectUnionExtension(command, start, false);
    }

    private Query parseSelectUnionExtension(Query command, int start, boolean unionOnly) throws SQLException {
          while (true) {
            if (readIf("UNION")) {
                SelectUnion union = new SelectUnion(session, command);
                if (readIf("ALL")) {
                    union.setUnionType(SelectUnion.UNION_ALL);
                } else {
                    readIf("DISTINCT");
                    union.setUnionType(SelectUnion.UNION);
                }
                union.setRight(parseSelectSub());
                command = union;
            } else if (readIf("MINUS") || readIf("EXCEPT")) {
                SelectUnion union = new SelectUnion(session, command);
                union.setUnionType(SelectUnion.EXCEPT);
                union.setRight(parseSelectSub());
                command = union;
            } else if (readIf("INTERSECT")) {
                SelectUnion union = new SelectUnion(session, command);
                union.setUnionType(SelectUnion.INTERSECT);
                union.setRight(parseSelectSub());
                command = union;
            } else {
                break;
            }
        }
        if (!unionOnly) {
            parseEndOfQuery(command);
        }
        setSQL(command, null, start);
        return command;
    }

    private void parseEndOfQuery(Prepared command) throws SQLException {
//
//        if (readIf("ORDER")) {
//            read("BY");
//            Select oldSelect = currentSelect;
//            if (command instanceof Select) {
//                currentSelect = (Select) command;
//            }
//            ObjectArray<SelectOrderBy> orderList = ObjectArray.newInstance();
//            do {
//                boolean canBeNumber = true;
//                if (readIf("=")) {
//                    canBeNumber = false;
//                }
//                SelectOrderBy order = new SelectOrderBy();
//                Expression expr = readExpression();
//                if (canBeNumber && expr instanceof ValueExpression && expr.getType() == Value.INT) {
//                    order.columnIndexExpr = expr;
//                } else if (expr instanceof Parameter) {
//                    recompileAlways = true;
//                    order.columnIndexExpr = expr;
//                } else {
//                    order.expression = expr;
//                }
//                if (readIf("DESC")) {
//                    order.descending = true;
//                } else {
//                    readIf("ASC");
//                }
//                if (readIf("NULLS")) {
//                    if (readIf("FIRST")) {
//                        order.nullsFirst = true;
//                    } else {
//                        read("LAST");
//                        order.nullsLast = true;
//                    }
//                }
//                orderList.add(order);
//            } while (readIf(","));
//            command.setOrder(orderList);
//            currentSelect = oldSelect;
//        }
//        if (database.getMode().supportOffsetFetch) {
//            // make sure aggregate functions will not work here
//            Select temp = currentSelect;
//            currentSelect = null;
//
//            // http://sqlpro.developpez.com/SQL2008/
//            if (readIf("OFFSET")) {
//                command.setOffset(readExpression().optimize(session));
//                if (!readIf("ROW")) {
//                    read("ROWS");
//                }
//            }
//            if (readIf("FETCH")) {
//                read("FIRST");
//                if (readIf("ROW")) {
//                    command.setLimit(ValueExpression.get(ValueInt.get(1)));
//                } else {
//                    Expression limit = readExpression().optimize(session);
//                    command.setLimit(limit);
//                    if (!readIf("ROW")) {
//                        read("ROWS");
//                    }
//                }
//                read("ONLY");
//            }
//
//            currentSelect = temp;
//        }
//        if (readIf("LIMIT")) {
//            Select temp = currentSelect;
//            // make sure aggregate functions will not work here
//            currentSelect = null;
//            Expression limit = readExpression().optimize(session);
//            command.setLimit(limit);
//            if (readIf("OFFSET")) {
//                Expression offset = readExpression().optimize(session);
//                command.setOffset(offset);
//            } else if (readIf(",")) {
//                // MySQL: [offset, ] rowcount
//                Expression offset = limit;
//                limit = readExpression().optimize(session);
//                command.setOffset(offset);
//                command.setLimit(limit);
//            }
//            if (readIf("SAMPLE_SIZE")) {
//                command.setSampleSize(getPositiveInt());
//            }
//            currentSelect = temp;
//        }
//        if (readIf("FOR")) {
//            if (readIf("UPDATE")) {
//                if (readIf("OF")) {
//                    do {
//                        readIdentifierWithSchema();
//                    } while (readIf(","));
//                } else if (readIf("NOWAIT")) {
//                    // TOxDO parser: select for update nowait: should not wait
//                } else if (readIf("WITH")) {
//                    // Hibernate / Derby support
//                    read("RR");
//                }
//                command.setForUpdate(true);
//            } else if (readIf("READ")) {
//                read("ONLY");
//                if (readIf("WITH")) {
//                    read("RS");
//                }
//            }
//        }
    }

    private Query parseSelectSub() throws SQLException {
        if (readIf("(")) {
           Query command = parseSelectUnion();
            read(")");
            return command;
        }
       Select select = parseSelectSimple();
       return select;
    }

    private void parseSelectSimpleFromPart(Select command) throws SQLException {
        do {
            TableFilter filter = readTableFilter(false);
            parseJoinTableFilter(filter, command);
        } while (readIf(","));
    }

    private void parseJoinTableFilter(TableFilter top, Select command) throws SQLException {
        top = readJoin(top, command, top.isJoinOuter());
        command.addTableFilter(top, true);
        boolean isOuter = false;
        while (true) {
            TableFilter join = top.getJoin();
            if (join == null) {
                break;
            }
            isOuter = isOuter | join.isJoinOuter();
            if (isOuter) {
                command.addTableFilter(join, false);
            } else {
                // make flat so the optimizer can work better
                //Expression on = join.getJoinCondition();
                //if (on != null) {
                //    command.addCondition(on);
                //}
                //join.removeJoinCondition();
                // top.removeJoin();
                command.addTableFilter(join, true);
            }
            top = join;
        }
    }

    private void parseSelectSimpleSelectPart(Select command) throws SQLException {
        Select temp = currentSelect;
        // make sure aggregate functions will not work in TOP and LIMIT
        currentSelect = null;
        if (readIf("TOP")) {
            // can't read more complex expressions here because
            // SELECT TOP 1 +? A FROM TEST could mean
            // SELECT TOP (1+?) A FROM TEST or
            // SELECT TOP 1 (+?) AS A FROM TEST
           // Expression limit = readTerm().optimize(session);
           // command.setLimit(limit);
        } else if (readIf("LIMIT")) {
           // Expression offset = readTerm().optimize(session);
           // command.setOffset(offset);
           // Expression limit = readTerm().optimize(session);
           // command.setLimit(limit);
        }
        currentSelect = temp;
        if (readIf("DISTINCT")) {
           // command.setDistinct(true);
        } else {
            readIf("ALL");
        }
        ObjectArray<Expression> expressions = ObjectArray.newInstance();
        do {
            if (readIf("*")) {
          //      expressions.add(new Wildcard(null, null));
            } else {
                Expression expr = readExpression();
                if (readIf("AS") || currentTokenType == IDENTIFIER) {
                    String alias = readAliasIdentifier();
          //          expr = new Alias(expr, alias, database.getMode().aliasColumnName);
                }
                expressions.add(expr);
            }
        } while (readIf(","));
        command.setExpressions(expressions);
    }

    private Select parseSelectSimple() throws SQLException {
        boolean fromFirst;
        if (readIf("SELECT")) {
            fromFirst = false;
        } else if (readIf("FROM")) {
            fromFirst = true;
        } else {
            throw getSyntaxError();
        }

        Select command = new Select(session);
        int start = lastParseIndex;
        Select oldSelect = currentSelect;
        currentSelect = command;
        currentPrepared = command;
        if (fromFirst) {
            parseSelectSimpleFromPart(command);
            read("SELECT");
            parseSelectSimpleSelectPart(command);
        } else {
            parseSelectSimpleSelectPart(command);
            if (!readIf("FROM")) {
                // select without FROM: convert to SELECT ... FROM
                // SYSTEM_RANGE(1,1)
                Table dual = getDualTable();
                TableFilter filter = new TableFilter(session, dual, null, currentSelect);
                command.addTableFilter(filter, true);
            } else {
              //  Window.alert("parseSelectSimple D");
                parseSelectSimpleFromPart(command);
              //  Window.alert("parseSelectSimple E");
            }
        }
     //   Window.alert("parseSelectSimple F" + command);
        if (readIf("WHERE")) {
            Expression condition = readExpression();
            command.addCondition(condition);
        }
     //   Window.alert("parseSelectSimple G" + command);
        // the group by is read for the outer select (or not a select)
        // so that columns that are not grouped can be used
        currentSelect = oldSelect;
        if (readIf("GROUP")) {
            read("BY");
            ObjectArray<Expression> list = ObjectArray.newInstance();
            do {
                Expression expr = readExpression();
                list.add(expr);
            } while (readIf(","));
            command.setGroupBy(list);
        }
        currentSelect = command;
        if (readIf("HAVING")) {
            Expression condition = readExpression();
            command.setHaving(condition);
        }
        // TODO 97 command.setParameterList(parameters);
        currentSelect = oldSelect;
        setSQL(command, "SELECT", start);
        return command;
    }

    private Table getDualTable() throws SQLException {
        return null;
//        Schema main = database.findSchema(Constants.SCHEMA_MAIN);
//        Expression one = ValueExpression.get(ValueLong.get(1));
//        return new RangeTable(main, one, one);
    }

    private void setSQL(Prepared command, String start, int startIndex) {
        String sql = originalSQL.substring(startIndex, lastParseIndex).trim();
        if (start != null) {
            sql = start + " " + sql;
        }
        //command.setSQL(sql);
    }

    private Expression readExpression() throws SQLException {
        Expression r = readAnd();
        while (readIf("OR")) {
            r = new ConditionAndOr(session, ConditionAndOr.OR, r, readAnd());
        }
        return r;
    }

    private Expression readAnd() throws SQLException {
        Expression r = readCondition();
        while (readIf("AND")) {
            r = new ConditionAndOr(session, ConditionAndOr.AND, r, readCondition());
        }
        return r;
    }

    private Expression readCondition() throws SQLException {
//        // TOxDO parser: should probably use switch case for performance
//        if (readIf("NOT")) {
//            return new ConditionNot(readCondition());
//        }
//        if (readIf("EXISTS")) {
//            read("(");
//            Query query = parseSelect();
//            // can not reduce expression because it might be a union except
//            // query with distinct
//            read(")");
//            return new ConditionExists(query);
//        }
        Expression r = readConcat();
        while (true) {
//            // special case: NOT NULL is not part of an expression (as in CREATE
//            // TABLE TEST(ID INT DEFAULT 0 NOT NULL))
//            int backup = parseIndex;
            boolean not = false;
            if (readIf("NOT")) {
                not = true;
//                if (isToken("NULL")) {
//                    // this really only works for NOT NULL!
//                    parseIndex = backup;
//                    currentToken = "NOT";
//                    break;
//                }
            }
            Operator op = readCustom(not);
            if (op != null) {
                Expression b;
                switch (op.getCardinality()) {
                case ZERO: {
                    b = null;
                    break;
                }
                case ONE: {
                    b = readConcat();
                    break;
                }
                case MULTI: {
                    read("(");
                  Collection<Expression> v = new ArrayList<Expression>();
                  Expression last;
                  do {
                      last = readExpression();
                      v.add(last);
                  } while (readIf(","));
                  read(")");
                  b = new Parameter(session, v);
                  break;
                }
                default:
                    throw new IllegalArgumentException("Can't handle "
                            + op.getCardinality());
                }

                Expression esc = null;
                if (readIf("ESCAPE")) {
                    esc = readConcat();
                }
                recompileAlways = true;
                r = new Comparison(session, op.getName(), r, b);
            } //else if (readIf("REGEXP")) {
//                Expression b = readConcat();
//                r = new CompareLike(database.getCompareMode(), r, b, null, true);
           // } else
              if (readIf("IS")) {
                String type;
                if (readIf("NOT")) {
                    type = Operator.IS_NOT_NULL;
                } else {
                    type = Operator.IS_NULL;
                }
                read("NULL");
                r = new Comparison(session, type, r, null);
//            } else if (readIf("IN")) {
//                if (SysProperties.OPTIMIZE_IN && !SysProperties.OPTIMIZE_IN_LIST) {
//                    recompileAlways = true;
//                }
//                read("(");
//                if (readIf(")")) {
//                    r = ValueExpression.get(ValueBoolean.get(false));
//                } else {
//                    if (isToken("SELECT") || isToken("FROM")) {
//                        Query query = parseSelect();
//                        r = new ConditionInSelect(database, r, query, false, Comparison.EQUAL);
//                    } else {
//                        ObjectArray<Expression> v = ObjectArray.newInstance();
//                        Expression last;
//                        do {
//                            last = readExpression();
//                            v.add(last);
//                        } while (readIf(","));
//                        if (v.size() == 1 && (last instanceof Subquery)) {
//                            Subquery s = (Subquery) last;
//                            Query q = s.getQuery();
//                            r = new ConditionInSelect(database, r, q, false, Comparison.EQUAL);
//                        } else {
//                            r = new ConditionIn(database, r, v);
//                        }
//                    }
//                    read(")");
//                }
//            } else if (readIf("BETWEEN")) {
//                Expression low = readConcat();
//                read("AND");
//                Expression high = readConcat();
//                Expression condLow = new Comparison(session, Comparison.SMALLER_EQUAL, low, r);
//                Expression condHigh = new Comparison(session, Comparison.BIGGER_EQUAL, high, r);
//                r = new ConditionAndOr(ConditionAndOr.AND, condLow, condHigh);
            } else {
                String compareType = getCompareType(currentTokenType);
                if (compareType == null) {
                    break;
                }
                read();
//                if (readIf("ALL")) {
//                    read("(");
//                    Query query = parseSelect();
//                    r = new ConditionInSelect(database, r, query, true, compareType);
//                    read(")");
//                } else if (readIf("ANY") || readIf("SOME")) {
//                    read("(");
//                    Query query = parseSelect();
//                    r = new ConditionInSelect(database, r, query, false, compareType);
//                    read(")");
//                } else {
                    Expression right = readConcat();
//                    if (readIf("(") && readIf("+") && readIf(")")) {
//                        // support for a subset of old-fashioned Oracle outer
//                        // join with (+)
//                        if (r instanceof ExpressionColumn && right instanceof ExpressionColumn) {
//                            ExpressionColumn leftCol = (ExpressionColumn) r;
//                            ExpressionColumn rightCol = (ExpressionColumn) right;
//                            ObjectArray<TableFilter> filters = currentSelect.getTopFilters();
//                            for (TableFilter f : filters) {
//                                while (f != null) {
//                                    leftCol.mapColumns(f, 0);
//                                    rightCol.mapColumns(f, 0);
//                                    f = f.getJoin();
//                                }
//                            }
//                            TableFilter leftFilter = leftCol.getTableFilter();
//                            TableFilter rightFilter = rightCol.getTableFilter();
//                            r = new Comparison(session, compareType, r, right);
//                            if (leftFilter != null && rightFilter != null) {
//                                int idx = filters.indexOf(rightFilter);
//                                if (idx >= 0) {
//                                    filters.remove(idx);
//                                    leftFilter.addJoin(rightFilter, true, r);
//                                } else {
//                                    rightFilter.mapAndAddFilter(r);
//                                }
//                                r = ValueExpression.get(ValueBoolean.get(true));
//                            }
//                        }
//                    } else {
                        r = new Comparison(session, compareType, r, right);
//                    }
//                }

            //if (not) {
              //  r = new ConditionNot(session, r);
            //}
            }
        }
        return r;
    }

    private Expression readConcat() throws SQLException {
        Expression r = readSum();
        return r;
//        while (true) {
//            if (readIf("||")) {
//                r = new Operation(Operation.CONCAT, r, readSum());
//            } else if (readIf("~")) {
//                if (readIf("*")) {
//                    Function function = Function.getFunction(database, "CAST");
//                    function.setDataType(new Column("X", Value.STRING_IGNORECASE));
//                    function.setParameter(0, r);
//                    r = function;
//                }
//                r = new CompareLike(database.getCompareMode(), r, readSum(), null, true);
//            } else if (readIf("!~")) {
//                if (readIf("*")) {
//                    Function function = Function.getFunction(database, "CAST");
//                    function.setDataType(new Column("X", Value.STRING_IGNORECASE));
//                    function.setParameter(0, r);
//                    r = function;
//                }
//                r = new ConditionNot(new CompareLike(null, r, readSum(), null, true));
//            } else {
//                return r;
//            }
//        }
    }

    private Expression readSum() throws SQLException {
        Expression r = readFactor();
//        while (true) {
//            if (readIf("+")) {
//                r = new Operation(Operation.PLUS, r, readFactor());
//            } else if (readIf("-")) {
//                r = new Operation(Operation.MINUS, r, readFactor());
//            } else {
                return r;
//            }
//        }
    }

    private Expression readFactor() throws SQLException {
        Expression r = readTerm();
//        while (true) {
//            if (readIf("*")) {
//                r = new Operation(Operation.MULTIPLY, r, readTerm());
//            } else if (readIf("/")) {
//                r = new Operation(Operation.DIVIDE, r, readTerm());
//            } else {
                return r;
//            }
//        }
    }

    private Expression readAggregate(int aggregateType) throws SQLException {
        return null;
//        if (currentSelect == null) {
//            throw getSyntaxError();
//        }
//        currentSelect.setGroupQuery();
//        Expression r;
//        if (aggregateType == Aggregate.COUNT) {
//            if (readIf("*")) {
//                r = new Aggregate(Aggregate.COUNT_ALL, null, currentSelect, false);
//            } else {
//                boolean distinct = readIf("DISTINCT");
//                Expression on = readExpression();
//                if (on instanceof Wildcard && !distinct) {
//                    // PostgreSQL compatibility: count(t.*)
//                    r = new Aggregate(Aggregate.COUNT_ALL, null, currentSelect, false);
//                } else {
//                    r = new Aggregate(Aggregate.COUNT, on, currentSelect, distinct);
//                }
//            }
//        } else if (aggregateType == Aggregate.GROUP_CONCAT) {
//            boolean distinct = readIf("DISTINCT");
//            Aggregate agg = new Aggregate(Aggregate.GROUP_CONCAT, readExpression(), currentSelect, distinct);
//            if (readIf("ORDER")) {
//                read("BY");
//                agg.setOrder(parseSimpleOrderList());
//            }
//            if (readIf("SEPARATOR")) {
//                agg.setSeparator(readExpression());
//            }
//            r = agg;
//        } else {
//            boolean distinct = readIf("DISTINCT");
//            r = new Aggregate(aggregateType, readExpression(), currentSelect, distinct);
//        }
//        read(")");
//        return r;
    }

    private ObjectArray<Object> parseSimpleOrderList() throws SQLException {
//        ObjectArray<SelectOrderBy> orderList = ObjectArray.newInstance();
//        do {
//            SelectOrderBy order = new SelectOrderBy();
//            Expression expr = readExpression();
//            order.expression = expr;
//            if (readIf("DESC")) {
//                order.descending = true;
//            } else {
//                readIf("ASC");
//            }
//            orderList.add(order);
//        } while (readIf(","));
//        return orderList;
        return null;
    }

    private Expression readFunction(String name) throws SQLException {
        return null;
//        int agg = Aggregate.getAggregateType(name);
//        if (agg >= 0) {
//            return readAggregate(agg);
//        }
//        Function function = Function.getFunction(database, name);
//        if (function == null) {
//            UserAggregate aggregate = database.findAggregate(name);
//            if (aggregate != null) {
//                return readJavaAggregate(aggregate);
//            }
//            return readJavaFunction(name);
//        }
//        switch (function.getFunctionType()) {
//        case Function.CAST: {
//            function.setParameter(0, readExpression());
//            read("AS");
//            Column type = parseColumn(null);
//            function.setDataType(type);
//            read(")");
//            break;
//        }
//        case Function.CONVERT: {
//            function.setParameter(0, readExpression());
//            read(",");
//            Column type = parseColumn(null);
//            function.setDataType(type);
//            read(")");
//            break;
//        }
//        case Function.EXTRACT: {
//            function.setParameter(0, ValueExpression.get(ValueString.get(currentToken)));
//            read();
//            read("FROM");
//            function.setParameter(1, readExpression());
//            read(")");
//            break;
//        }
//        case Function.DATE_DIFF: {
//            if (Function.isDatePart(currentToken)) {
//                function.setParameter(0, ValueExpression.get(ValueString.get(currentToken)));
//                read();
//            } else {
//                function.setParameter(0, readExpression());
//            }
//            read(",");
//            function.setParameter(1, readExpression());
//            read(",");
//            function.setParameter(2, readExpression());
//            read(")");
//            break;
//        }
//        case Function.SUBSTRING: {
//            function.setParameter(0, readExpression());
//            if (!readIf(",")) {
//                read("FROM");
//            }
//            function.setParameter(1, readExpression());
//            if (readIf("FOR") || readIf(",")) {
//                function.setParameter(2, readExpression());
//            }
//            read(")");
//            break;
//        }
//        case Function.POSITION: {
//            // can't read expression because IN would be read too early
//            function.setParameter(0, readConcat());
//            if (!readIf(",")) {
//                read("IN");
//            }
//            function.setParameter(1, readExpression());
//            read(")");
//            break;
//        }
//        case Function.TRIM: {
//            Expression space = null;
//            if (readIf("LEADING")) {
//                function = Function.getFunction(database, "LTRIM");
//                if (!readIf("FROM")) {
//                    space = readExpression();
//                    read("FROM");
//                }
//            } else if (readIf("TRAILING")) {
//                function = Function.getFunction(database, "RTRIM");
//                if (!readIf("FROM")) {
//                    space = readExpression();
//                    read("FROM");
//                }
//            } else if (readIf("BOTH")) {
//                if (!readIf("FROM")) {
//                    space = readExpression();
//                    read("FROM");
//                }
//            }
//            Expression p0 = readExpression();
//            if (readIf(",")) {
//                space = readExpression();
//            } else if (readIf("FROM")) {
//                space = p0;
//                p0 = readExpression();
//            }
//            function.setParameter(0, p0);
//            if (space != null) {
//                function.setParameter(1, space);
//            }
//            read(")");
//            break;
//        }
//        case Function.TABLE:
//        case Function.TABLE_DISTINCT: {
//            int i = 0;
//            ObjectArray<Column> columns = ObjectArray.newInstance();
//            do {
//                String columnName = readAliasIdentifier();
//                Column column = parseColumn(columnName);
//                columns.add(column);
//                read("=");
//                function.setParameter(i, readExpression());
//                i++;
//            } while (readIf(","));
//            read(")");
//            TableFunction tf = (TableFunction) function;
//            tf.setColumns(columns);
//            break;
//        }
//        default:
//            if (!readIf(")")) {
//                int i = 0;
//                do {
//                    function.setParameter(i++, readExpression());
//                } while (readIf(","));
//                read(")");
//            }
//        }
//        function.doneWithParameters();
//        return function;
    }

    private Function readFunctionWithoutParameters(String name) throws SQLException {
//        if (readIf("(")) {
//            read(")");
//        }
//        Function function = Function.getFunction(database, name);
//        function.doneWithParameters();
//        return function;
        return null;
    }

    private Expression readWildcardOrSequenceValue(String schema, String objectName) throws SQLException {
//        if (readIf("*")) {
//            return new Wildcard(schema, objectName);
//        }
//        if (schema == null) {
//            schema = session.getCurrentSchemaName();
//        }
//        if (readIf("NEXTVAL")) {
//            Sequence sequence = findSequence(schema, objectName);
//            if (sequence != null) {
//                return new SequenceValue(sequence);
//            }
//        } else if (readIf("CURRVAL")) {
//            Sequence sequence = findSequence(schema, objectName);
//            if (sequence != null) {
//                Function function = Function.getFunction(database, "CURRVAL");
//                function.setParameter(0, ValueExpression.get(ValueString.get(sequence.getSchema().getName())));
//                function.setParameter(1, ValueExpression.get(ValueString.get(sequence.getName())));
//                function.doneWithParameters();
//                return function;
//            }
//        }
        return null;
    }

    private Expression readTermObjectDot(String objectName) throws SQLException {
//        Expression expr = readWildcardOrSequenceValue(null, objectName);
//        if (expr != null) {
//            return expr;
//        }
        String name = readColumnIdentifier();
//        if (readIf(".")) {
//            String schema = objectName;
//            objectName = name;
//            expr = readWildcardOrSequenceValue(schema, objectName);
//            if (expr != null) {
//                return expr;
//            }
//            name = readColumnIdentifier();
//            if (readIf(".")) {
//                String databaseName = schema;
//                if (!database.getShortName().equals(databaseName)) {
//                    throw Message.getSQLException(ErrorCode.DATABASE_NOT_FOUND_1, databaseName);
//                }
//                schema = objectName;
//                objectName = name;
//                expr = readWildcardOrSequenceValue(schema, objectName);
//                if (expr != null) {
//                    return expr;
//                }
//                name = readColumnIdentifier();
//                return new ExpressionColumn(database, schema, objectName, name);
//            }
//            return new ExpressionColumn(database, schema, objectName, name);
//        }
        return new ExpressionColumn(session, null, objectName, name);
    }

    private Expression readTerm() throws SQLException {
        Expression r = null;
        switch (currentTokenType) {
//        case AT:
//            read();
//            r = new Variable(session, readAliasIdentifier());
//            if (readIf(":=")) {
//                Expression value = readExpression();
//                Function function = Function.getFunction(database, "SET");
//                function.setParameter(0, r);
//                function.setParameter(1, value);
//                r = function;
//            }
//            break;
        case PARAMETER:
            // there must be no space between ? and the number
            boolean indexed = Character.isDigit(sqlCommandChars[parseIndex]);
            read();
            Parameter p;
//            if (indexed && currentTokenType == VALUE && currentValue.getType() == Value.INT) {
//                if (indexedParameterList == null) {
//                    if (parameters == null) {
//                        // this can occur when parsing expressions only (for example check constraints)
//                        throw getSyntaxError();
//                    } else if (parameters.size() > 0) {
//                        throw Message.getSQLException(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS);
//                    }
//                    indexedParameterList = ObjectArray.newInstance();
//                }
//                int index = 0;//currentValue.getInt() - 1;
//                if (index < 0 || index >= Constants.MAX_PARAMETER_INDEX) {
//                    throw Message.getInvalidValueException("" + index, "Parameter Index");
//                }
//                if (indexedParameterList.size() <= index) {
//                    indexedParameterList.setSize(index + 1);
//                }
//                p = indexedParameterList.get(index);
//                if (p == null) {
//                    p = new Parameter(index);
//                    indexedParameterList.set(index, p);
//                }
//                read();
//            } else {
//                if (indexedParameterList != null) {
//                    throw Message.getSQLException(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS);
//                }
                p = new Parameter(session, parameters.size());
//            }
            parameters.add(p);
            r = p;
            break;
//        case KEYWORD:
//            if (isToken("SELECT") || isToken("FROM")) {
//                Query query = parseSelect();
//                r = new Subquery(query);
//            } else {
//                throw getSyntaxError();
//            }
//            break;
        case IDENTIFIER:
            String name = currentToken;
//            if (currentTokenQuoted) {
//                read();
//                if (readIf("(")) {
//                    r = readFunction(name);
//                } else if (readIf(".")) {
//                    r = readTermObjectDot(name);
//                } else {
//                    r = new ExpressionColumn(database, null, null, name);
//                }
//            } else {
                read();
//                if ("X".equals(name) && currentTokenType == VALUE && currentValue.getType() == Value.STRING) {
//                    read();
//                 //   byte[] buffer = ByteUtils.convertStringToBytes(currentValue.getString());
//                    r = null;//ValueExpression.get(ValueBytes.getNoCopy(buffer));
//                } else
                  if (readIf(".")) {
                    r = readTermObjectDot(name);
//                } else if ("CASE".equals(name)) {
//                    // CASE must be processed before (,
//                    // otherwise CASE(3) would be a function call, which it is
//                    // not
//                    if (isToken("WHEN")) {
//                        r = readWhen(null);
//                    } else {
//                        Expression left = readExpression();
//                        r = readWhen(left);
//                    }
//                } else if (readIf("(")) {
//                    r = readFunction(name);
//                } else if ("CURRENT_USER".equals(name)) {
//                    r = readFunctionWithoutParameters("USER");
//                } else if ("CURRENT".equals(name)) {
//                    if (readIf("TIMESTAMP")) {
//                        r = readFunctionWithoutParameters("CURRENT_TIMESTAMP");
//                    } else if (readIf("TIME")) {
//                        r = readFunctionWithoutParameters("CURRENT_TIME");
//                    } else if (readIf("DATE")) {
//                        r = readFunctionWithoutParameters("CURRENT_DATE");
//                    } else {
//                        r = new ExpressionColumn(database, null, null, name);
//                    }
//                } else if ("NEXT".equals(name) && readIf("VALUE")) {
//                    read("FOR");
//                    Sequence sequence = readSequence();
//                    r = new SequenceValue(sequence);
//                } else if ("DATE".equals(name) && currentTokenType == VALUE && currentValue.getType() == Value.STRING) {
//                    String date = currentValue.getString();
//                    read();
//                    //r = ValueExpression.get(ValueDate.get(ValueDate.parseDate(date)));
//                } else if ("TIME".equals(name) && currentTokenType == VALUE && currentValue.getType() == Value.STRING) {
//                    String time = currentValue.getString();
//                    read();
//                   // r = ValueExpression.get(ValueTime.get(ValueTime.parseTime(time)));
//                } else if ("TIMESTAMP".equals(name) && currentTokenType == VALUE
//                        && currentValue.getType() == Value.STRING) {
//                    String timestamp = currentValue.getString();
//                    read();
//                    //r = ValueExpression.get(ValueTimestamp.getNoCopy(ValueTimestamp.parseTimestamp(timestamp)));
//                } else if ("E".equals(name) && currentTokenType == VALUE && currentValue.getType() == Value.STRING) {
//                    String text = currentValue.getString();
//                    read();
//                    r = ValueExpression.get(ValueString.get(text));
                } else {
                    r = new ExpressionColumn(session, null, null, name);
                }
//            }
            break;
//        case MINUS:
//            read();
//            if (currentTokenType == VALUE) {
//                //r = ValueExpression.get(currentValue.negate());
//                // convert Integer.MIN_VALUE to int (-Integer.MIN_VALUE needed
//                // to be a long)
//              //  if (r.getType() == Value.LONG && r.getValue(session).getLong() == Integer.MIN_VALUE) {
//               //     r = ValueExpression.get(ValueInt.get(Integer.MIN_VALUE));
//                //}
//                read();
//            } else {
//                r = new Operation(Operation.NEGATE, readTerm(), null);
//            }
//            break;
//        case PLUS:
//            read();
//            r = readTerm();
//            break;
        case OPEN:
            read();
            r = readExpression();
            if (readIf(",")) {
                ObjectArray<Expression> list = ObjectArray.newInstance();
                list.add(r);
                do {
                    r = readExpression();
                    list.add(r);
                } while (readIf(","));
                Expression[] array = new Expression[list.size()];
                list.toArray(array);
                // XXX r = new ExpressionList(array);
            }
            read(")");
            break;
//        case TRUE:
//            read();
//            r = ValueExpression.get(ValueBoolean.get(true));
//            break;
//        case FALSE:
//            read();
//            r = ValueExpression.get(ValueBoolean.get(false));
//            break;
//        case CURRENT_TIME:
//            read();
//            r = readFunctionWithoutParameters("CURRENT_TIME");
//            break;
//        case CURRENT_DATE:
//            read();
//            r = readFunctionWithoutParameters("CURRENT_DATE");
//            break;
//        case CURRENT_TIMESTAMP: {
//            Function function = Function.getFunction(database, "CURRENT_TIMESTAMP");
//            read();
//            if (readIf("(")) {
//                if (!readIf(")")) {
//                    function.setParameter(0, readExpression());
//                    read(")");
//                }
//            }
//            function.doneWithParameters();
//            r = function;
//            break;
//        }
//        case ROWNUM:
//            read();
//            if (readIf("(")) {
//                read(")");
//            }
//            r = new Rownum(currentSelect == null ? currentPrepared : currentSelect);
//            break;
        case NULL:
            read();
            r = new Null();
            break;
//        case VALUE:
//            r = ValueExpression.get(currentValue);
//            read();
//            break;
        default:
            throw getSyntaxError();
        }
//        if (readIf("[")) {
//            Function function = Function.getFunction(database, "ARRAY_GET");
//            function.setParameter(0, r);
//            r = readExpression();
//            r = new Operation(Operation.PLUS, r, null);//ValueExpression.get(ValueInt.get(1)));
//            function.setParameter(1, r);
//            r = function;
//            read("]");
//        }
//        if (readIf("::")) {
//            // PostgreSQL compatibility
//            Column col = parseColumn(null);
//            Function function = Function.getFunction(database, "CAST");
//            function.setDataType(col);
//            function.setParameter(0, r);
//            r = function;

        return r;
    }

    private Expression readWhen(Expression left) throws SQLException {
//        if (readIf("END")) {
//            readIf("CASE");
//            return ValueExpression.getNull();
//        }
//        if (readIf("ELSE")) {
//            Expression elsePart = readExpression();
//            read("END");
//            readIf("CASE");
//            return elsePart;
//        }
//        readIf("WHEN");
//        Expression when = readExpression();
//        if (left != null) {
//            when = new Comparison(session, Comparison.EQUAL, left, when);
//        }
//        read("THEN");
//        Expression then = readExpression();
//        Expression elsePart = readWhen(left);
//        Function function = Function.getFunction(session.getDatabase(), "CASEWHEN");
//        function.setParameter(0, when);
//        function.setParameter(1, then);
//        function.setParameter(2, elsePart);
//        function.doneWithParameters();
//        return function;
        return null;
    }

    private int getPositiveInt() throws SQLException {
        return 1;
//        int v = getInt();
//        if (v < 0) {
//            throw Message.getInvalidValueException("" + v, "positive integer");
//        }
//        return v;
    }

    private int getInt() throws SQLException {
        return 1;
//        boolean minus = false;
//        if (currentTokenType == MINUS) {
//            minus = true;
//            read();
//        } else if (currentTokenType == PLUS) {
//            read();
//        }
//        if (currentTokenType != VALUE || currentValue.getType() != Value.INT) {
//            throw Message.getSyntaxError(sqlCommand, parseIndex, "integer");
//        }
//        int i = 1;//currentValue.getInt();
//        read();
//        return minus ? -i : i;
    }

    private long readLong() throws SQLException {
        return 1;
//        boolean minus = false;
//        if (currentTokenType == MINUS) {
//            minus = true;
//            read();
//        }
//        if (currentTokenType != VALUE
//                || (currentValue.getType() != Value.INT && currentValue.getType() != Value.DECIMAL)) {
//            throw Message.getSyntaxError(sqlCommand, parseIndex, "long");
//        }
//        long i = 1;//currentValue.getLong();
//        read();
//        return minus ? -i : i;
    }

    private String readString() throws SQLException {
//        Expression expr = readExpression();
//        if (!(expr instanceof ValueExpression)) {
//            throw Message.getSyntaxError(sqlCommand, parseIndex, "string");
//        }
//        String s = expr.getValue(session).getString();
//        return s;
        return null;
    }

    private String readIdentifierWithSchema(String defaultSchemaName) throws SQLException {
        if (currentTokenType != IDENTIFIER) {
            throw Message.getSyntaxError(sqlCommand, parseIndex, "identifier");
        }
        String s = currentToken;
        read();
//        schemaName = defaultSchemaName;
//        if (readIf(".")) {
//            schemaName = s;
//            if (currentTokenType != IDENTIFIER) {
//                throw Message.getSyntaxError(sqlCommand, parseIndex, "identifier");
//            }
//            s = currentToken;
//            read();
//        }
//        if (".".equals(currentToken)) {
//            if (schemaName.equalsIgnoreCase(database.getShortName())) {
//                read(".");
//                schemaName = s;
//                if (currentTokenType != IDENTIFIER) {
//                    throw Message.getSyntaxError(sqlCommand, parseIndex, "identifier");
//                }
//                s = currentToken;
//                read();
//            }
//        }
        return s;
    }

    private String readIdentifierWithSchema() throws SQLException {
        return null; //readIdentifierWithSchema(session.getCurrentSchemaName());
    }

    private String readAliasIdentifier() throws SQLException {
        return readColumnIdentifier();
    }

    private String readUniqueIdentifier() throws SQLException {
        return readColumnIdentifier();
    }

    private String readColumnIdentifier() throws SQLException {
        if (currentTokenType != IDENTIFIER) {
            throw Message.getSyntaxError(sqlCommand, parseIndex, "identifier");
        }
        String s = currentToken;
        read();
        return s;
    }

    private void read(String expected) throws SQLException {
        if (!expected.equals(currentToken) || currentTokenQuoted) {
            throw Message.getSyntaxError(sqlCommand, parseIndex, expected);
        }
        read();
    }

    private Operator readCustom(boolean not) throws SQLException {
        String token = not ? "NOT " + currentToken : currentToken;
        Operator op = session.getDatabase().getOperatorByName(token);
        if (op != null) {
            read();
            return op;
        }
        return null;
    }

    private boolean readIf(String token) throws SQLException {
        if (token.equals(currentToken) && !currentTokenQuoted) {
            read();
            return true;
        }
        addExpected(token);
        return false;
    }

    private boolean isToken(String token) {
        boolean result = token.equals(currentToken) && !currentTokenQuoted;
        if (result) {
            return true;
        }
        addExpected(token);
        return false;
    }

    private void addExpected(String token) {
        if (expectedList != null) {
            expectedList.add(token);
        }
    }

    private void read() throws SQLException {
        currentTokenQuoted = false;
        if (expectedList != null) {
            expectedList.clear();
        }
        int[] types = characterTypes;
        lastParseIndex = parseIndex;
        int i = parseIndex;
        int type = types[i];
        while (type == 0) {
            type = types[++i];
        }
        int start = i;
        char[] chars = sqlCommandChars;
        char c = chars[i++];
        currentToken = "";
        switch (type) {
        case CHAR_NAME:
            while (true) {
                type = types[i];
                if (type != CHAR_NAME && type != CHAR_VALUE) {
                    break;
                }
                i++;
            }
            currentToken = sqlCommand.substring(start, i);
            currentTokenType = getTokenType(currentToken);
            parseIndex = i;
            return;
        case CHAR_QUOTED: {
            String result = null;
            while (true) {
                for (int begin = i;; i++) {
                    if (chars[i] == '\"') {
                        if (result == null) {
                            result = sqlCommand.substring(begin, i);
                        } else {
                            result += sqlCommand.substring(begin - 1, i);
                        }
                        break;
                    }
                }
                if (chars[++i] != '\"') {
                    break;
                }
                i++;
            }
            currentToken = result;
            parseIndex = i;
            currentTokenQuoted = true;
            currentTokenType = IDENTIFIER;
            return;
        }
        case CHAR_SPECIAL_2:
            if (types[i] == CHAR_SPECIAL_2) {
                i++;
            }
            currentToken = sqlCommand.substring(start, i);
            currentTokenType = getSpecialType(currentToken);
            parseIndex = i;
            return;
        case CHAR_SPECIAL_1:
            currentToken = sqlCommand.substring(start, i);
            currentTokenType = getSpecialType(currentToken);
            parseIndex = i;
            return;
        case CHAR_VALUE:
            if (c == '0' && chars[i] == 'X') {
                // hex number
                long number = 0;
                start += 2;
                i++;
                while (true) {
                    c = chars[i];
                    if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
                        checkLiterals(false);
                        currentValue = null; //ValueInt.get((int) number);
                        currentTokenType = VALUE;
                        currentToken = "0";
                        parseIndex = i;
                        return;
                    }
                    number = (number << 4) + c - (c >= 'A' ? ('A' - 0xa) : ('0'));
                    if (number > Integer.MAX_VALUE) {
                        readHexDecimal(start, i);
                        return;
                    }
                    i++;
                }
            }
            long number = c - '0';
            while (true) {
                c = chars[i];
                if (c < '0' || c > '9') {
                    if (c == '.') {
                        readDecimal(start, i);
                        break;
                    }
                    if (c == 'E') {
                        readDecimal(start, i);
                        break;
                    }
                    checkLiterals(false);
                    currentValue = null; //ValueInt.get((int) number);
                    currentTokenType = VALUE;
                    currentToken = "0";
                    parseIndex = i;
                    break;
                }
                number = number * 10 + (c - '0');
                if (number > Integer.MAX_VALUE) {
                    readDecimal(start, i);
                    break;
                }
                i++;
            }
            return;
        case CHAR_DECIMAL:
            if (types[i] != CHAR_VALUE) {
                currentTokenType = KEYWORD;
                currentToken = ".";
                parseIndex = i;
                return;
            }
            readDecimal(i - 1, i);
            return;
        case CHAR_STRING: {
            String result = null;
            while (true) {
                for (int begin = i;; i++) {
                    if (chars[i] == '\'') {
                        if (result == null) {
                            result = sqlCommand.substring(begin, i);
                        } else {
                            result += sqlCommand.substring(begin - 1, i);
                        }
                        break;
                    }
                }
                if (chars[++i] != '\'') {
                    break;
                }
                i++;
            }
            currentToken = "'";
            checkLiterals(true);
            currentValue = null; //ValueString.get(StringCache.getNew(result));
            parseIndex = i;
            currentTokenType = VALUE;
            return;
        }
        case CHAR_DOLLAR_QUOTED_STRING: {
            String result = null;
            int begin = i - 1;
            while (types[i] == CHAR_DOLLAR_QUOTED_STRING) {
                i++;
            }
            result = sqlCommand.substring(begin, i);
            currentToken = "'";
            checkLiterals(true);
            currentValue = null; //ValueString.get(StringCache.getNew(result));
            parseIndex = i;
            currentTokenType = VALUE;
            return;
        }
        case CHAR_END:
            currentToken = "";
            currentTokenType = END;
            parseIndex = i;
            return;
        default:
            throw getSyntaxError();
        }
    }

    private void checkLiterals(boolean text) throws SQLException {
//        if (!session.getAllowLiterals()) {
//            int allowed = database.getAllowLiterals();
//            if (allowed == Constants.ALLOW_LITERALS_NONE || (text && allowed != Constants.ALLOW_LITERALS_ALL)) {
//                throw Message.getSQLException(ErrorCode.LITERALS_ARE_NOT_ALLOWED);
//            }
//        }
    }

    private void readHexDecimal(int start, int i) throws SQLException {
        char[] chars = sqlCommandChars;
        char c;
        do {
            c = chars[++i];
        } while ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'));
        parseIndex = i;
        String sub = sqlCommand.substring(start, i);
        BigDecimal bd = new BigDecimal(new BigInteger(sub, 16));
        checkLiterals(false);
     //   currentValue = null;//ValueDecimal.get(bd);
        currentTokenType = VALUE;
    }

    private void readDecimal(int start, int i) throws SQLException {
        char[] chars = sqlCommandChars;
        int[] types = characterTypes;
        // go until the first non-number
        while (true) {
            int t = types[i];
            if (t != CHAR_DECIMAL && t != CHAR_VALUE) {
                break;
            }
            i++;
        }
        if (chars[i] == 'E') {
            i++;
            if (chars[i] == '+' || chars[i] == '-') {
                i++;
            }
            if (types[i] != CHAR_VALUE) {
                throw getSyntaxError();
            }
            while (types[++i] == CHAR_VALUE) {
                // go until the first non-number
            }
        }
        parseIndex = i;
        String sub = sqlCommand.substring(start, i);
        BigDecimal bd;
        try {
            bd = new BigDecimal(sub);
        } catch (NumberFormatException e) {
            throw Message.getSQLException(ErrorCode.DATA_CONVERSION_ERROR_1, e, sub);
        }
        checkLiterals(false);
        currentValue = null; //ValueDecimal.get(bd);
        currentTokenType = VALUE;
    }

    public Session getSession() {
        return null;
    }

    private void initialize(String sql) throws SQLException {
        if (sql == null) {
            sql = "";
        }
        originalSQL = sql;
        sqlCommand = sql;
        int len = sql.length() + 1;
        char[] command = new char[len];
        int[] types = new int[len];
        len--;
        sql.getChars(0, len, command, 0);
        boolean changed = false;
        command[len] = ' ';
        int startLoop = 0;
        int lastType = 0;
        for (int i = 0; i < len; i++) {
            char c = command[i];
            int type = 0;
            switch (c) {
            case '/':
                if (command[i + 1] == '*') {
                    // block comment
                    changed = true;
                    command[i] = ' ';
                    command[i + 1] = ' ';
                    startLoop = i;
                    i += 2;
                    checkRunOver(i, len, startLoop);
                    while (command[i] != '*' || command[i + 1] != '/') {
                        command[i++] = ' ';
                        checkRunOver(i, len, startLoop);
                    }
                    command[i] = ' ';
                    command[i + 1] = ' ';
                    i++;
                } else if (command[i + 1] == '/') {
                    // single line comment
                    changed = true;
                    startLoop = i;
                    while (true) {
                        c = command[i];
                        if (c == '\n' || c == '\r' || i >= len - 1) {
                            break;
                        }
                        command[i++] = ' ';
                        checkRunOver(i, len, startLoop);
                    }
                } else {
                    type = CHAR_SPECIAL_1;
                }
                break;
            case '-':
                if (command[i + 1] == '-') {
                    // single line comment
                    changed = true;
                    startLoop = i;
                    while (true) {
                        c = command[i];
                        if (c == '\n' || c == '\r' || i >= len - 1) {
                            break;
                        }
                        command[i++] = ' ';
                        checkRunOver(i, len, startLoop);
                    }
                } else {
                    type = CHAR_SPECIAL_1;
                }
                break;
            case '$':
                if (false && command[i + 1] == '$' && (i == 0 || command[i - 1] <= ' ')) {
                    // dollar quoted string
                    changed = true;
                    command[i] = ' ';
                    command[i + 1] = ' ';
                    startLoop = i;
                    i += 2;
                    checkRunOver(i, len, startLoop);
                    while (command[i] != '$' || command[i + 1] != '$') {
                        types[i++] = CHAR_DOLLAR_QUOTED_STRING;
                        checkRunOver(i, len, startLoop);
                    }
                    command[i] = ' ';
                    command[i + 1] = ' ';
                    i++;
                } else {
                    if (lastType == CHAR_NAME || lastType == CHAR_VALUE) {
                        // $ inside an identifier is supported
                        type = CHAR_NAME;
                    } else {
                        // but not at the start, to support PostgreSQL $1
                        type = CHAR_SPECIAL_1;
                    }
                }
                break;
            case '(':
            case ')':
            case '{':
            case '}':
            case '*':
            case ',':
            case ';':
            case '+':
            case '%':
            case '?':
            case '@':
            case ']':
                type = CHAR_SPECIAL_1;
                break;
            case '!':
            case '<':
            case '>':
            case '|':
            case '=':
            case ':':
            case '~':
                type = CHAR_SPECIAL_2;
                break;
            case '.':
                type = CHAR_DECIMAL;
                break;
            case '\'':
                type = types[i] = CHAR_STRING;
                startLoop = i;
                while (command[++i] != '\'') {
                    checkRunOver(i, len, startLoop);
                }
                break;
            case '[':
                if (false) {
                    // SQL Server alias for "
                    command[i] = '"';
                    changed = true;
                    type = types[i] = CHAR_QUOTED;
                    startLoop = i;
                    while (command[++i] != ']') {
                        checkRunOver(i, len, startLoop);
                    }
                    command[i] = '"';
                } else {
                    type = CHAR_SPECIAL_1;
                }
                break;
            case '`':
                // MySQL alias for ", but not case sensitive
                command[i] = '"';
                changed = true;
                type = types[i] = CHAR_QUOTED;
                startLoop = i;
                while (command[++i] != '`') {
                    checkRunOver(i, len, startLoop);
                    c = command[i];
                    command[i] = Character.toUpperCase(c);
                }
                command[i] = '"';
                break;
            case '\"':
                type = types[i] = CHAR_QUOTED;
                startLoop = i;
                while (command[++i] != '\"') {
                    checkRunOver(i, len, startLoop);
                }
                break;
            case '_':
                type = CHAR_NAME;
                break;
            default:
                if (c >= 'a' && c <= 'z') {
                    command[i] = (char) (c - ('a' - 'A'));
                    changed = true;
                    type = CHAR_NAME;
                } else if (c >= 'A' && c <= 'Z') {
                    type = CHAR_NAME;
                } else if (c >= '0' && c <= '9') {
                    type = CHAR_VALUE;
                } else {
                    if (false) { // TODO 95 Character.isJavaIdentifierPart(c)) {
                        type = CHAR_NAME;
                        char u = Character.toUpperCase(c);
                        if (u != c) {
                            command[i] = u;
                            changed = true;
                        }
                    }
                }
            }
            types[i] = type;
            lastType = type;
        }
        sqlCommandChars = command;
        types[len] = CHAR_END;
        characterTypes = types;
        if (changed) {
            sqlCommand = new String(command);
        }
        parseIndex = 0;
    }

    private void checkRunOver(int i, int len, int startLoop) throws SQLException {
        if (i >= len) {
            parseIndex = startLoop;
            throw getSyntaxError();
        }
    }

    private int getSpecialType(String s) throws SQLException {
        char c0 = s.charAt(0);
        if (s.length() == 1) {
            switch (c0) {
            case '?':
            case '$':
                return PARAMETER;
            case '@':
                return AT;
            case '+':
                return PLUS;
            case '-':
                return MINUS;
            case '{':
            case '}':
            case '*':
            case '/':
            case ';':
            case ',':
            case ':':
            case '[':
            case ']':
            case '~':
                return KEYWORD;
            case '(':
                return OPEN;
            case ')':
                return CLOSE;
            case '<':
                return SMALLER;
            case '>':
                return BIGGER;
            case '=':
                return EQUAL;
            default:
                break;
            }
        } else if (s.length() == 2) {
            switch (c0) {
            case ':':
                if ("::".equals(s)) {
                    return KEYWORD;
                } else if (":=".equals(s)) {
                    return KEYWORD;
                }
                break;
            case '>':
                if (">=".equals(s)) {
                    return BIGGER_EQUAL;
                }
                break;
            case '<':
                if ("<=".equals(s)) {
                    return SMALLER_EQUAL;
                } else if ("<>".equals(s)) {
                    return NOT_EQUAL;
                }
                break;
            case '!':
                if ("!=".equals(s)) {
                    return NOT_EQUAL;
                } else if ("!~".equals(s)) {
                    return KEYWORD;
                }
                break;
            case '|':
                if ("||".equals(s)) {
                    return STRING_CONCAT;
                }
                break;
            }
        }
        throw getSyntaxError();
    }

    private int getTokenType(String s) throws SQLException {
        int len = s.length();
        if (len == 0) {
            throw getSyntaxError();
        }
        return getSaveTokenType(s, false); //database.getMode().supportOffsetFetch);
    }

    /**
     * Checks if this string is a SQL keyword.
     *
     * @param s the token to check
     * @param supportOffsetFetch if OFFSET and FETCH are keywords
     * @return true if it is a keyword
     */
    public static boolean isKeyword(String s, boolean supportOffsetFetch) {
        if (s == null || s.length() == 0) {
            return false;
        }
        return getSaveTokenType(s, supportOffsetFetch) != IDENTIFIER;
    }

    private static int getSaveTokenType(String s, boolean supportOffsetFetch) {
        switch (s.charAt(0)) {
        case 'C':
            if (s.equals("CURRENT_TIMESTAMP")) {
                return CURRENT_TIMESTAMP;
            } else if (s.equals("CURRENT_TIME")) {
                return CURRENT_TIME;
            } else if (s.equals("CURRENT_DATE")) {
                return CURRENT_DATE;
            }
            return getKeywordOrIdentifier(s, "CROSS", KEYWORD);
        case 'D':
            return getKeywordOrIdentifier(s, "DISTINCT", KEYWORD);
        case 'E':
            if ("EXCEPT".equals(s)) {
                return KEYWORD;
            }
            return getKeywordOrIdentifier(s, "EXISTS", KEYWORD);
        case 'F':
            if ("FROM".equals(s)) {
                return KEYWORD;
            } else if ("FOR".equals(s)) {
                return KEYWORD;
            } else if ("FULL".equals(s)) {
                return KEYWORD;
            } else if (supportOffsetFetch && "FETCH".equals(s)) {
                return KEYWORD;
            }
            return getKeywordOrIdentifier(s, "FALSE", FALSE);
        case 'G':
            return getKeywordOrIdentifier(s, "GROUP", KEYWORD);
        case 'H':
            return getKeywordOrIdentifier(s, "HAVING", KEYWORD);
        case 'I':
            if ("INNER".equals(s)) {
                return KEYWORD;
            } else if ("INTERSECT".equals(s)) {
                return KEYWORD;
            }
            return getKeywordOrIdentifier(s, "IS", KEYWORD);
        case 'J':
            return getKeywordOrIdentifier(s, "JOIN", KEYWORD);
        case 'L':
            if ("LIMIT".equals(s)) {
                return KEYWORD;
            }
            return getKeywordOrIdentifier(s, "LIKE", KEYWORD);
        case 'M':
            return getKeywordOrIdentifier(s, "MINUS", KEYWORD);
        case 'N':
            if ("NOT".equals(s)) {
                return KEYWORD;
            } else if ("NATURAL".equals(s)) {
                return KEYWORD;
            }
            return getKeywordOrIdentifier(s, "NULL", NULL);
        case 'O':
            if ("ON".equals(s)) {
                return KEYWORD;
            } else if (supportOffsetFetch && "OFFSET".equals(s)) {
                return KEYWORD;
            }
            return getKeywordOrIdentifier(s, "ORDER", KEYWORD);
        case 'P':
            return getKeywordOrIdentifier(s, "PRIMARY", KEYWORD);
        case 'R':
            return getKeywordOrIdentifier(s, "ROWNUM", ROWNUM);
        case 'S':
            if (s.equals("SYSTIMESTAMP")) {
                return CURRENT_TIMESTAMP;
            } else if (s.equals("SYSTIME")) {
                return CURRENT_TIME;
            } else if (s.equals("SYSDATE")) {
                return CURRENT_TIMESTAMP;
            }
            return getKeywordOrIdentifier(s, "SELECT", KEYWORD);
        case 'T':
            if ("TODAY".equals(s)) {
                return CURRENT_DATE;
            }
            return getKeywordOrIdentifier(s, "TRUE", TRUE);
        case 'U':
            if ("UNIQUE".equals(s)) {
                return KEYWORD;
            }
            return getKeywordOrIdentifier(s, "UNION", KEYWORD);
        case 'W':
            return getKeywordOrIdentifier(s, "WHERE", KEYWORD);
        default:
            return IDENTIFIER;
        }
    }

    private static int getKeywordOrIdentifier(String s1, String s2, int keywordType) {
        if (s1.equals(s2)) {
            return keywordType;
        }
        return IDENTIFIER;
    }

    private Column parseColumnForTable(String columnName) throws SQLException {
       return null;
    }

    private void parseAutoIncrement(Column column) throws SQLException {
        long start = 1, increment = 1;
        if (readIf("(")) {
            start = readLong();
            if (readIf(",")) {
                increment = readLong();
            }
            read(")");
        }
      //  column.setAutoIncrement(true, start, increment);
    }

    private String readCommentIf() throws SQLException {
        if (readIf("COMMENT")) {
            readIf("IS");
            return readString();
        }
        return null;
    }

    private Column parseColumn(String columnName) throws SQLException {
        return null;
    }

    private Prepared parseCreate() throws SQLException {
       return null;
    }

    private boolean addRoleOrRight(int command) throws SQLException {
       return false;
    }

    private Prepared parseGrantRevoke(int operationType) throws SQLException {
        return null;
    }

    private Prepared parserCall() throws SQLException {
//        Call command = new Call(session);
//        currentPrepared = command;
//        command.setValue(readExpression());
//        return command;
        return null;
    }

    private Prepared parseCreateRole() throws SQLException {
        return null;
    }

    private Prepared parseCreateSchema() throws SQLException {
        return null;
    }

    private Prepared parseCreateSequence() throws SQLException {
        return null;
    }

    private boolean readIfNoExists() throws SQLException {
        if (readIf("IF")) {
            read("NOT");
            read("EXISTS");
            return true;
        }
        return false;
    }

    private Prepared parseCreateConstant() throws SQLException {
        return null;
    }

    private Prepared parseCreateAggregate(boolean force) throws SQLException {
//        boolean ifNotExists = readIfNoExists();
//        CreateAggregate command = new CreateAggregate(session);
//        command.setForce(force);
//        String name = readUniqueIdentifier();
//        if (isKeyword(name, false) || Function.getFunction(database, name) != null || Aggregate.getAggregateType(name) >= 0) {
//            throw Message.getSQLException(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name);
//        }
//        command.setName(name);
//        command.setIfNotExists(ifNotExists);
//        read("FOR");
//        command.setJavaClassMethod(readUniqueIdentifier());
//        return command;
        return null;
    }

    private Prepared parseCreateUserDataType() throws SQLException {
        return null;
    }

    private Prepared parseCreateTrigger(boolean force) throws SQLException {
       return null;
    }

    private Prepared parseCreateUser() throws SQLException {
        return null;
    }

    private Prepared parseCreateFunctionAlias(boolean force) throws SQLException {
      return null;
    }

    private Prepared parserWith() throws SQLException {
        return null;
    }

    private Prepared parseCreateView(boolean force) throws SQLException {
        return null;
    }

    private Prepared parseCheckpoint() throws SQLException {
        return null;
    }

    private Prepared parseAlter() throws SQLException {
        if (readIf("TABLE")) {
            return parseAlterTable();
        } else if (readIf("USER")) {
            return parseAlterUser();
        } else if (readIf("INDEX")) {
            return parseAlterIndex();
        } else if (readIf("SEQUENCE")) {
            return parseAlterSequence();
        } else if (readIf("VIEW")) {
            return parseAlterView();
        }
        throw getSyntaxError();
    }

    private void checkSchema(Schema old) throws SQLException {
        if (old != null && getSchema() != old) {
            throw Message.getSQLException(ErrorCode.SCHEMA_NAME_MUST_MATCH);
        }
    }

    private Prepared parseAlterIndex() throws SQLException {
        return null;
//        String indexName = readIdentifierWithSchema();
//        Schema old = getSchema();
//        AlterIndexRename command = new AlterIndexRename(session);
//
//        read("RENAME");
//        read("TO");
//        String newName = readIdentifierWithSchema(old.getName());
//        checkSchema(old);
//        command.setNewName(newName);
//        return command;
    }

    private Prepared parseAlterView() throws SQLException {
//        AlterView command = new AlterView(session);
//        String viewName = readIdentifierWithSchema();
//        Table tableView = getSchema().findTableOrView(session, viewName);
//        if (!(tableView instanceof TableView)) {
//            throw Message.getSQLException(ErrorCode.VIEW_NOT_FOUND_1, viewName);
//        }
//        TableView view = (TableView) tableView;
//        command.setView(view);
//        read("RECOMPILE");
//        return command;
        return null;
    }

    private Prepared parseAlterSequence() throws SQLException {
        return null;
    }

    private Prepared parseAlterUser() throws SQLException {
//        String userName = readUniqueIdentifier();
//        if (readIf("SET")) {
//            AlterUser command = new AlterUser(session);
//            command.setType(AlterUser.SET_PASSWORD);
//            command.setUser(database.getUser(userName));
//            if (readIf("PASSWORD")) {
//                command.setPassword(readExpression());
//            } else if (readIf("SALT")) {
//                command.setSalt(readExpression());
//                read("HASH");
//                command.setHash(readExpression());
//            } else {
//                throw getSyntaxError();
//            }
//            return command;
//        } else if (readIf("RENAME")) {
//            read("TO");
//            AlterUser command = new AlterUser(session);
//            command.setType(AlterUser.RENAME);
//            command.setUser(database.getUser(userName));
//            String newName = readUniqueIdentifier();
//            command.setNewName(newName);
//            return command;
//        } else if (readIf("ADMIN")) {
//            AlterUser command = new AlterUser(session);
//            command.setType(AlterUser.ADMIN);
//            User user = database.getUser(userName);
//            command.setUser(user);
//            if (readIf("TRUE")) {
//                command.setAdmin(true);
//            } else if (readIf("FALSE")) {
//                command.setAdmin(false);
//            } else {
//                throw getSyntaxError();
//            }
//            return command;
//        }
//        throw getSyntaxError();
        return null;
    }

    private void readIfEqualOrTo() throws SQLException {
        if (!readIf("=")) {
            readIf("TO");
        }
    }

    private Prepared parseSet() throws SQLException {
        return null;
    }

    private Prepared parseSetCollation() throws SQLException {
        return null;


    }

    private Prepared parseRunScript() throws SQLException {
        return null;
    }

    private Prepared parseScript() throws SQLException {
        return null;
    }

    private Table readTableOrView() throws SQLException {
        return readTableOrView(readIdentifierWithSchema(null));
    }

    private Table readTableOrView(String tableName) throws SQLException {
//        // same algorithm than readSequence
//        if (schemaName != null) {
//            return getSchema().getTableOrView(session, tableName);
//        }
        Table table = database.getMainSchema(
                //session.getCurrentSchemaName()
                ).findTableOrView(tableName);
        if (table != null) {
            return table;
        }
//        throw new RuntimeException("Unable to find table " + tableName);
//        String[] schemaNames = session.getSchemaSearchPath();
//        for (int i = 0; schemaNames != null && i < schemaNames.length; i++) {
//            Schema s = database.getSchema(schemaNames[i]);
//            table = s.findTableOrView(session, tableName);
//            if (table != null) {
//                return table;
//            }
//        }
        throw new SQLException("Unable to find table " + tableName);
    }

    private Sequence findSequence(String schemaName, String sequenceName) throws SQLException {
//        Sequence sequence = database.getSchema(schemaName).findSequence(sequenceName);
//        if (sequence != null) {
//            return sequence;
//        }
//        String[] schemaNames = session.getSchemaSearchPath();
//        for (int i = 0; schemaNames != null && i < schemaNames.length; i++) {
//            Schema s = database.getSchema(schemaNames[i]);
//            sequence = s.findSequence(sequenceName);
//            if (sequence != null) {
//                return sequence;
//            }
//        }
        return null;
    }

    private Sequence readSequence() throws SQLException {
//        // same algorithm than readTableOrView
//        String sequenceName = readIdentifierWithSchema(null);
//        if (schemaName != null) {
//            return getSchema().getSequence(sequenceName);
//        }
//        Sequence sequence = findSequence(session.getCurrentSchemaName(), sequenceName);
//        if (sequence != null) {
//            return sequence;
//        }
        throw Message.getSQLException(ErrorCode.SEQUENCE_NOT_FOUND_1, "sequenceName");
    }

    private Prepared parseAlterTable() throws SQLException {
        Table table = readTableOrView();
        if (readIf("ADD")) {
            Prepared command = parseAlterTableAddConstraintIf(table.getName(), table.getSchema());
            if (command != null) {
                return command;
            }
            return parseAlterTableAddColumn(table);
        } else if (readIf("SET")) {
            return null;
        } else if (readIf("RENAME")) {
            read("TO");
            //String newName = readIdentifierWithSchema(table.getSchema().getName());
            //checkSchema(table.getSchema());
            return null;
        } else if (readIf("DROP")) {
            if (readIf("CONSTRAINT")) {
                return null;
            } else if (readIf("PRIMARY")) {
                read("KEY");

                return null;
            } else {
                readIf("COLUMN");
                return null;
            }
        } else if (readIf("ALTER")) {
            readIf("COLUMN");
            String columnName = readColumnIdentifier();
            Column column = null; //table.getColumn(columnName);
            if (readIf("RENAME")) {
                read("TO");
                return null;
            } else if (readIf("SET")) {
                if (readIf("DATA")) {
                    // Derby compatibility
                    read("TYPE");
                    Column newColumn = parseColumnForTable(columnName);
                    return null;
                }
                return null;
            } else if (readIf("RESTART")) {
                readIf("WITH");
                Expression start = readExpression();
                return null;
            } else if (readIf("SELECTIVITY")) {
                return null;
            } else {
                Column newColumn = parseColumnForTable(columnName);
                return null;
            }
        }
        throw getSyntaxError();
    }

    private Prepared parseAlterTableAddColumn(Table table) throws SQLException {
        return null;
    }

    private int parseAction() throws SQLException {
       return 0;
    }

    private Prepared parseAlterTableAddConstraintIf(String tableName, Schema schema) throws SQLException {
        String constraintName = null, comment = null;
        boolean ifNotExists = false;
        if (readIf("CONSTRAINT")) {
            ifNotExists = readIfNoExists();
            constraintName = readIdentifierWithSchema(schema.getName());
            checkSchema(schema);
            comment = readCommentIf();
        }
        if (readIf("PRIMARY")) {
            read("KEY");
            return null;
        } else if (false && (readIf("INDEX") || readIf("KEY"))) {
            // MySQL
            return null;
        }
        Prepared command;
        if (readIf("CHECK")) {
            command = null; //new AlterTableAddConstraint(session, schema, ifNotExists);
            //command.setType(AlterTableAddConstraint.CHECK);
            //command.setCheckExpression(readExpression());
        } else if (readIf("UNIQUE")) {
            readIf("KEY");
            readIf("INDEX");
            command = null; //new AlterTableAddConstraint(session, schema, ifNotExists);
            //command.setType(AlterTableAddConstraint.UNIQUE);
            if (!readIf("(")) {
                constraintName = readUniqueIdentifier();
                read("(");
            }
            //command.setIndexColumns(parseIndexColumnList());
            if (readIf("INDEX")) {
                String indexName = readIdentifierWithSchema();

            }
        } else if (readIf("FOREIGN")) {
            command = null;
            read("KEY");
            read("(");
            //command.setIndexColumns(parseIndexColumnList());
            if (readIf("INDEX")) {
                String indexName = readIdentifierWithSchema();

            }
            read("REFERENCES");
            parseReferences(command, schema, tableName);
        } else {
            if (constraintName != null) {
                throw getSyntaxError();
            }
            return null;
        }
        if (readIf("NOCHECK")) {
            //command.setCheckExisting(false);
        } else {
            readIf("CHECK");
            //command.setCheckExisting(true);
        }
        //command.setTableName(tableName);
        //command.setConstraintName(constraintName);
        //command.setComment(comment);
        return command;
    }

    private void parseReferences(Prepared command, Schema schema, String tableName) throws SQLException {
//        if (readIf("(")) {
//            command.setRefTableName(schema, tableName);
//            command.setRefIndexColumns(parseIndexColumnList());
//        } else {
//            String refTableName = readIdentifierWithSchema(schema.getName());
//            command.setRefTableName(getSchema(), refTableName);
//            if (readIf("(")) {
//                command.setRefIndexColumns(parseIndexColumnList());
//            }
//        }
//        if (readIf("INDEX")) {
//            String indexName = readIdentifierWithSchema();
//
//        }
//        while (readIf("ON")) {
//            if (readIf("DELETE")) {
//                command.setDeleteAction(parseAction());
//            } else {
//                read("UPDATE");
//                command.setUpdateAction(parseAction());
//            }
//        }
//        if (readIf("NOT")) {
//            read("DEFERRABLE");
//        } else {
//            readIf("DEFERRABLE");
//        }
    }

    private Prepared parseCreateLinkedTable(boolean temp, boolean globalTemp, boolean force) throws SQLException {
        return null;
    }

    private Prepared parseCreateTable(boolean temp, boolean globalTemp, boolean persistIndexes) throws SQLException {
        return null;
    }

    private String getCompareType(int tokenType) {
        switch (tokenType) {
        case EQUAL:
            return Operator.EQUAL;
        case BIGGER_EQUAL:
            return Operator.BIGGER_EQUAL;
        case BIGGER:
            return Operator.BIGGER;
        case SMALLER:
            return Operator.SMALLER;
        case SMALLER_EQUAL:
            return Operator.SMALLER_EQUAL;
        case NOT_EQUAL:
            return Operator.NOT_EQUAL;
        default:
            return null;
        }
    }

    public void setRightsChecked(boolean rightsChecked) {
        this.rightsChecked = rightsChecked;
    }

    /**
     * Parse a SQL code snippet that represents an expression.
     *
     * @param sql the code snippet
     * @return the expression object
     * @throws SQLException if the code snippet could not be parsed
     */
    public Expression parseExpression(String sql) throws SQLException {
        parameters = ObjectArray.newInstance();
        initialize(sql);
        read();
        return readExpression();
    }

}
TOP

Related Classes of com.redspr.redquerybuilder.core.client.command.Parser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.