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(session, 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(session, 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();
    }

} public class Prepared extends BaseSqlWidget {

    public Prepared(Session session) {
        super(session);
       //initWidget(new Label("Class=" + getClass().getName()));
    }
} public class Select extends Query implements ColumnResolver {

    private final Button add = new Button("Add condition");

    private final XWidget<Expression> xcondition = new XWidget<Expression>();

    private HashMap<Expression, Object> currentGroup;

    private int currentGroupRowId;

    private ObjectArray<Expression> expressions;
    private final ObjectArray<TableFilter> filters = ObjectArray.newInstance();
    // private ObjectArray<SelectOrderBy> orderList;
    private ObjectArray<Expression> group;
    private boolean[] groupByExpression;
    private int[] groupIndex;
    private Expression having;

    private boolean isQuickAggregateQuery;
    private final Session session;
    private int visibleColumnCount;

    private final VerticalPanel vp = new VerticalPanel();

    private final CommandListBox what;

    public Select(final Session session2) {
        super(session2);
        session = session2;
        session.setSelect(this);

        initWidget(vp);

        what = new CommandListBox(this);
        what.addStyleName("rqbWhat");  // XXX to be removed
        what.addStyleName("rqbFrom");
        what.setVisible(session.getConfig().getFrom().isVisible());
        vp.add(what);

        vp.add(add);
        add.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                addFirstCondition();
                fireDirty();
            }
        });
        List items = new ArrayList();
        CommandWithLabel prompt = new PromptCommand();
        what.setValue(prompt);
        items.add(prompt);

        // XXX 00 What is SQL 92 case sensitivity rule

        for (Table t : session.getDatabase().getMainSchema().getTables()) {
            if (!t.isHidden()) {
                items.add(new TableCommand(t));
            }
        }

        what.setAcceptableValues(items);

        vp.add(xcondition);
    }

    // XXX scope for testing
    public Comparison addFirstCondition() {
        Comparison c = new Comparison(session);
        addCondition(c);
        return c;
    }

    class TableCommand extends CommandWithLabel {
        private final Table table;
        TableCommand(Table t) {
            super(t);
            table = t;
        }
        @Override
        public void execute() {
            updateTable(table);
        }
    }

    class PromptCommand extends CommandWithLabel {
        PromptCommand() {
            super("Please select");
        }

        @Override
        public void execute() {
            updateTable(null);
        }
    }


    /**
     * Add a condition to the list of conditions.
     *
     * @param cond
     *            the condition to add
     */
    public void addCondition(Expression cond) {
        Expression condition = getCondition();
        if (condition == null) {
            condition = cond;
        } else {
            condition = new ConditionAndOr(session, ConditionAndOr.AND, cond, condition);
        }
        setCondition(condition);
    }

    private void setCondition(Expression condition) {
        xcondition.setValue(condition);
    }

    /**
     * Add a table to the query.
     *
     * @param filter
     *            the table to add
     * @param isTop
     *            if the table can be the first table in the query plan
     */
    public void addTableFilter(TableFilter filter, boolean isTop) {
        // Oracle doesn't check on duplicate aliases
        // String alias = filter.getAlias();
        // if(filterNames.contains(alias)) {
        // throw Message.getSQLException(
        // ErrorCode.DUPLICATE_TABLE_ALIAS, alias);
        // }
        // filterNames.add(alias);

        filters.add(filter);

    }

    public int getColumnCount() {
        return visibleColumnCount;
    }

    public Expression getCondition() {
        return xcondition.getValue();
    }

    public HashMap<Expression, Object> getCurrentGroup() {
        return currentGroup;
    }

    public int getCurrentGroupRowId() {
        return currentGroupRowId;
    }

    public ObjectArray<Expression> getExpressions() {
        return expressions;
    }

    public ObjectArray<TableFilter> getFilters() {
        return filters;
    }

    public String getSQL(List args) {

        StatementBuilder buff = new StatementBuilder("SELECT ");
        // if (distinct) {
        // buff.append("DISTINCT ");
        // }
        if (expressions != null) {
         for (int i = 0; i < expressions.size(); i++) {
         buff.appendExceptFirst(", ");
         buff.append(expressions.get(i).getSQL(args));
         }
        }
        buff.append("\nFROM ");
        TableFilter filter = null;
        if (filters.size() > 0) {
            filter = filters.get(0);
        }
        if (filter != null) {
            buff.resetCount();
            int i = 0;
            do {
                buff.appendExceptFirst("\n");
                buff.append(filter.getSQL(i++ > 0, args));
                filter = filter.getJoin();
            } while (filter != null);
        } else {
            buff.resetCount();
            int i = 0;
            for (TableFilter f : filters) {
                buff.appendExceptFirst("\n");
                buff.append(f.getSQL(i++ > 0, args));
            }
        }
        if (xcondition.getValue() != null) {
            buff.append("\nWHERE ").append(xcondition.getValue().getSQL(args));
        }
        // if (groupIndex != null) {
        // buff.append("\nGROUP BY ");
        // buff.resetCount();
        // for (int gi : groupIndex) {
        // Expression g = exprList[gi];
        // g = g.getNonAliasExpression();
        // buff.appendExceptFirst(", ");
        // buff.append(StringUtils.unEnclose(g.getSQL()));
        // }
        // }
        // if (group != null) {
        // buff.append("\nGROUP BY ");
        // buff.resetCount();
        // for (Expression g : group) {
        // buff.appendExceptFirst(", ");
        // buff.append(StringUtils.unEnclose(g.getSQL()));
        // }
        // }
        // if (having != null) {
        // // could be set in addGlobalCondition
        // // in this case the query is not run directly, just getPlanSQL is
        // // called
        // Expression h = having;
        // buff.append("\nHAVING ").append(StringUtils.unEnclose(h.getSQL()));
        // } else if (havingIndex >= 0) {
        // Expression h = exprList[havingIndex];
        // buff.append("\nHAVING ").append(StringUtils.unEnclose(h.getSQL()));
        // }
        // if (sort != null) {
        // buff.append("\nORDER BY ").append(sort.getSQL(exprList,
        // visibleColumnCount));
        // }
        // if (orderList != null) {
        // buff.append("\nORDER BY ");
        // buff.resetCount();
        // for (SelectOrderBy o : orderList) {
        // buff.appendExceptFirst(", ");
        // buff.append(StringUtils.unEnclose(o.getSQL()));
        // }
        // }
        // if (limitExpr != null) {
        // buff.append("\nLIMIT ").append(StringUtils.unEnclose(limitExpr.getSQL()));
        // if (offsetExpr != null) {
        // buff.append(" OFFSET ").append(StringUtils.unEnclose(offsetExpr.getSQL()));
        // }
        // }
        // if (isForUpdate) {
        // buff.append("\nFOR UPDATE");
        // }
        // if (isQuickAggregateQuery) {
        // buff.append("\n/* direct lookup */");
        // }
        // if (isDistinctQuery) {
        // buff.append("\n/* distinct */");
        // }
        // if (sortUsingIndex) {
        // buff.append("\n/* index sorted */");
        // }
        // if (isGroupQuery) {
        // if (isGroupSortedQuery) {
        // buff.append("\n/* group sorted */");
        // }
        // }
        return buff.toString();
    }

   // public ListBox getWhat() {
   //     return what;
   // }

    // public HashSet<Table> getTables() {
    // HashSet<Table> set = New.hashSet();
    // for (TableFilter filter : filters) {
    // set.add(filter.getTable());
    // }
    // return set;
    // }

    /**
     * Check if this is an aggregate query with direct lookup, for example a
     * query of the type SELECT COUNT(*) FROM TEST or SELECT MAX(ID) FROM TEST.
     *
     * @return true if a direct lookup is possible
     */
    public boolean isQuickAggregateQuery() {
        return isQuickAggregateQuery;
    }

    @Override
    public BaseSqlWidget remove(Expression e) {
        if (getCondition() == e) {
            setCondition(null);
            return this;
        }
        throw new IllegalArgumentException();
    }

    @Override
    public void replace(Expression e0, Expression e1) {
        if (getCondition() == e0) {
            setCondition(e1);
        }
    }

    public void setExpressions(ObjectArray<Expression> p) {
        this.expressions = p;
    }

    // public void mapColumns(ColumnResolver resolver, int level) throws
    // SQLException {
    // for (Expression e : expressions) {
    // e.mapColumns(resolver, level);
    // }
    // if (condition != null) {
    // condition.mapColumns(resolver, level);
    // }
    // }
    //
    // public void setEvaluatable(TableFilter tableFilter, boolean b) {
    // for (Expression e : expressions) {
    // e.setEvaluatable(tableFilter, b);
    // }
    // if (condition != null) {
    // condition.setEvaluatable(tableFilter, b);
    // }
    // }

    public void setGroupBy(ObjectArray<Expression> p) {
        this.group = p;
    }

    public void setHaving(Expression p) {
        this.having = p;
    }

    public void updateTable(Table tt) {
        if (tt == null) {
            filters.clear();
        } else if (getFilters().size() == 0) {
            TableFilter filter = new TableFilter(session, tt, TableFilter
                    .newAlias(), Select.this);
            addTableFilter(filter, true); // XXX really is true?
        } else {
            getFilters().get(0).setTable(tt);
        }
        expressions = null; // XXX 03 bit harsh?
        setCondition(null);

        session.getMsgBus().fireEvent(new TableEvent());
    }

    @Override
    public void onDirty() {
        garbageCollectFilters();

        add.setVisible(getCondition() == null && this.getFilters().size() > 0);

        if (getFilters().size() > 0) {
            what.setValue(new TableCommand(getFilters().get(0).getTable()));
        }
    }

    public void garbageCollectFilters() {
        if (filters.size() > 1) {
            // XXX identity?
            final Set<TableFilter> used = new HashSet<TableFilter>();
            used.add(null);
            Visitor counter = new VisitorBase() {
                @Override
                public void handle(BaseSqlWidget w) {
                    if (w instanceof ExpressionColumn) {
                        ExpressionColumn ec = (ExpressionColumn) w;
                        TableFilter tf = ec.getTableFilter();
                        if (used.add(tf)) {
                            if (tf.getJoinCondition() != null) {
                                tf.getJoinCondition().traverse(this);
                            }
                        }
                    }
                }
            };

            if (getCondition() != null) {
                getCondition().traverse(counter);
            }

            boolean removed;
            do {
                removed = false;
                TableFilter prev = filters.get(0);
                TableFilter x = prev.getJoin();
                while (x != null) {
                    if (!used.contains(x)) {
                        // look for used in all other filters?
                        int i = filters.indexOf(x);
                        if (i > 0) {
                            filters.remove(i);
                        }
                            // XXX why is this the case?
                            // just when comma seperated?
                        prev.setJoin(x.getJoin());
                        removed = true;
                    }
                    prev = x;
                    x = x.getJoin();
                }
            } while (removed);

        }
    }

    @Override
    public void acceptChildren(Visitor callback) {
        Expression condition = getCondition();
        if (condition != null) {
            condition.traverse(callback);
        }
    }

    @Override
    public Column resolveColumn(String alias, String columnName) {
        for (TableFilter tf : getFilters()) {
            if (alias == null || alias.equals(tf.getAlias())) {
               Column c = tf.getTable().getColumn(columnName);
               if (c != null) {
                   return c;
               }
               // XXX if alias != null should blowup?
               // XXX should blowup if find more than one?
            }

            //if (alias.equals(tf.getAlias())) {
            //    return tf.getTable().getColumn(columnName);
            //}
        }
        // XXX log? Window.alert("Unable to find " + alias + "." + columnName);
        return null;
    }

    public TableFilter getTableFilter(String alias) {
        if (alias == null) {
            return getFilters().get(0);
        }

        for (TableFilter tf : getFilters()) {
            if (alias.equals(tf.getAlias())) {
                return tf;
            }
        }
        return null;
    }

    // public void addGlobalCondition(Parameter param, int columnId, int
    // comparisonType) throws SQLException {
    // addParameter(param);
    // Expression col = expressions.get(columnId);
    // col = col.getNonAliasExpression();
    // Expression comp = new Comparison(session, comparisonType, col, param);
    // comp = comp.optimize(session);
    // boolean addToCondition = true;
    // if (isGroupQuery) {
    // addToCondition = false;
    // for (int i = 0; groupIndex != null && i < groupIndex.length; i++) {
    // if (groupIndex[i] == columnId) {
    // addToCondition = true;
    // break;
    // }
    // }
    // if (!addToCondition) {
    // if (havingIndex >= 0) {
    // having = expressions.get(havingIndex);
    // }
    // if (having == null) {
    // having = comp;
    // } else {
    // having = new ConditionAndOr(ConditionAndOr.AND, having, comp);
    // }
    // }
    // }
    // if (addToCondition) {
    // if (condition == null) {
    // condition = comp;
    // } else {
    // condition = new ConditionAndOr(ConditionAndOr.AND, condition, comp);
    // }
    // }
    // }

    // public String getFirstColumnAlias(Session s) {
    // if (SysProperties.CHECK) {
    // if (visibleColumnCount > 1) {
    // Message.throwInternalError("" + visibleColumnCount);
    // }
    // }
    // Expression expr = expressions.get(0);
    // if (expr instanceof Alias) {
    // return expr.getAlias();
    // }
    // Mode mode = s.getDatabase().getMode();
    // String name = s.getNextSystemIdentifier(getSQL());
    // expr = new Alias(expr, name, mode.aliasColumnName);
    // expressions.set(0, expr);
    // return expr.getAlias();
    // }
    //

} public class Session {

    private static IdentifierEscaper identifierEscaper = new IdentifierEscaper() {
        @Override
        public String quote(String id) {
            return "\"" + id + "\"";
        }
    };

  public void setIdentifierEscaper(IdentifierEscaper p) {
      identifierEscaper = p;
  }

  /**
   * Add double quotes around an identifier if required.
   *
   * @param s the identifier
   * @return the quoted identifier
   */
  public static String quoteIdentifier(String s) {
      return identifierEscaper.quote(s);
  }

    // XXX need commandBuilder and select?
    private CommandBuilder commandBuilder;

    private Select select;

    private Configuration config;

    private final ValueRegistry valueRegistry = new ValueRegistry();

    private final Database database;

    private final HandlerManager msgbus;


  // XXX 00 remove one of these constructors
  public Session(Configuration config2) {
      this(config2.getDatabase());
      this.config = config2;
  }

  @Deprecated
  public Session(Database database2) {
      database = database2;
      msgbus = new HandlerManager(this);
  }

    public Configuration getConfig() {
        return config;
    }

    public CommandBuilder getCommandBuilder() {
        return commandBuilder;
    }

    public void setCommandBuilder(CommandBuilder p) {
        commandBuilder = p;
    }

    public Database getDatabase() {
        return database;
    }


  public HandlerManager getMsgBus() {
      return msgbus;
  }



  public void setSelect(Select p) {
      select = p;
  }

  public Column resolveColumn(String alias, String columnName) {
      return select.resolveColumn(alias, columnName);
  }

  public ObjectArray<TableFilter> getFilters() {
      return select.getFilters();
  }

  public TableFilter getTableFilter(Table t) {
      for (TableFilter tf : select.getFilters()) {
          if (tf.getTable().equals(t)) {
              return tf;
          }
      }
      return null;
  }

  public TableFilter createTableFilter(Table t) {
      TableFilter tf = new TableFilter(this, t, TableFilter.newAlias(), select);
      select.addTableFilter(tf, true); // XXX really is true?
      return tf;
  }

  public TableFilter getOrCreateTableFilter(Table t) {
      TableFilter tf = getTableFilter(t);
      if (tf == null) {
          tf = createTableFilter(t);
      }
      return tf;
  }

  //public Table getTable(String alias) {
  //    return select.getTable(alias);
  //}

  public Table getRootTable() {
      // XXX maybe if no table then grab default?
      // or should select always have one table?
      // or ui shouldn't offer condition button till table?
      return select.getFilters().get(0).getTable();
  }

    public ValueRegistry getValueRegistry() {
        return valueRegistry;
    }
} public class TableEvent extends GwtEvent<TableEventHandler> {
    public static final Type<TableEventHandler> TYPE = new Type<TableEventHandler>();

    // XXX only for root table changing?
    public TableEvent() {
    }

    @Override
    public Type<TableEventHandler> getAssociatedType() {
      return TYPE;
    }

    @Override
    protected void dispatch(TableEventHandler handler) {
      handler.onTable(this);
    }
} public interface TableEventHandler extends EventHandler {
    void onTable(TableEvent e);
} public class Comparison extends Condition {

    private String compareType;
    private final XWidget<Expression> xleft = new XWidget<Expression>();
    private final XWidget<Expression> xright = new XWidget<Expression>();

    private final ListBox2<Operator> op = new ListBox2<Operator>();

    private final Button remove = new Button("-");

    public Comparison(Session session2) {
        super(session2);

        // XXX why do this defaulting here? Not just do it in ExpressionColumn?
        TableFilter tf = getSession().getFilters().get(0);
        Column col = null;
        for (Column col2 : tf.getTable().getColumns()) {
            if (!col2.isHidden()) {
                col = col2;
                break;
            }
        }
        if (col == null) {
            throw new RuntimeException("No visible column");
        }

        setLeft(new ExpressionColumn(getSession(), null, tf.getAlias(),
                col.getName()));
        setRight(new Parameter(getSession()));
        compareType = col.getType().getOperators()[0].getName();
        init();
    }

    public Comparison(Session session2, String compareType2,
            final Expression left2, final Expression right2) {
        super(session2);
        setLeft(left2);
        setRight(right2);
        compareType = compareType2;
        init();
    }

    private void init() {
        HorizontalPanel v = new HorizontalPanel();
        v.addStyleName("rqbComparison");
        v.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);

        v.add(xleft);

        v.add(op);
        op.addValueChangeHandler(new ValueChangeHandler<Operator>() {
            @Override
            public void onValueChange(ValueChangeEvent<Operator> event) {
                setOperator(op.getValue());
                fireDirty();
            }
        });

        v.add(xright);

        v.add(remove);
        remove.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                remove();
                fireDirty();
            }
        });

        Button add = new Button("+");

        v.add(add);
        add.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                add();
                fireDirty();
            }
        });

        initWidget(v);
    }

    public void setOperator(Operator p) {
        this.compareType = p.getName();

        Cardinality c = p.getCardinality();
        if (c == Cardinality.ZERO) {
            if (getRight() != null) {
                setRight(null);
            }
        } else {
            if (getRight() == null) {
                setRight(new Parameter(getSession()));
            }
        }
    }

    public String getOperatorName() {
        return compareType;
    }

    public Operator getOperator() {
        ExpressionColumn leftec = ((ExpressionColumn) getLeft());
        Column lc = leftec.getColumn();
        if (lc == null) {
            leftec.getColumn();
        }
        Type lt = lc.getType();
        Operator o = lt.getOperatorByName(compareType);
        if (o == null) {
            o = lt.getOperators()[0];
            compareType = o.getName();
        }
        return o;
    }

    @Override
    public void onDirty() {
        Column col = ((ExpressionColumn) getLeft()).getColumn();

        op.setValue(getOperator());
        op.setAcceptableValues(col.getType().getOperators());
    }

    @Override
    public String getSQL(List<Object> args) {
        Operator op = getOperator();
        switch (op.getCardinality()) {
        case ZERO:
            return "(" + getLeft().getSQL(args) + " " + op.getName() + ")";
        case ONE:
        case MULTI:
            return "(" + getLeft().getSQL(args) + " " + getOperatorName() + " "
                    + getRight().getSQL(args) + ")";
        default:
            throw new RuntimeException("Unknown cardinality "
                    + op.getCardinality());
        }
    }

    public Expression getLeft() {
        return xleft.getValue();
    }

    public void setLeft(Expression left) {
        xleft.setValue(left);
    }

    public Expression getRight() {
        return xright.getValue();
    }

    public void setRight(Expression right) {
        xright.setValue(right);
    }

    // XXX one side ask about the other...
    // XXX expression have getTypeInfo?
    public Expression getOther(Expression me) {
        if (getLeft() == me) {
            return getRight();
        } else if (getRight() == me) {
            return getLeft();
        } else {
            throw new RuntimeException("Unable to find other side");
        }
    }

    public BaseSqlWidget remove() {
        return getParentExpression().remove(this);
    }

    public void add() {
        ConditionAndOr andOr = new ConditionAndOr(getSession(), 0, new Nop(),
                new Nop());
        getParentExpression().replace(this, andOr);
        andOr.setLeft(this);

        andOr.setRight(new Comparison(getSession()));
    }

    @Override
    public void acceptChildren(Visitor callback) {
        if (xleft.getValue() != null) {
            xleft.getValue().traverse(callback);
        }
        if (xright.getValue() != null) {
            xright.getValue().traverse(callback);
        }
    }
} public class ConditionAndOr extends Condition {
    interface MyUiBinder extends UiBinder<Widget, ConditionAndOr> {
    }

    private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

    @UiField
    XWidget<Expression> left;

    @UiField
    XWidget<Expression> right;

    @UiField
    ListBox op;



    /**
     * The AND condition type as in ID=1 AND NAME='Hello'.
     */
    public static final int AND = 0;

    /**
     * The OR condition type as in ID=1 OR NAME='Hello'.
     */
    public static final int OR = 1;

    private int andOrType;

    public ConditionAndOr(Session session, int andOrType2, Expression left2, Expression right2) {
        super(session);
        initWidget(uiBinder.createAndBindUi(this));
        this.andOrType = andOrType2;
        setLeft(left2);
        setRight(right2);

        op.addItem("AND");
        op.addItem("OR");

        op.setSelectedIndex(andOrType2);


    }

    public Expression getLeft() {
        return left.getValue();
    }

    public void setLeft(Expression e) {
        left.setValue(e);
    }

    public Expression getRight() {
        return right.getValue();
    }

    public void setRight(Expression e) {
        right.setValue(e);
    }

    public String getString() {
        switch (andOrType) {
        case AND:
            return "AND";
        case OR:
            return "OR";
        default:
            throw new RuntimeException("andOrType=" + andOrType);
        }
    }

    @Override
    public String getSQL(List args) {
        String sql;
        switch (andOrType) {
        case AND:
            sql = getLeft().getSQL(args) + " AND " + getRight().getSQL(args);
            break;
        case OR:
            sql = getLeft().getSQL(args) + " OR " + getRight().getSQL(args);
            break;
        default:
            throw new RuntimeException("andOrType=" + andOrType);
        }
        return "(" + sql + ")";
    }

    @Override
    public BaseSqlWidget remove(Expression e) {
        BaseSqlWidget parent = getParentExpression();
        if (getLeft() == e) {
            parent.replace(this, getRight());
        } else if (getRight() == e) {
            parent.replace(this, getLeft());
        } else {
            throw new IllegalArgumentException();
        }
        return parent;
    }

    @Override
    public void replace(Expression a, Expression b) {
        if (getLeft() == a) {
            setLeft(b);
        } else if (getRight() == a) {
            setRight(b);
        }
    }

    @Override
    public void acceptChildren(Visitor callback) {
        getLeft().traverse(callback);
        getRight().traverse(callback);
    }

    @UiHandler("op")
    void handleChange(ChangeEvent e) {
        andOrType = op.getSelectedIndex();
        fireDirty();
    }

    @Override
    public int hook() {
        return (getLeft().hook() + getRight().hook()) / 2;
    }

    @Override
    public void onDirty() {
        Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
            @Override
            public boolean execute() {
                Element opOuter = op.getElement().getParentElement();
                Element outerDiv = opOuter.getParentElement();
                Element topDiv = outerDiv.getFirstChildElement();

                int middleOffset = (hook() - getAbsoluteTop()) - opOuter.getOffsetHeight() / 2;

                opOuter.getStyle().setTop(middleOffset, Unit.PX);

                int leftHook = getLeft().hook();
                int rightHook = getRight().hook();

                int borderWidth = 10;

                int h = rightHook - leftHook - borderWidth;
                int top = leftHook - getAbsoluteTop() - borderWidth / 2;

                topDiv.getStyle().setHeight(h, Unit.PX);
                topDiv.getStyle().setTop(top, Unit.PX);

                return false;
        }

        }, 30);
    }
} public class ExpressionColumn extends Expression implements TableEventHandler {
    private Database database;
    private final String schemaName;
    private String tableAlias;
    private String columnName;

    private final HorizontalPanel hp = new HorizontalPanel();

    public ExpressionColumn(Session session2, String schemaName2,
            String tableAlias2, String columnName2) {
        super(session2);
        this.schemaName = schemaName2;
        this.tableAlias = tableAlias2;
        this.columnName = columnName2;

        initWidget(hp);

        reg2 = getSession().getMsgBus().addHandler(TableEvent.TYPE, this);
    }

    public void selectConstraintRef(ConstraintReferential ref) {
        TableFilter targetTf = JoinHelper.getOrCreateFor(getSession(), ref);
        this.tableAlias = targetTf.getAlias();
        this.columnName = targetTf.getTable().getColumns().iterator().next()
                .getName();

    }

    public void updateColumn(String alias, Column col2) {
        this.tableAlias = alias;
        this.columnName = col2.getName();
    }

    private final HandlerRegistration reg2;

    @Override
    public void onUnload() {
        super.onUnload();
        if (reg2 != null) {
            reg2.removeHandler();
        }
    }

    public Column getColumn() {
        return getSession().resolveColumn(tableAlias, getColumnName());
    }

    public String getColumnName() {
        return columnName;
    }

    public String getQualifiedColumnName() {
        if (tableAlias == null) {
            return getColumnName();
        }
        return tableAlias + "." + getColumnName();
    }

    @Override
    public void onDirty() {
        hp.clear();

        TableFilter tf = null;
        for (TableFilter tf2 : getSession().getFilters()) {
            if (StringUtils.equals(tableAlias, tf2.getAlias())) {
                tf = tf2;
                break;
            } else if (tf == null) {
                tf = tf2;
            }
        }

        Object hotValue = getColumn();

        Command hotCommand = null;
        while (tf != null) {
            final CommandListBox ght = new CommandListBox(this);
            hp.insert(ght, 0);
            List<Command> items = new ArrayList();
//           hotCommand = new ClearCommand();
//            items.add(hotCommand);

            for (Constraint c : tf.getTable().getConstraints()) {
                if (c instanceof ConstraintReferential) {
                    ConstraintReferential cr = (ConstraintReferential) c;
                    if (!cr.isHidden()) {
                        Command command = new ConstraintCommand(cr);
                        if (cr == hotValue) {
                            hotCommand = command;
                        }
                        items.add(command);
                    }
                }
            }

            for (Column c : tf.getTable().getColumns()) {
                if (!c.isHidden()) {
                    Command command = new ColumnCommand(tf.getAlias(), c);
                    if (c == hotValue) {
                        hotCommand = command;
                    }
                    items.add(command);
                }
            }

            ght.setValue(hotCommand);
            ght.setAcceptableValues(items);

            JoinHelper thing = JoinHelper.getParent(tf);
            if (thing != null) {
                tf = thing.getParent();
                hotValue = thing.getConstraint();
            } else {
                tf = null;
            }
        }
    }

    @Override
    public String getSQL(List args) {
        String sql;
        // if (column != null) {
        // sql = column.getSQL();
        // } else {
        sql = Session.quoteIdentifier("" + getColumnName());
        // }
        if (tableAlias != null) {
            sql = Session.quoteIdentifier(tableAlias) + "." + sql;
        }
        // if (schemaName != null) {
        // sql = Parser.quoteIdentifier(schemaName) + "." + sql;
        // }
        return sql;
    }

    @Override
    public void onTable(TableEvent e) {
        // Window.alert("Changed table");
    }

    public TableFilter getTableFilter() {
        // return resolver == null ? null : resolver.getTableFilter();
        for (TableFilter tf2 : getSession().getFilters()) {

            if (StringUtils.equals(tableAlias, tf2.getAlias())) {
                return tf2;
            }
        }
        return null; // XXX or blowup?
    }

//    private class ClearCommand extends Command2 {
//        ClearCommand() {
//            super("Please select...");
//        }
//
//        @Override
//        public void execute() {
//            // TOxDO 00 a Select "PLease select" for column and goes bang. in getSql?
//            // really want this at all? Ever selected? Defaults to first column...
//            // A) Default to first column and get rid of this
//            // B) Don't default and make this work ok
//            updateColumn(null, null);
//        }
//    }

    private class ColumnCommand extends CommandWithLabel {
        private final String alias;
        private final Column column;

        ColumnCommand(String a, Column c) {
            super(c);
            assert (c != null);
            this.alias = a;
            this.column = c;
        }

        @Override
        public void execute() {
            updateColumn(alias, column);
        }
    }

    private class ConstraintCommand extends CommandWithLabel {
        private final ConstraintReferential constraintReferential;

        ConstraintCommand(ConstraintReferential constraintReferential2) {
            super(constraintReferential2);
            this.constraintReferential = constraintReferential2;
        }

        @Override
        public void execute() {
            selectConstraintRef(constraintReferential);
        }
    }
} public class Nop extends Expression {
    public Nop() {
        super(null);
        initWidget(new Label());
    }

    @Override
    public String getSQL(List args) {
        return "1=1";
    }
} public class JoinHelper {

    // private final Session session;

    private TableFilter parent;
    private final TableFilter child;
    private Constraint constraint;
    private final Expression joinCondition;

    // XXX not valid till isSimple called
    public TableFilter getParent() {
        return parent;
    }

    // XXX not valid till isSimple called
    public Constraint getConstraint() {
        return constraint;
    }

    public JoinHelper(TableFilter child2, Expression joinCondition2) {
        this.child = child2;
        this.joinCondition = joinCondition2;
    }

    private static boolean match(ConstraintReferential a,
            ConstraintReferential b) {
        if (match(a, b.getTable(), b.getColumns(), b.getRefTable(), b.getRefColumns())) {
            return true;
        }

        if (match(a, b.getRefTable(), b.getRefColumns(), b.getTable(), b.getColumns())) {
            return true;
        }
        return false;
    }

    private static boolean match(ConstraintReferential a, Table btl, Column[] bc, Table btr, Column[] bcr) {
        if (a.getTable() != btl) {
            return false;
        }
        if (a.getRefTable() != btr) {
            return false;
        }
        if (a.getColumns().length != bc.length) {
            return false;
        }
        // TODO 90 dont want to care about order
        for (int i = 0; i < bc.length; i++) {
            if (a.getColumns()[i] != bc[i]) {
                return false;
            }
            if (a.getRefColumns()[i] != bcr[i]) {
                return false;
            }
        }
        return true;
    }

    public static TableFilter getOrCreateFor(Session session,
            ConstraintReferential ref) {

        for (TableFilter tf : session.getFilters()) {
            // XXX not very efficient as know the ref wanted?
            JoinHelper jh = new JoinHelper(tf, tf.getJoinCondition());

            if ((jh.isCartesian() && tf.getTable() == ref.getRefTable())
                    || (jh.isSimple() && jh.getConstraint() == ref)) {
                return tf;
            }
        }

        TableFilter tableFilter = session
                .getTableFilter(ref.getTable());

        TableFilter refTableFilter = session.createTableFilter(ref
                .getRefTable());

        Comparison joinCond = new Comparison(session, Operator.EQUAL,
                new ExpressionColumn(session, null, tableFilter.getAlias(), ref
                        .getColumns()[0].getName()), new ExpressionColumn(
                        session, null, refTableFilter.getAlias(), ref
                                .getRefColumns()[0].getName()));
        tableFilter.addJoin(refTableFilter, true, joinCond);
        return refTableFilter;
    }

    // XXX better name "forChild"
    public static JoinHelper getParent(TableFilter tf) {
        // for (TableFilter tf2 : tf.getSession().getFilters()) {
        // TableFilter child2 = tf2.getJoin();
        Expression joinCondition = tf.getJoinCondition();
        JoinHelper thing = new JoinHelper(tf, joinCondition);

        if (thing.isSimple()) {
            return thing;
        }
        // }
        return null;
    }

    private boolean foo(Expression exp, List<Column> parentCols,
            List<Column> childCols) {
        if (exp instanceof Comparison) {
            return foo((Comparison) exp, parentCols, childCols);
        }

        return false;
    }

    private boolean foo(Comparison c, List<Column> parentCols,
            List<Column> childCols) {
        if (!Operator.EQUAL.equals(c.getOperatorName())) {
            return false;
        }
        if (!(c.getLeft() instanceof ExpressionColumn)) {
            return false;
        }
        if (!(c.getRight() instanceof ExpressionColumn)) {
            return false;
        }
        ExpressionColumn left = (ExpressionColumn) c.getLeft();
        ExpressionColumn right = (ExpressionColumn) c.getRight();
        if (left.getTableFilter() == child) {
            foo(right.getTableFilter());
            childCols.add(left.getColumn());
            parentCols.add(right.getColumn());
        } else if (right.getTableFilter() == child) {
            foo(left.getTableFilter());
            childCols.add(right.getColumn());
            parentCols.add(left.getColumn());
        } else {
            return false;
        }
return true;
    }

    private boolean foo(TableFilter parent2) {
        if (parent == null) {
            parent = parent2;
        } else {
            if (parent != parent2) {
                return false;
            }
        }
        return true;
    }

    private boolean isCartesian() {
        isSimple();
        return getParent() == null;
    }

    private boolean isSimple() {
        List<Column> parentCols = new ArrayList<Column>();
        List<Column> childCols = new ArrayList<Column>();
        if (
           !foo(joinCondition, parentCols, childCols)) {

            return false;
        }
        ConstraintReferential cand = new ConstraintReferential("test", child
                .getTable());
        cand.setRefTable(parent.getTable());
        cand.setColumns(childCols.toArray(new Column[0]));
        cand.setRefColumns(parentCols.toArray(new Column[0]));

        for (Constraint c : parent.getTable().getConstraints()) {
            if (c instanceof ConstraintReferential) {
                ConstraintReferential cr = (ConstraintReferential) c;
                if (match(cand, cr)) {
                    this.constraint = cr;
                    return true;
                }
            }
        }

        return false;
    }
} public class ConstraintReferential extends Constraint {
    private Column[] columns;

    private Table refTable;

    private Column[] refColumns;

    private String revName;

    private boolean hidden;

    private boolean revHidden;

    public boolean isHidden() {
        return hidden;
    }

    public void setHidden(boolean p) {
        this.hidden = p;
    }

    public boolean isRevHidden() {
        return revHidden;
    }

    public void setRevHidden(boolean p) {
        this.revHidden = p;
    }

    public String getRevName() {
        return revName;
    }

    public void setRevName(String p) {
        this.revName = p;
    }

    // for GWT RPC
    public ConstraintReferential() {
    }

    public ConstraintReferential(String name, Table t) {
        super(name, t);
    }

    public Column[] getColumns() {
        return columns;
    }

    public void setColumns(Column[] p) {
        this.columns = p;
    }

    public Column[] getRefColumns() {
        return refColumns;
    }

    public void setRefColumns(Column[] p) {
        this.refColumns = p;
    }

    public Table getRefTable() {
        return refTable;
    }

    public void setRefTable(Table p) {
        this.refTable = p;
    }

    @Override
    public String toString() {
        return "FK:" + getName() + "(" + getTable() + " to " + getRefTable() + ")";
    }
} public class Editor implements HasStyleName, Serializable, IsSerializable {
    public static class TextEditor extends Editor {
        @Override
        public Object getDefault() {
            return "";
        }
    }

    public static class DateEditor extends Editor {
        public static final String FORMAT = "format";
    }

    public static class BooleanEditor extends Editor {
        @Override
        public Object getDefault() {
            return Boolean.FALSE;
        }
    }

    public static class SelectEditor extends Editor {

    }

    public static class NumberEditor extends Editor {
    }


    // XXX - rubbish, only used by JSON?
    private static final Map<String, Editor> editorByName = new HashMap<String, Editor>();

    private static Editor valueOf2(String name) {
        if ("STRING".equals(name) || "TEXT".equals(name)) {
            return new TextEditor();
        } else if ("DATE".equals(name)) {
            return new DateEditor();
        } else if ("SUGGEST".equals(name)) {
            return new SuggestEditor();
        } else if ("SELECT".equals(name)) {
            return new SelectEditor();
        } else if ("NUMBER".equals(name)) {
            return new NumberEditor();
        } else {
            throw new RuntimeException("No editor for " + name);
        }
    }

    public static Editor valueOf(String name) {
        Editor e = editorByName.get(name);
        if (e == null) {
            e = valueOf2(name);
            editorByName.put(name, e);
        }
        return e;
    }

    // XXX map mush
    private final Map<String, Object> attributes = new HashMap<String, Object>();

    private String styleName;

    public Object getDefault() {
        return null;
    }

    public void setAttribute(String name, Object value) {
        attributes.put(name, value);
    }

    public Object getAttribute(String name) {
        return attributes.get(name);
    }

    public String getStyleName() {
        return styleName;
    }

    @Override
    public void setStyleName(String p) {
        this.styleName = p;
    }
} public class Table extends SchemaObjectBase implements HasLabel {

    private final Map<String, Column> columns = new LinkedHashMap<String, Column>();

    private final List<Constraint> constraints = new ArrayList<Constraint>();

    private boolean hidden;


    public Table() {
    }

    public Table(String name) {
        setObjectName(name);
    }

    public void add(Column c) {
        c.setTable(this);
        columns.put(c.getName().toUpperCase(), c);
    }

    public Collection<Column> getColumns() {
        return columns.values();
    }

    public Column getColumn(String name) {
        return columns.get(name.toUpperCase());
    }

    public List<Constraint> getConstraints() {
        return constraints;
    }

    public Constraint getConstraintByName(String name) {
        for (Constraint c : constraints) {
            if (c.getName().equals(name)) {
                return c;
            }
        }
        throw new IllegalArgumentException("Unable to find constraint '" + name + "'");
    }

    public void add(Constraint c) {
        constraints.add(c);
    }

    public boolean isHidden() {
        return hidden;
    }

    public void setHidden(boolean p) {
        this.hidden = p;
    }

    @Override
    public String toString() {
        return "Table:" + getName();
    }
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.Test;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle.MultiWordSuggestion;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SuggestOracle.Response;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.redspr.redquerybuilder.core.client.command.CommandBuilder;
import com.redspr.redquerybuilder.core.client.command.Parser;
import com.redspr.redquerybuilder.core.client.command.Prepared;
import com.redspr.redquerybuilder.core.client.command.dml.Select;
import com.redspr.redquerybuilder.core.client.engine.Session;
import com.redspr.redquerybuilder.core.client.engine.TableEvent;
import com.redspr.redquerybuilder.core.client.engine.TableEventHandler;
import com.redspr.redquerybuilder.core.client.expression.Comparison;
import com.redspr.redquerybuilder.core.client.expression.ConditionAndOr;
import com.redspr.redquerybuilder.core.client.expression.ExpressionColumn;
import com.redspr.redquerybuilder.core.client.expression.Nop;
import com.redspr.redquerybuilder.core.client.expression.Parameter;
import com.redspr.redquerybuilder.core.client.expression.SelectEditorWidget;
import com.redspr.redquerybuilder.core.client.table.JoinHelper;
import com.redspr.redquerybuilder.core.client.table.TableFilter;
import com.redspr.redquerybuilder.core.client.util.ObjectArray;
import com.redspr.redquerybuilder.core.shared.meta.ConstraintReferential;
import com.redspr.redquerybuilder.core.shared.meta.Editor;
import com.redspr.redquerybuilder.core.shared.meta.Table;
        cb.getSelect().getSQL(new ArrayList());

        Comparison c = new Comparison(s);

        cb.getSelect().addCondition(c);
        cb.fireDirty();

        cb.getSelect().remove(c);
        cb.fireDirty();
    }

    @Test
    public void testChangeTableWithCondition() throws Exception {
        Session s = getSession();
        CommandBuilder cb = new CommandBuilder(s);
        // RootPanel.get().add(cb);
        cb.getSelect().updateTable(
                s.getDatabase().getMainSchema().findTableOrView("PERSON"));
        cb.fireDirty();

        Comparison c = new Comparison(s);

        cb.getSelect().addCondition(c);
        cb.fireDirty();

        cb.getSelect().updateTable(
                s.getDatabase().getMainSchema().findTableOrView("Log"));
        cb.fireDirty();
    }

    @Test
    public void testSimpleJoin() throws Exception {
        Session s = getSession();
        CommandBuilder cb = new CommandBuilder(s);

        cb.getSelect().updateTable(
                s.getDatabase().getMainSchema().findTableOrView("Log"));
        s.getMsgBus().fireEvent(new TableEvent()); // XXX encapsulation
        cb.fireDirty();

        Comparison c = new Comparison(s);
        cb.getSelect().addCondition(c);
        cb.fireDirty();

        ExpressionColumn left = (ExpressionColumn) c.getLeft();

        TableFilter log = cb.getSelect().getFilters().get(0);
        assertEquals("Log", log.getTable().getName());
        assertEquals("x0", log.getAlias());
        for (int i = 0; i < 2; i++) {
            left.selectConstraintRef((ConstraintReferential) log.getTable()
                    .getConstraints().get(0));
            left.updateColumn("x1", s.getDatabase().getMainSchema().findTableOrView(
                    "PERSON").getColumn("sex"));

            {
                JoinHelper thing = JoinHelper.getParent(cb.getSelect()
                        .getTableFilter("x1"));
                assertTrue(log.getTable().getConstraints().get(0) == thing
                        .getConstraint());
                assertEquals("x0", thing.getParent().getAlias());
            }

            {
                JoinHelper thing = JoinHelper.getParent(cb.getSelect()
                        .getTableFilter("x0"));
                assertTrue(thing == null);
            }

            assertEquals(
                    "try " + i,
                    "SELECT \nFROM Log x0\nLEFT OUTER JOIN PERSON x1 ON x0.parent = x1.id\nWHERE (x1.sex = ?)",
                    cb.getSelect().getSQL(new ArrayList()));
        }
    }

    @Test
    public void testSelectAndUnselectJoin() throws Exception {
        Session s = getSession();
        CommandBuilder cb = new CommandBuilder(s);

        cb.getSelect().updateTable(
                s.getDatabase().getMainSchema().findTableOrView("Log"));
        cb.fireDirty();

        Comparison c = new Comparison(s);

        cb.getSelect().addCondition(c);
        cb.fireDirty();

        ExpressionColumn left = (ExpressionColumn) c.getLeft();

        TableFilter log = cb.getSelect().getFilters().get(0);
        left.selectConstraintRef((ConstraintReferential) log.getTable()
                .getConstraints().get(0));

        cb.fireDirty();
        left.updateColumn("x1", s.getDatabase().getMainSchema().findTableOrView(
                "PERSON").getColumn("sex"));

        cb.fireDirty();



        assertEquals(
                "SELECT \nFROM Log x0\nLEFT OUTER JOIN PERSON x1 ON x0.parent = x1.id\nWHERE (x1.sex = ?)",
                cb.getSelect().getSQL(new ArrayList()));

        // change back to simple column
        left.updateColumn("x0", log.getTable().getColumn("date"));
        cb.fireDirty();
    }

    // select Log -> Person -> Log
    @Test
    public void testSelectAndThenReverseJoin() throws Exception {
        Session s = getSession();
        s.getDatabase().getMainSchema().addReverseConstraints();
        CommandBuilder cb = new CommandBuilder(s);

        Table log = s.getDatabase().getMainSchema().findTableOrView("Log");