/* Copyright (c) 2001-2018, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb;

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.HsqlList;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.map.ValuePool;
import org.hsqldb.persist.HsqlDatabaseProperties;
import org.hsqldb.result.Result;
import org.hsqldb.result.ResultProperties;
import org.hsqldb.rights.User;
import org.hsqldb.types.Charset;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

/**
 * Parser for session and management statements
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.4.1
 * @since 1.9.0
 */
public class ParserCommand extends ParserDDL {

    ParserCommand(Session session, Scanner t) {
        super(session, t);
    }

    Statement compileStatement(int props) {

        Statement cs = compilePart(props);

        if (token.tokenType == Tokens.X_ENDPARSE) {
            if (cs.getSchemaName() == null) {
                cs.setSchemaHsqlName(session.getCurrentSchemaHsqlName());
            }

            return cs;
        }

        throw unexpectedToken();
    }

    HsqlArrayList compileStatements(String sql, Result cmd) {

        HsqlArrayList list = new HsqlArrayList();
        Statement     cs   = null;

        reset(session, sql);

        while (true) {
            if (token.tokenType == Tokens.X_ENDPARSE) {
                break;
            }

            try {
                lastError = null;
                cs        = compilePart(cmd.getExecuteProperties());
            } catch (HsqlException e) {
                if (lastError != null && lastError.getLevel() > e.getLevel()) {
                    throw lastError;
                }

                throw e;
            }

            if (!cs.isExplain
                    && cs.getParametersMetaData().getColumnCount() > 0) {
                throw Error.error(ErrorCode.X_42575);
            }

            cs.setCompileTimestamp(
                database.txManager.getGlobalChangeTimestamp());
            list.add(cs);
        }

        if (list.size() > 1) {
            if (database.sqlRestrictExec) {
                throw Error.error(ErrorCode.X_07502);
            }
        }

        int returnType = cmd.getStatementType();

        if (returnType != StatementTypes.RETURN_ANY) {
            int group = cs.getGroup();

            if (group == StatementTypes.X_SQL_DATA) {
                if (returnType == StatementTypes.RETURN_COUNT) {
                    throw Error.error(ErrorCode.X_07503);
                }
            } else if (returnType == StatementTypes.RETURN_RESULT) {

                // allow update count statements with Statement.executeQuery()
                // to return an empty result set
                if (database.sqlRestrictExec) {
                    throw Error.error(ErrorCode.X_07504);
                }
            }
        }

        return list;
    }

    private Statement compilePart(int props) {

        Statement cs;

        compileContext.reset();
        setPartPosition(getPosition());

        if (token.tokenType == Tokens.X_STARTPARSE) {
            read();
        }

        switch (token.tokenType) {

            // DQL
            case Tokens.WITH :
            case Tokens.OPENBRACKET :
            case Tokens.SELECT :
            case Tokens.TABLE : {
                cs = compileCursorSpecification(RangeGroup.emptyArray, props,
                                                false);

                break;
            }
            case Tokens.VALUES : {
                RangeGroup[] ranges =
                    session.sessionContext.sessionVariableRangeGroups;

                compileContext.setOuterRanges(ranges);

                cs = compileShortCursorSpecification(props);

                break;
            }

            // DML
            case Tokens.INSERT : {
                cs = compileInsertStatement(RangeGroup.emptyArray);

                break;
            }
            case Tokens.UPDATE : {
                cs = compileUpdateStatement(RangeGroup.emptyArray);

                break;
            }
            case Tokens.MERGE : {
                cs = compileMergeStatement(RangeGroup.emptyArray);

                break;
            }
            case Tokens.DELETE : {
                cs = compileDeleteStatement(RangeGroup.emptyArray);

                break;
            }
            case Tokens.TRUNCATE : {
                cs = compileTruncateStatement();

                break;
            }
            case Tokens.REPLACE : {
                cs = compileInsertStatement(RangeGroup.emptyArray);

                break;
            }

            // PROCEDURE
            case Tokens.CALL : {
                cs = compileCallStatement(
                    session.sessionContext.sessionVariableRangeGroups, false);

                break;
            }

            // SQL SESSION
            case Tokens.SET :
                cs = compileSet();
                break;

            // diagnostic
            case Tokens.GET :
                cs = compileGetStatement(
                    session.sessionContext.sessionVariableRangeGroups);
                break;

            case Tokens.START :
                cs = compileStartTransaction();
                break;

            case Tokens.COMMIT :
                cs = compileCommit();
                break;

            case Tokens.ROLLBACK :
                cs = compileRollback();
                break;

            case Tokens.SAVEPOINT :
                cs = compileSavepoint();
                break;

            case Tokens.RELEASE :
                cs = compileReleaseSavepoint();
                break;

            // DDL
            case Tokens.CREATE :
                cs = compileCreate();
                break;

            case Tokens.ALTER :
                cs = compileAlter();
                break;

            case Tokens.DROP :
                cs = compileDrop();
                break;

            case Tokens.GRANT :
            case Tokens.REVOKE :
                cs = compileGrantOrRevoke();
                break;

            case Tokens.COMMENT :
                cs = compileComment();
                break;

            // HSQL SESSION
            case Tokens.LOCK :
                cs = compileLock();
                break;

            case Tokens.CONNECT :
                cs = compileConnect();
                break;

            case Tokens.DISCONNECT :
                cs = compileDisconnect();
                break;

            // HSQL COMMAND
            case Tokens.SCRIPT :
                cs = compileScript();
                break;

            case Tokens.SHUTDOWN :
                cs = compileShutdown();
                break;

            case Tokens.BACKUP :
                cs = compileBackup();
                break;

            case Tokens.CHECKPOINT :
                cs = compileCheckpoint();
                break;

            case Tokens.EXPLAIN : {
                int position = getPosition();

                read();

                if (token.tokenType == Tokens.PLAN) {
                    cs = compileExplainPlan();
                } else {
                    cs = compileExplainReferences();
                }

                cs.setSQL(getLastPart(position));

                break;
            }
            case Tokens.DECLARE :
                cs = compileDeclare();
                break;

            case Tokens.PERFORM :
                cs = compilePerform();
                break;

            default :
                throw unexpectedToken();
        }

        // SET_SESSION_AUTHORIZATION is translated dynamically at runtime for logging
        switch (cs.type) {

            // these are set at compile time for logging
            case StatementTypes.COMMIT_WORK :
            case StatementTypes.ROLLBACK_WORK :
            case StatementTypes.SET_USER_PASSWORD :
            case StatementTypes.EXPLAIN_PLAN :
                break;

            default :
                cs.setSQL(getLastPart());
        }

        if (token.tokenType == Tokens.SEMICOLON) {
            read();
        } else if (token.tokenType == Tokens.X_ENDPARSE) {}

        return cs;
    }

    private Statement compileDeclare() {

        Statement      cs;
        ColumnSchema[] variables;

        cs = compileDeclareLocalTableOrNull();

        if (cs != null) {
            return cs;
        }

        variables = readLocalVariableDeclarationOrNull();

        if (variables != null) {
            Object[] args = new Object[]{ variables };

            cs = new StatementSession(StatementTypes.DECLARE_VARIABLE, args);

            return cs;
        }

        cs = compileDeclareCursorOrNull(RangeGroup.emptyArray, false);

        if (cs == null) {
            throw lastError == null ? unexpectedToken()
                                    : lastError;
        }

        return cs;
    }

    private Statement compileScript() {

        String name = null;

        read();

        if (token.tokenType == Tokens.X_VALUE) {
            name = readQuotedString();
        }

        HsqlName[] names =
            database.schemaManager.getCatalogAndBaseTableNames();
        Object[] args = new Object[]{ name };

        return new StatementCommand(StatementTypes.DATABASE_SCRIPT, args,
                                    null, names);
    }

    private Statement compileConnect() {

        String userName;
        String password = null;

        read();
        readThis(Tokens.USER);
        checkIsSimpleName();

        userName = token.tokenString;

        read();

        if (!session.isProcessingLog()) {
            readThis(Tokens.PASSWORD);

            password = readPassword();
        }

        Expression[] args = new Expression[] {
            new ExpressionValue(userName, Type.SQL_VARCHAR),
            new ExpressionValue(password, Type.SQL_VARCHAR)
        };
        Statement cs =
            new StatementSession(session, compileContext,
                                 StatementTypes.SET_SESSION_AUTHORIZATION,
                                 args);

        return cs;
    }

    private StatementCommand compileSetDefault() {

        read();

        switch (token.tokenType) {

            case Tokens.INITIAL : {
                read();
                readThis(Tokens.SCHEMA);

                HsqlName schema = database.schemaManager.getSchemaHsqlName(
                    token.tokenString);

                read();

                Object[] args = new Object[]{ schema };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_DEFAULT_INITIAL_SCHEMA, args);
            }
            case Tokens.RESULT : {
                read();
                readThis(Tokens.MEMORY);
                readThis(Tokens.ROWS);

                Integer  size = readIntegerObject();
                Object[] args = new Object[]{ size };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_RESULT_MEMORY_ROWS, args);
            }
            case Tokens.TABLE : {
                read();
                readThis(Tokens.TYPE);

                int type;

                switch (token.tokenType) {

                    case Tokens.MEMORY :
                        type = TableBase.MEMORY_TABLE;
                        break;

                    case Tokens.CACHED :
                        type = TableBase.CACHED_TABLE;
                        break;

                    default :
                        throw unexpectedToken();
                }

                read();

                Object[] args = new Object[]{ ValuePool.getInt(type) };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_DEFAULT_TABLE_TYPE, args);
            }
            case Tokens.ISOLATION : {
                read();
                readThis(Tokens.LEVEL);

                int level;

                switch (token.tokenType) {

                    case Tokens.READ :
                        read();
                        readThis(Tokens.COMMITTED);

                        level = SessionInterface.TX_READ_COMMITTED;
                        break;

                    case Tokens.SERIALIZABLE :
                        read();

                        level = SessionInterface.TX_SERIALIZABLE;
                        break;

                    default :
                        throw unexpectedToken();
                }

                Object[] args = new Object[]{ ValuePool.getInt(level) };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_DEFAULT_ISOLATION_LEVEL, args);
            }
            default :
                throw unexpectedToken();
        }
    }

    private StatementCommand compileSetProperty() {

        // command is a no-op from 1.9
        read();

        String property;
        Object value;

        checkIsSimpleName();
        checkIsDelimitedIdentifier();

        property = token.tokenString;

        read();

        if (token.tokenType == Tokens.TRUE) {
            value = Boolean.TRUE;
        } else if (token.tokenType == Tokens.FALSE) {
            value = Boolean.FALSE;
        } else {
            checkIsValue();

            value = token.tokenValue;
        }

        read();

        Object[] args = new Object[] {
            property, value
        };

        return new StatementCommand(StatementTypes.SET_DATABASE_PROPERTY,
                                    args);
    }

    private Statement compileSet() {

        read();

        switch (token.tokenType) {

            case Tokens.CATALOG : {
                read();

                Expression e = XreadValueSpecificationOrNull();

                if (e == null) {
                    HsqlName name = readSchemaName();
                    Object[] args = new Object[]{ name };

                    return new StatementSession(StatementTypes.SET_CATALOG,
                                                args);
                }

                if (!e.getDataType().isCharacterType()) {
                    throw Error.error(ErrorCode.X_0P000);
                }

                if (e.getType() != OpTypes.VALUE
                        && (e.getType() != OpTypes.SQL_FUNCTION
                            || !((FunctionSQL) e).isValueFunction())) {
                    throw Error.error(ErrorCode.X_0P000);
                }

                Expression[] args = new Expression[]{ e };

                return new StatementSession(session, compileContext,
                                            StatementTypes.SET_CATALOG, args);
            }
            case Tokens.SCHEMA : {
                read();

                Expression e = XreadValueSpecificationOrNull();

                if (e == null) {
                    HsqlName name = readSchemaName();
                    Object[] args = new Object[]{ name };

                    return new StatementSession(StatementTypes.SET_SCHEMA,
                                                args);
                }

                if (e.isUnresolvedParam()) {
                    e.setDataType(session, Type.SQL_VARCHAR_DEFAULT);
                }

                if (!e.getDataType().isCharacterType()) {
                    throw Error.error(ErrorCode.X_0P000);
                }

                switch (e.getType()) {

                    case OpTypes.PARAMETER :
                    case OpTypes.DYNAMIC_PARAM :
                    case OpTypes.VALUE :
                        break;

                    case OpTypes.SQL_FUNCTION :
                        if (((FunctionSQL) e).isValueFunction()) {
                            break;
                        }

                    // fall through
                    default :
                        throw Error.error(ErrorCode.X_0P000);
                }

                Expression[] args = new Expression[]{ e };

                return new StatementSession(session, compileContext,
                                            StatementTypes.SET_SCHEMA, args);
            }
            case Tokens.NO : {
                read();
                readThis(Tokens.COLLATION);

                HsqlArrayList charsets = null;

                if (readIfThis(Tokens.FOR)) {
                    charsets = new HsqlArrayList();

                    while (true) {
                        SchemaObject charset =
                            readSchemaObjectName(SchemaObject.CHARSET);

                        charsets.add(charset);

                        if (token.tokenType == Tokens.COMMA) {
                            read();

                            continue;
                        }

                        break;
                    }
                }

                Object[] args = new Object[] {
                    null, Boolean.FALSE, charsets
                };

                return new StatementSession(StatementTypes.SET_COLLATION,
                                            args);
            }
            case Tokens.COLLATION : {
                read();

                Expression e = XreadValueSpecificationOrNull();

                if (e == null || !e.getDataType().isCharacterType()) {
                    throw Error.error(ErrorCode.X_2H000);
                }

                HsqlArrayList charsets = null;

                if (readIfThis(Tokens.FOR)) {
                    charsets = new HsqlArrayList();

                    while (true) {
                        SchemaObject charset =
                            readSchemaObjectName(SchemaObject.CHARSET);

                        charsets.add(charset);

                        if (token.tokenType == Tokens.COMMA) {
                            read();

                            continue;
                        }

                        break;
                    }
                }

                Object[] args = new Object[] {
                    e, Boolean.TRUE, charsets
                };

                return new StatementSession(StatementTypes.SET_COLLATION,
                                            args);
            }
            case Tokens.TIME : {
                read();

                return compileSetTimeZone();
            }
            case Tokens.ROLE : {
                read();

                return compileSetRole();
            }
            case Tokens.SESSION : {
                read();

                return compileSessionSettings();
            }
            case Tokens.TRANSACTION : {
                read();

                Object[] args = processTransactionCharacteristics();

                if (args[0] == null && args[1] == null) {
                    throw unexpectedToken();
                }

                return new StatementSession(StatementTypes.SET_TRANSACTION,
                                            args);
            }
            case Tokens.AUTOCOMMIT : {
                read();

                Boolean  mode = processTrueOrFalseObject();
                Object[] args = new Object[]{ mode };

                return new StatementSession(
                    StatementTypes.SET_SESSION_AUTOCOMMIT, args);
            }

            // deprecated
            case Tokens.READONLY : {
                read();

                Boolean  readonly = processTrueOrFalseObject();
                Object[] args     = new Object[]{ readonly };

                return new StatementSession(
                    StatementTypes.SET_SESSION_CHARACTERISTICS, args);
            }
            case Tokens.IGNORECASE : {
                read();

                Boolean  mode = processTrueOrFalseObject();
                Object[] args = new Object[]{ mode };

                return new StatementSession(
                    StatementTypes.SET_SESSION_SQL_IGNORECASE, args);
            }
            case Tokens.MAXROWS : {
                read();

                Integer  size = readIntegerObject();
                Object[] args = new Object[]{ size };

                return new StatementSession(
                    StatementTypes.SET_SESSION_RESULT_MAX_ROWS, args);
            }

            // for backward compatibility
            case Tokens.DEFAULT : {
                read();
                readThis(Tokens.TABLE);
                readThis(Tokens.TYPE);

                int type;

                switch (token.tokenType) {

                    case Tokens.MEMORY :
                        type = TableBase.MEMORY_TABLE;
                        break;

                    case Tokens.CACHED :
                        type = TableBase.CACHED_TABLE;
                        break;

                    default :
                        throw unexpectedToken();
                }

                read();

                Object[] args = new Object[]{ ValuePool.getInt(type) };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_DEFAULT_TABLE_TYPE, args);
            }
            case Tokens.TABLE : {
                return compileSetTable();
            }
            case Tokens.WRITE_DELAY : {
                read();

                int delay = 0;

                if (token.tokenType == Tokens.TRUE) {
                    delay = database.getProperties().getDefaultWriteDelay();

                    read();
                } else if (token.tokenType == Tokens.FALSE) {
                    delay = 0;

                    read();
                } else {
                    delay = readInteger();

                    if (delay < 0) {
                        delay = 0;
                    }

                    if (token.tokenType == Tokens.MILLIS) {
                        read();
                    } else {
                        delay *= 1000;
                    }
                }

                Object[] args = new Object[]{ Integer.valueOf(delay) };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_FILES_WRITE_DELAY, args, null,
                    null);
            }
            case Tokens.PASSWORD : {
                String  password;
                Boolean isDigest = Boolean.FALSE;

                read();

                if (readIfThis(Tokens.DIGEST)) {
                    isDigest = Boolean.TRUE;
                }

                password = readPassword();

                Object[] args = new Object[] {
                    null, password, isDigest
                };
                Statement cs =
                    new StatementCommand(StatementTypes.SET_USER_PASSWORD,
                                         args);
                String sql = User.getSetCurrentPasswordDigestSQL(
                    database.granteeManager, password, isDigest);

                cs.setSQL(sql);

                return cs;
            }
            case Tokens.INITIAL : {
                read();
                readThis(Tokens.SCHEMA);

                HsqlName schema;

                if (token.tokenType == Tokens.DEFAULT) {
                    schema = null;
                } else {
                    schema = database.schemaManager.getSchemaHsqlName(
                        token.tokenString);
                }

                read();

                Object[] args = new Object[] {
                    null, schema
                };

                return new StatementCommand(
                    StatementTypes.SET_USER_INITIAL_SCHEMA, args);
            }
            case Tokens.FILES : {
                return compileSetFilesProperty();
            }
            case Tokens.DATABASE : {
                return compileSetDatabaseProperty();
            }
            case Tokens.PROPERTY : {
                return compileSetProperty();
            }
            default : {
                return compileSetStatement(
                    session.sessionContext.sessionVariableRangeGroups,
                    session.sessionContext.sessionVariablesRange);
            }
        }
    }

    StatementCommand compileSetTable() {

        read();

        Table    table = readTableName();
        Object[] args  = new Object[] {
            table.getName(), null, null
        };

        switch (token.tokenType) {

            default : {
                throw unexpectedToken();
            }
            case Tokens.SOURCE :
                read();

                return compileTableSource(table);

            case Tokens.READ : {
                read();

                boolean readonly = false;

                if (token.tokenType == Tokens.WRITE) {
                    read();
                } else {
                    readThis(Tokens.ONLY);

                    readonly = true;
                }

                args[1] = Boolean.valueOf(readonly);

                return new StatementCommand(StatementTypes.SET_TABLE_READONLY,
                                            args, null,
                                            new HsqlName[]{ table.getName() });
            }

            // deprecated
            case Tokens.READONLY : {
                read();

                Boolean readonly = processTrueOrFalseObject();

                args[1] = readonly;

                return new StatementCommand(StatementTypes.SET_TABLE_READONLY,
                                            args, null,
                                            new HsqlName[]{ table.getName() });
            }
            case Tokens.INDEX : {
                String value;

                read();
                checkIsValue();

                value = token.tokenString;

                read();

                args[1] = value;
                args[2] = Integer.valueOf(TableBase.CACHED_TABLE);

                return new StatementCommand(StatementTypes.SET_TABLE_INDEX,
                                            args, null,
                                            new HsqlName[]{ table.getName() });
            }
            case Tokens.TYPE : {
                read();

                int newType;

                switch (token.tokenType) {

                    case Tokens.MEMORY :
                        newType = TableBase.MEMORY_TABLE;
                        break;

                    case Tokens.CACHED :
                        newType = TableBase.CACHED_TABLE;
                        break;

                    default :
                        throw unexpectedToken();
                }

                switch (table.getTableType()) {

                    case TableBase.MEMORY_TABLE :
                    case TableBase.CACHED_TABLE :
                    case TableBase.TEXT_TABLE :
                        break;

                    default :
                        throw unexpectedToken();
                }

                read();

                args[1] = Integer.valueOf(newType);

                return new StatementCommand(StatementTypes.SET_TABLE_TYPE,
                                            args, null,
                                            new HsqlName[]{ table.getName() });
            }
            case Tokens.CLUSTERED : {
                read();
                readThis(Tokens.ON);

                OrderedHashSet set = new OrderedHashSet();

                readThis(Tokens.OPENBRACKET);
                readSimpleColumnNames(set, table, false);
                readThis(Tokens.CLOSEBRACKET);

                int[] colIndex = table.getColumnIndexes(set);

                args[1] = colIndex;

                return new StatementCommand(StatementTypes.SET_TABLE_CLUSTERED,
                                            args, null,
                                            new HsqlName[]{ table.getName() });
            }
            case Tokens.NEW : {
                read();
                readThis(Tokens.SPACE);

                args = new Object[]{ table.getName() };

                HsqlName[] writeLockNames =
                    database.schemaManager.getCatalogAndBaseTableNames(
                        table.getName());

                return new StatementCommand(
                    StatementTypes.SET_TABLE_NEW_TABLESPACE, args, null,
                    writeLockNames);
            }
            case Tokens.SPACE : {
                read();

                Integer id = readIntegerObject();

                args = new Object[] {
                    table.getName(), id
                };

                HsqlName[] writeLockNames =
                    database.schemaManager.getCatalogAndBaseTableNames(
                        table.getName());

                return new StatementCommand(
                    StatementTypes.SET_TABLE_SET_TABLESPACE, args, null,
                    writeLockNames);
            }
        }
    }

    StatementCommand compileSetDatabaseProperty() {

        read();

        String name;

        checkDatabaseUpdateAuthorisation();

        switch (token.tokenType) {

            case Tokens.AUTHENTICATION : {
                read();
                readThis(Tokens.FUNCTION);

                Routine  routine = readCreateDatabaseAuthenticationFunction();
                Object[] args    = new Object[]{ routine };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_AUTHENTICATION, args, null,
                    null);
            }
            case Tokens.COLLATION : {
                Boolean padSpace = null;

                read();
                checkIsSimpleName();

                name = token.tokenString;

                read();

                if (readIfThis(Tokens.NO)) {
                    readThis(Tokens.PAD);

                    padSpace = Boolean.FALSE;
                } else if (readIfThis(Tokens.PAD)) {
                    readThis(Tokens.SPACE);

                    padSpace = Boolean.TRUE;
                }

                if (padSpace == null) {
                    if (session.isProcessingScript()
                            && database.getProperties().isVersion18()) {
                        padSpace = Boolean.FALSE;
                    } else {
                        padSpace = Boolean.TRUE;
                    }
                }

                Object[] args = new Object[] {
                    name, padSpace
                };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_SQL_COLLATION, args, null,
                    null);
            }
            case Tokens.DEFAULT : {
                return compileSetDefault();
            }
            case Tokens.EVENT : {
                read();
                readThis(Tokens.LOG);

                boolean sqlLog = readIfThis(Tokens.SQL);

                readThis(Tokens.LEVEL);

                Integer  value = readIntegerObject();
                Object[] args  = new Object[] {
                    value, Boolean.valueOf(sqlLog)
                };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_FILES_EVENT_LOG, args, null,
                    null);
            }
            case Tokens.GC : {
                read();

                Integer  size = readIntegerObject();
                Object[] args = new Object[]{ size };

                return new StatementCommand(StatementTypes.SET_DATABASE_GC,
                                            args, null, null);
            }
            case Tokens.PASSWORD : {
                read();

                switch (token.tokenType) {

                    case Tokens.CHECK : {
                        read();
                        readThis(Tokens.FUNCTION);

                        Routine  routine = readCreatePasswordCheckFunction();
                        Object[] args    = new Object[]{ routine };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_PASSWORD_CHECK, args,
                            null, null);
                    }
                    case Tokens.DIGEST : {
                        read();

                        name = readQuotedString();

                        Object[] args = new Object[]{ name };

                        return new StatementCommand(
                            StatementTypes.SET_DATABASE_PASSWORD_DIGEST, args,
                            null, null);
                    }
                    default :
                        throw unexpectedToken();
                }
            }
            case Tokens.REFERENTIAL : {
                read();
                readThis(Tokens.INTEGRITY);

                boolean  mode = processTrueOrFalse();
                Object[] args = new Object[]{ Boolean.valueOf(mode) };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_SQL_REFERENTIAL_INTEGRITY,
                    args, null, null);
            }
            case Tokens.SQL : {
                read();

                int     type     = StatementTypes.SET_DATABASE_SQL;
                Boolean flag     = Boolean.TRUE;
                Integer value    = Integer.valueOf(0);
                String  property = null;

                switch (token.tokenType) {

                    case Tokens.RESTRICT :
                        read();
                        readThis(Tokens.EXEC);

                        property = HsqlDatabaseProperties.sql_restrict_exec;
                        flag     = processTrueOrFalseObject();
                        break;

                    case Tokens.LIVE :
                        read();
                        readThis(Tokens.OBJECT);

                        property = HsqlDatabaseProperties.sql_live_object;
                        flag     = processTrueOrFalseObject();
                        break;

                    case Tokens.NAMES :
                        read();

                        property = HsqlDatabaseProperties.sql_enforce_names;
                        flag     = processTrueOrFalseObject();
                        break;

                    case Tokens.REGULAR :
                        read();
                        readThis(Tokens.NAMES);

                        property = HsqlDatabaseProperties.sql_regular_names;
                        flag     = processTrueOrFalseObject();
                        break;

                    case Tokens.REFERENCES :
                        read();

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_enforce_refs;
                        break;

                    case Tokens.SIZE :
                        read();

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_enforce_size;
                        break;

                    case Tokens.TYPES :
                        read();

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_enforce_types;
                        break;

                    case Tokens.TDC :
                        read();

                        if (readIfThis(Tokens.DELETE)) {
                            property = HsqlDatabaseProperties.sql_enforce_tdcd;
                        } else {
                            readThis(Tokens.UPDATE);

                            property = HsqlDatabaseProperties.sql_enforce_tdcu;
                        }

                        flag = processTrueOrFalseObject();
                        break;

                    case Tokens.TRANSLATE :
                        read();
                        readThis(Tokens.TTI);
                        readThis(Tokens.TYPES);

                        flag = processTrueOrFalseObject();
                        property =
                            HsqlDatabaseProperties.jdbc_translate_tti_types;
                        break;

                    case Tokens.CHARACTER :
                        read();
                        readThis(Tokens.LITERAL);

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_char_literal;
                        break;

                    case Tokens.CONCAT_WORD :
                        read();
                        readThis(Tokens.NULLS);

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_concat_nulls;
                        break;

                    case Tokens.NULLS :
                        read();

                        if (readIfThis(Tokens.FIRST)) {
                            property = HsqlDatabaseProperties.sql_nulls_first;
                        } else {
                            readThis(Tokens.ORDER);

                            property = HsqlDatabaseProperties.sql_nulls_order;
                        }

                        flag = processTrueOrFalseObject();
                        break;

                    case Tokens.UNIQUE :
                        read();
                        readThis(Tokens.NULLS);

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_unique_nulls;
                        break;

                    case Tokens.CONVERT :
                        read();
                        readThis(Tokens.TRUNCATE);

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_convert_trunc;
                        break;

                    case Tokens.AVG :
                        read();
                        readThis(Tokens.SCALE);

                        value    = readIntegerObject();
                        property = HsqlDatabaseProperties.sql_avg_scale;
                        break;

                    case Tokens.DOUBLE :
                        read();
                        readThis(Tokens.NAN);

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_double_nan;
                        break;

                    case Tokens.LONGVAR :
                        read();
                        readThis(Tokens.IS);
                        readThis(Tokens.LOB);

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_longvar_is_lob;
                        break;

                    case Tokens.IGNORECASE :
                        read();

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_ignore_case;
                        break;

                    case Tokens.SYNTAX :
                        read();

                        if (token.tokenString.equals(Tokens.T_DB2)) {
                            read();

                            property = HsqlDatabaseProperties.sql_syntax_db2;
                        } else if (token.tokenString.equals(Tokens.T_MSS)) {
                            read();

                            property = HsqlDatabaseProperties.sql_syntax_mss;
                        } else if (token.tokenString.equals(Tokens.T_MYS)) {
                            read();

                            property = HsqlDatabaseProperties.sql_syntax_mys;
                        } else if (token.tokenString.equals(Tokens.T_ORA)) {
                            read();

                            property = HsqlDatabaseProperties.sql_syntax_ora;
                        } else if (token.tokenString.equals(Tokens.T_PGS)) {
                            read();

                            property = HsqlDatabaseProperties.sql_syntax_pgs;
                        } else {
                            throw unexpectedToken();
                        }

                        flag = processTrueOrFalseObject();
                        break;

                    case Tokens.SYS : {
                        read();
                        readThis(Tokens.INDEX);
                        readThis(Tokens.NAMES);

                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_sys_index_names;

                        break;
                    }
                    default :
                        throw unexpectedToken();
                }

                Object[] args = new Object[] {
                    property, flag, value
                };

                return new StatementCommand(type, args, null, null);
            }
            case Tokens.TEXT : {
                read();
                readThis(Tokens.TABLE);
                readThis(Tokens.DEFAULTS);

                String   source = readQuotedString();
                Object[] args   = new Object[]{ source };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_TEXT_SOURCE, args, null, null);
            }
            case Tokens.TRANSACTION : {
                read();

                if (readIfThis(Tokens.ROLLBACK)) {
                    readThis(Tokens.ON);

                    if (!readIfThis(Tokens.DEADLOCK)) {
                        readThis(Tokens.CONFLICT);
                    }

                    Boolean mode = processTrueOrFalseObject();
                    StatementCommand cs = new StatementCommand(
                        StatementTypes.SET_DATABASE_TRANSACTION_CONFLICT,
                        new Object[]{ mode }, null, null);

                    return cs;
                }

                readThis(Tokens.CONTROL);

                int mode = TransactionManager.LOCKS;

                switch (token.tokenType) {

                    case Tokens.MVCC :
                        read();

                        mode = TransactionManager.MVCC;
                        break;

                    case Tokens.MVLOCKS :
                        read();

                        mode = TransactionManager.MVLOCKS;
                        break;

                    case Tokens.LOCKS :
                        read();

                        mode = TransactionManager.LOCKS;
                        break;

                    default :
                }

                HsqlName[] names =
                    database.schemaManager.getCatalogAndBaseTableNames();
                Object[] args = new Object[]{ ValuePool.getInt(mode) };
                StatementCommand cs = new StatementCommand(
                    StatementTypes.SET_DATABASE_TRANSACTION_CONTROL, args,
                    null, names);

                return cs;
            }
            case Tokens.UNIQUE : {
                read();
                readThis(Tokens.NAME);

                if (!isUndelimitedSimpleName()) {
                    throw unexpectedToken();
                }

                name = token.tokenString;

                read();

                if (name.length() != 16) {
                    throw Error.error(ErrorCode.X_42555);
                }

                if (!Charset.isInSet(name, Charset.unquotedIdentifier)
                        || !Charset.startsWith(name,
                                               Charset.uppercaseLetters)) {
                    throw Error.error(ErrorCode.X_42501);
                }

                Object[] args = new Object[]{ name };

                return new StatementCommand(
                    StatementTypes.SET_DATABASE_UNIQUE_NAME, args, null, null);
            }
            default : {
                throw unexpectedToken();
            }
        }
    }

    StatementCommand compileSetFilesProperty() {

        read();

        int        type  = 0;
        Boolean    flag  = null;
        Integer    value = null;
        Boolean    mode  = null;
        HsqlName[] names = database.schemaManager.getCatalogNameArray();

        checkDatabaseUpdateAuthorisation();

        switch (token.tokenType) {

            case Tokens.CHECK : {
                read();

                long longValue  = readBigint();
                long longValue2 = -1;

                type  = StatementTypes.SET_DATABASE_FILES_CHECK;
                names = database.schemaManager.getCatalogNameArray();

                if (readIfThis(Tokens.COMMA)) {
                    longValue2 = readBigint();
                }

                Object[] args = new Object[2];

                args[0] = Long.valueOf(longValue);
                args[1] = Long.valueOf(longValue2);

                return new StatementCommand(type, args, null, names);
            }
            case Tokens.CACHE : {
                read();

                if (readIfThis(Tokens.SIZE)) {
                    value = readIntegerObject();
                    type  = StatementTypes.SET_DATABASE_FILES_CACHE_SIZE;
                } else {
                    readThis(Tokens.ROWS);

                    value = readIntegerObject();
                    type  = StatementTypes.SET_DATABASE_FILES_CACHE_ROWS;
                }

                if (readIfThis(Tokens.NO)) {
                    readThis(Tokens.CHECK);

                    mode = Boolean.TRUE;
                }

                break;
            }
            case Tokens.SCALE : {
                read();

                value = readIntegerObject();
                type  = StatementTypes.SET_DATABASE_FILES_SCALE;
                names = database.schemaManager.getCatalogAndBaseTableNames();

                break;
            }
            case Tokens.SPACE : {
                read();

                if (token.tokenType == Tokens.TRUE) {
                    flag = Boolean.TRUE;

                    read();
                } else if (token.tokenType == Tokens.FALSE) {
                    flag = Boolean.FALSE;

                    read();
                } else {
                    value = readIntegerObject();
                }

                type  = StatementTypes.SET_DATABASE_FILES_SPACE;
                names = database.schemaManager.getCatalogAndBaseTableNames();

                break;
            }
            case Tokens.LOB : {
                read();

                if (readIfThis(Tokens.SCALE)) {
                    value = readIntegerObject();
                    type  = StatementTypes.SET_DATABASE_FILES_LOBS_SCALE;
                } else {
                    readThis(Tokens.COMPRESSED);

                    type = StatementTypes.SET_DATABASE_FILES_LOBS_COMPRESSED;
                    flag = processTrueOrFalseObject();
                }

                names = database.schemaManager.getCatalogAndBaseTableNames();

                break;
            }
            case Tokens.DEFRAG : {
                read();

                type  = StatementTypes.SET_DATABASE_FILES_DEFRAG;
                value = readIntegerObject();

                break;
            }
            case Tokens.NIO : {
                read();

                if (readIfThis(Tokens.SIZE)) {
                    value = readIntegerObject();
                } else {
                    flag = processTrueOrFalseObject();
                }

                type = StatementTypes.SET_DATABASE_FILES_NIO;

                break;
            }
            case Tokens.BACKUP : {
                read();

                type = StatementTypes.SET_DATABASE_FILES_BACKUP_INCREMENT;

                readThis(Tokens.INCREMENT);

                flag = processTrueOrFalseObject();

                break;
            }
            case Tokens.LOG : {
                read();

                if (readIfThis(Tokens.SIZE)) {
                    type  = StatementTypes.SET_DATABASE_FILES_LOG_SIZE;
                    value = readIntegerObject();
                } else {
                    type = StatementTypes.SET_DATABASE_FILES_LOG;
                    flag = processTrueOrFalseObject();
                }

                break;
            }
            case Tokens.TEMP : {
                read();
                readThis(Tokens.PATH);

                type  = StatementTypes.SET_DATABASE_FILES_TEMP_PATH;
                value = readIntegerObject();

                break;
            }
            case Tokens.WRITE : {
                read();
                readThis(Tokens.DELAY);

                type = StatementTypes.SET_DATABASE_FILES_WRITE_DELAY;

                int delay = 0;

                if (token.tokenType == Tokens.TRUE) {
                    delay = database.getProperties().getDefaultWriteDelay();

                    read();
                } else if (token.tokenType == Tokens.FALSE) {
                    delay = 0;

                    read();
                } else {
                    delay = readInteger();

                    if (delay < 0) {
                        delay = 0;
                    }

                    if (token.tokenType == Tokens.MILLIS) {
                        read();
                    } else {
                        delay *= 1000;
                    }
                }

                value = Integer.valueOf(delay);

                break;
            }
            case Tokens.SCRIPT : {
                read();
                readThis(Tokens.FORMAT);

                if (token.tokenType == Tokens.TEXT) {
                    read();

                    value = Integer.valueOf(0);
                } else {
                    readThis(Tokens.COMPRESSED);

                    value = Integer.valueOf(3);
                }

                type = StatementTypes.SET_DATABASE_FILES_SCRIPT_FORMAT;

                break;
            }
            default :
                throw unexpectedToken();
        }

        Object[] args = new Object[2];

        args[0] = flag == null ? value
                               : flag;
        args[1] = mode;

        return new StatementCommand(type, args, null, names);
    }

    Object[] processTransactionCharacteristics() {

        int      level    = 0;
        boolean  readonly = false;
        Object[] args     = new Object[2];

        outerloop:
        while (true) {
            switch (token.tokenType) {

                case Tokens.READ : {
                    if (args[0] != null) {
                        throw unexpectedToken();
                    }

                    read();

                    if (token.tokenType == Tokens.ONLY) {
                        read();

                        readonly = true;
                    } else {
                        readThis(Tokens.WRITE);

                        readonly = false;
                    }

                    args[0] = Boolean.valueOf(readonly);

                    break;
                }
                case Tokens.ISOLATION : {
                    if (args[1] != null) {
                        throw unexpectedToken();
                    }

                    read();
                    readThis(Tokens.LEVEL);

                    switch (token.tokenType) {

                        case Tokens.SERIALIZABLE :
                            read();

                            level = SessionInterface.TX_SERIALIZABLE;
                            break;

                        case Tokens.READ :
                            read();

                            if (token.tokenType == Tokens.COMMITTED) {
                                read();

                                level = SessionInterface.TX_READ_COMMITTED;
                            } else if (token.tokenType == Tokens.UNCOMMITTED) {
                                read();

                                level = SessionInterface.TX_READ_UNCOMMITTED;
                            } else {
                                throw unexpectedToken();
                            }
                            break;

                        case Tokens.REPEATABLE :
                            read();
                            readThis(Tokens.READ);

                            level = SessionInterface.TX_REPEATABLE_READ;
                            break;

                        default :
                            throw unexpectedToken();
                    }

                    args[1] = Integer.valueOf(level);

                    break;
                }
                case Tokens.COMMA : {
                    if (args[0] == null && args[1] == null) {
                        throw unexpectedToken();
                    }

                    read();

                    break;
                }
                default : {
                    break outerloop;
                }
            }
        }

        if (!readonly && level == 1) {
            throw unexpectedToken(Tokens.T_WRITE);
        }

        return args;
    }

    /**
     * Responsible for  handling the execution of COMMIT [WORK]
     *
     * @throws  HsqlException
     */
    private Statement compileCommit() {

        boolean chain = false;

        read();
        readIfThis(Tokens.WORK);

        if (token.tokenType == Tokens.AND) {
            read();

            if (token.tokenType == Tokens.NO) {
                read();
            } else {
                chain = true;
            }

            readThis(Tokens.CHAIN);
        }

        String sql = chain ? StatementSession.commitAndChainStatement.sql
                           : StatementSession.commitNoChainStatement.sql;
        Statement st = new StatementSession(StatementTypes.COMMIT_WORK,
                                            new Object[]{
                                                Boolean.valueOf(chain) });

        st.setSQL(sql);

        return st;
    }

    private Statement compileStartTransaction() {

        read();
        readThis(Tokens.TRANSACTION);

        Object[] args = processTransactionCharacteristics();
        Statement cs = new StatementSession(StatementTypes.START_TRANSACTION,
                                            args);

        return cs;
    }

    private Statement compileLock() {

        read();

        if (readIfThis(Tokens.CATALOG)) {
            return compileLockCatalog();
        } else {
            readThis(Tokens.TABLE);

            return compileLockTable();
        }
    }

    private Statement compileLockCatalog() {

        boolean isLock    = processTrueOrFalse();
        int statementType = isLock ? StatementTypes.TRANSACTION_LOCK_CATALOG
                                   : StatementTypes.TRANSACTION_UNLOCK_CATALOG;
        HsqlName[] writeTableNames =
            isLock ? database.schemaManager.getCatalogAndBaseTableNames()
                   : null;
        Statement cs = new StatementSession(statementType, null,
                                            writeTableNames);

        return cs;
    }

    private Statement compileLockTable() {

        OrderedHashSet readSet  = new OrderedHashSet();
        OrderedHashSet writeSet = new OrderedHashSet();

        outerloop:
        while (true) {
            Table table = readTableName(true);

            switch (token.tokenType) {

                case Tokens.READ :
                    read();
                    readSet.add(table.getName());
                    break;

                case Tokens.WRITE :
                    read();
                    writeSet.add(table.getName());
                    break;

                default :
                    throw unexpectedToken();
            }

            if (token.tokenType == Tokens.COMMA) {
                read();

                continue;
            }

            break outerloop;
        }

        HsqlName[] writeTableNames = new HsqlName[writeSet.size()];

        writeSet.toArray(writeTableNames);
        readSet.removeAll(writeTableNames);

        HsqlName[] readTableNames = new HsqlName[readSet.size()];

        readSet.toArray(readTableNames);

        Statement cs =
            new StatementSession(StatementTypes.TRANSACTION_LOCK_TABLE,
                                 readTableNames, writeTableNames);

        return cs;
    }

    private Statement compileRollback() {

        boolean chain     = false;
        String  savepoint = null;

        read();

        if (token.tokenType == Tokens.WORK) {
            read();
        }

        if (token.tokenType == Tokens.TO) {
            read();
            readThis(Tokens.SAVEPOINT);
            checkIsSimpleName();

            savepoint = token.tokenString;

            read();

            Object[] args = new Object[]{ savepoint };
            Statement cs =
                new StatementSession(StatementTypes.ROLLBACK_SAVEPOINT, args);

            return cs;
        } else {
            if (token.tokenType == Tokens.AND) {
                read();

                if (token.tokenType == Tokens.NO) {
                    read();
                } else {
                    chain = true;
                }

                readThis(Tokens.CHAIN);
            }
        }

        String sql = chain ? StatementSession.rollbackAndChainStatement.sql
                           : StatementSession.rollbackNoChainStatement.sql;
        Statement st = new StatementSession(StatementTypes.ROLLBACK_WORK,
                                            new Object[]{
                                                Boolean.valueOf(chain) });

        st.setSQL(sql);

        return st;
    }

    private Statement compileSavepoint() {

        String name;

        read();
        checkIsSimpleName();

        name = token.tokenString;

        read();

        Object[] args = new Object[]{ name };

        return new StatementSession(StatementTypes.SAVEPOINT, args);
    }

    private Statement compileReleaseSavepoint() {

        read();
        readThis(Tokens.SAVEPOINT);

        String name = token.tokenString;

        read();

        Object[] args = new Object[]{ name };

        return new StatementSession(StatementTypes.RELEASE_SAVEPOINT, args);
    }

    private Statement compileSessionSettings() {

        switch (token.tokenType) {

            case Tokens.CHARACTERISTICS : {
                read();
                readThis(Tokens.AS);
                readThis(Tokens.TRANSACTION);

                Object[] args = processTransactionCharacteristics();

                return new StatementSession(
                    StatementTypes.SET_SESSION_CHARACTERISTICS, args);
            }
            case Tokens.AUTHORIZATION : {
                read();

                Expression e = XreadValueSpecificationOrNull();

                if (e == null) {
                    throw Error.error(ErrorCode.X_42584);
                }

                e.resolveTypes(session, null);

                if (e.isUnresolvedParam()) {
                    e.dataType = Type.SQL_VARCHAR;
                }

                if (e.dataType == null || !e.dataType.isCharacterType()) {
                    throw Error.error(ErrorCode.X_42563);
                }

                Expression[] args = new Expression[] {
                    e, null
                };

                return new StatementSession(
                    session, compileContext,
                    StatementTypes.SET_SESSION_AUTHORIZATION, args);
            }
            case Tokens.RESULT : {
                read();
                readThis(Tokens.MEMORY);
                readThis(Tokens.ROWS);

                Integer  size = readIntegerObject();
                Object[] args = new Object[]{ size };

                return new StatementSession(
                    StatementTypes.SET_SESSION_RESULT_MEMORY_ROWS, args);
            }
            case Tokens.FEATURE : {
                read();

                String   feature = parseSQLFeatureValue();
                Boolean  value   = processTrueOrFalseObject();
                Object[] args    = new Object[] {
                    feature, value
                };

                return new StatementSession(StatementTypes.SET_SESSION_FEATURE,
                                            args);
            }
            default :
                throw unexpectedToken();
        }
    }

    private Statement compileSetRole() {

        Expression e;

        if (token.tokenType == Tokens.NONE) {
            read();

            e = new ExpressionValue(null, Type.SQL_VARCHAR);
        } else {
            e = XreadValueSpecificationOrNull();

            if (e == null) {
                throw Error.error(ErrorCode.X_2A000);
            }

            if (!e.getDataType().isCharacterType()) {
                throw Error.error(ErrorCode.X_0P000);
            }

            if (e.getType() != OpTypes.VALUE
                    && (e.getType() != OpTypes.SQL_FUNCTION
                        || !((FunctionSQL) e).isValueFunction())) {
                throw Error.error(ErrorCode.X_0P000);
            }
        }

        return new StatementSession(session, compileContext,
                                    StatementTypes.SET_ROLE,
                                    new Expression[]{ e });
    }

    private Statement compileSetTimeZone() {

        Expression e;

        readThis(Tokens.ZONE);

        if (token.tokenType == Tokens.LOCAL) {
            read();

            e = new ExpressionValue(null, Type.SQL_INTERVAL_HOUR_TO_MINUTE);
        } else {
            e = XreadIntervalValueExpression();

            HsqlList unresolved = e.resolveColumnReferences(session,
                RangeGroup.emptyGroup, RangeGroup.emptyArray, null);

            ExpressionColumn.checkColumnsResolved(unresolved);
            e.resolveTypes(session, null);

            if (e.dataType == null) {
                throw Error.error(ErrorCode.X_42563);
            }

            if (e.dataType.typeCode != Types.SQL_INTERVAL_HOUR_TO_MINUTE) {
                throw Error.error(ErrorCode.X_42563);
            }
        }

        return new StatementSession(session, compileContext,
                                    StatementTypes.SET_TIME_ZONE,
                                    new Expression[]{ e });
    }

    private Statement compileShutdown() {

        int closemode;

        session.checkAdmin();

        closemode = Database.CLOSEMODE_NORMAL;

        read();

        switch (token.tokenType) {

            case Tokens.IMMEDIATELY :
                closemode = Database.CLOSEMODE_IMMEDIATELY;

                read();
                break;

            case Tokens.COMPACT :
                closemode = Database.CLOSEMODE_COMPACT;

                read();
                break;

            case Tokens.SCRIPT :
                closemode = Database.CLOSEMODE_SCRIPT;

                read();
                break;

            default :
        }

        // only semicolon is accepted here
        if (token.tokenType == Tokens.SEMICOLON) {
            read();
        }

        if (token.tokenType != Tokens.X_ENDPARSE) {
            throw unexpectedToken();
        }

        Object[] args = new Object[]{ Integer.valueOf(closemode) };
        Statement cs = new StatementCommand(StatementTypes.DATABASE_SHUTDOWN,
                                            args, null, null);

        return cs;
    }

    private Statement compileBackup() {

        String  path;
        Boolean blockingMode = null;    // defaults to blocking
        Boolean scriptMode   = null;    // defaults to non-script
        Boolean compression  = null;    // defaults to compressed
        Boolean files        = null;    // defaults to false

        read();
        readThis(Tokens.DATABASE);
        readThis(Tokens.TO);

        path = readQuotedString();
        path = path.trim();

        if (path.length() == 0) {
            throw unexpectedToken(path);
        }

        outerLoop:
        while (true) {
            switch (token.tokenType) {

                case Tokens.BLOCKING :
                    if (blockingMode != null) {
                        throw unexpectedToken();
                    }

                    blockingMode = Boolean.TRUE;

                    read();
                    break;

                case Tokens.SCRIPT :
                    if (scriptMode != null) {
                        throw unexpectedToken();
                    }

                    scriptMode = Boolean.TRUE;

                    read();
                    break;

                case Tokens.COMPRESSED :
                    if (compression != null) {
                        throw unexpectedToken();
                    }

                    compression = Boolean.TRUE;

                    read();
                    break;

                case Tokens.NOT :
                    read();

                    if (token.tokenType == Tokens.COMPRESSED) {
                        if (compression != null) {
                            throw unexpectedToken();
                        }

                        compression = Boolean.FALSE;

                        read();
                    } else if (token.tokenType == Tokens.BLOCKING) {
                        if (blockingMode != null) {
                            throw unexpectedToken();
                        }

                        blockingMode = Boolean.FALSE;

                        read();
                    } else {
                        throw unexpectedToken();
                    }
                    break;

                case Tokens.AS :
                    if (files != null) {
                        throw unexpectedToken();
                    }

                    read();
                    readThis(Tokens.FILES);

                    files = Boolean.TRUE;
                    break;

                default :
                    break outerLoop;
            }
        }

        if (scriptMode == null) {
            scriptMode = Boolean.FALSE;
        }

        if (blockingMode == null) {
            blockingMode = Boolean.TRUE;
        }

        if (compression == null) {
            compression = Boolean.TRUE;
        }

        if (files == null) {
            files = Boolean.FALSE;
        }

        if (scriptMode) {
            if (!blockingMode) {
                throw unexpectedToken(Tokens.T_NOT);
            }
        }

        HsqlName[] names =
            blockingMode ? database.schemaManager.getCatalogAndBaseTableNames()
                         : HsqlName.emptyArray;
        Object[] args = new Object[] {
            path, blockingMode, scriptMode, compression, files
        };
        Statement cs = new StatementCommand(StatementTypes.DATABASE_BACKUP,
                                            args, null, names);

        return cs;
    }

    private Statement compilePerform() {

        Integer type   = Integer.valueOf(1);
        Integer number = Integer.valueOf(-1);

        read();

        switch (token.tokenType) {

            case Tokens.CHECK : {
                read();

                HsqlName tableName = null;

                readThis(Tokens.TABLE);
                readThis(Tokens.SPACE);

                if (readIfThis(Tokens.AND)) {
                    readThis("FIX");

                    type = Integer.valueOf(2);
                }

                Object[] args = new Object[] {
                    tableName, type, number
                };
                HsqlName[] names =
                    database.schemaManager.getCatalogAndBaseTableNames();

                return new StatementCommand(StatementTypes.CHECK_INDEX, args,
                                            null, names);
            }
            default :
                throw unexpectedToken();
        }
    }

    private Statement compileCheckpoint() {

        boolean defrag = false;

        read();

        if (token.tokenType == Tokens.DEFRAG) {
            defrag = true;

            read();
        } else if (token.tokenType == Tokens.SEMICOLON) {
            read();

            // only semicolon is accepted here
        }

        if (token.tokenType != Tokens.X_ENDPARSE) {
            throw unexpectedToken();
        }

        HsqlName[] names =
            database.schemaManager.getCatalogAndBaseTableNames();
        Object[] args = new Object[]{ Boolean.valueOf(defrag) };
        Statement cs = new StatementCommand(StatementTypes.DATABASE_CHECKPOINT,
                                            args, null, names);

        return cs;
    }

    public static Statement getAutoCheckpointStatement(Database database) {

        HsqlName[] names =
            database.schemaManager.getCatalogAndBaseTableNames();
        Object[] args = new Object[]{ Boolean.FALSE };
        Statement cs = new StatementCommand(StatementTypes.DATABASE_CHECKPOINT,
                                            args, null, names);

        cs.setCompileTimestamp(database.txManager.getGlobalChangeTimestamp());
        cs.setSQL(Tokens.T_CHECKPOINT);

        return cs;
    }

    private Statement compileDisconnect() {

        read();

        String sql = Tokens.T_DISCONNECT;
        Statement cs = new StatementSession(StatementTypes.DISCONNECT,
                                            (Object[]) null);

        return cs;
    }

    private Statement compileExplainPlan() {

        Statement cs;

        readThis(Tokens.PLAN);
        readThis(Tokens.FOR);

        cs = compilePart(ResultProperties.defaultPropsValue);

        cs.setDescribe();

        return new StatementCommand(StatementTypes.EXPLAIN_PLAN,
                                    new Object[]{ cs });
    }

    private Statement compileExplainReferences() {

        SchemaObject object;
        boolean      referencesFrom = false;

        readThis(Tokens.REFERENCES);

        if (!readIfThis(Tokens.TO)) {
            readThis(Tokens.FROM);

            referencesFrom = true;
        }

        int type;

        switch (token.tokenType) {

            case Tokens.TABLE :
            case Tokens.VIEW :
                read();

                type = SchemaObject.TABLE;
                break;

            case Tokens.SPECIFIC :
                read();
                readThis(Tokens.ROUTINE);

                type = SchemaObject.SPECIFIC_ROUTINE;
                break;

            case Tokens.DOMAIN :
            case Tokens.TYPE :
                read();

                type = SchemaObject.DOMAIN;
                break;

            default :
                throw unexpectedToken();
        }

        object = readSchemaObjectName(type);

        HsqlName name = object.getName();

        if (object instanceof Routine) {
            name = ((Routine) object).getSpecificName();
        }

        return new StatementCommand(StatementTypes.EXPLAIN_REFERENCES,
                                    new Object[] {
            name, Boolean.valueOf(referencesFrom)
        });
    }

    private StatementCommand compileTableSource(Table t) {

        boolean  isSourceHeader = false;
        boolean  isDesc         = false;
        String   source;
        Object[] args = new Object[5];

        args[0] = t.getName();

        if (!t.isText()) {
            Exception e = Error.error(ErrorCode.X_S0522);
        }

        // SET TABLE <table> SOURCE ON
        if (token.tokenType == Tokens.ON) {
            read();

            String sql = getLastPart();

            args[1] = Boolean.TRUE;

            return new StatementCommand(StatementTypes.SET_TABLE_SOURCE, args,
                                        null, new HsqlName[]{ t.getName() });
        } else if (token.tokenType == Tokens.OFF) {
            read();

            String sql = getLastPart();

            args[1] = Boolean.FALSE;

            return new StatementCommand(StatementTypes.SET_TABLE_SOURCE, args,
                                        null, new HsqlName[]{ t.getName() });
        } else if (token.tokenType == Tokens.HEADER) {
            read();

            isSourceHeader = true;
        }

        if (token.tokenType == Tokens.X_DELIMITED_IDENTIFIER) {
            source = token.tokenString;

            read();
        } else {
            source = readQuotedString();
        }

        if (!isSourceHeader && token.tokenType == Tokens.DESC) {
            isDesc = true;

            read();
        }

        String sql = getLastPart();

        args[2] = source;
        args[3] = Boolean.valueOf(isDesc);
        args[4] = Boolean.valueOf(isSourceHeader);

        int type = isSourceHeader ? StatementTypes.SET_TABLE_SOURCE_HEADER
                                  : StatementTypes.SET_TABLE_SOURCE;

        return new StatementCommand(type, args, null,
                                    new HsqlName[]{ t.getName() });
    }
}
