ParserCommand.java
/* Copyright (c) 2001-2024, 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.index.IndexStats;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.List;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.map.ValuePool;
import org.hsqldb.persist.HsqlDatabaseProperties;
import org.hsqldb.persist.RowInsertInterface;
import org.hsqldb.result.Result;
import org.hsqldb.result.ResultProperties;
import org.hsqldb.rights.User;
import org.hsqldb.types.Charset;
import org.hsqldb.types.DateTimeType;
import org.hsqldb.types.TimestampData;
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.7.3
 * @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<Statement> compileStatements(String sql, Result cmd) {
        HsqlArrayList<Statement> 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.getSystemChangeNumber());
            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(false);
                break;
            case Tokens.SHUTDOWN :
                cs = compileShutdown();
                break;
            case Tokens.BACKUP :
                cs = compileBackup();
                break;
            case Tokens.CHECKPOINT :
                cs = compileCheckpoint();
                break;
            case Tokens.EXPLAIN : {
                cs = compileExplain();
                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(boolean extended) {
        String        name      = null;
        int           scope     = 0;
        int           type      = 0;
        Boolean       colNames  = Boolean.FALSE;
        HsqlName      tableName = null;
        TimestampData timestamp = null;
        readThis(Tokens.SCRIPT);
        if (extended) {
            readThis(Tokens.FOR);
            checkIsAny(Tokens.DATABASE, Tokens.TABLE, 0, 0);
            if (readIfThis(Tokens.DATABASE)) {
                switch (token.tokenType) {
                    case Tokens.STRUCTURE :
                        read();
                        type = Tokens.STRUCTURE;
                        break;
                    case Tokens.VERSIONING :
                        read();
                        readThis(Tokens.DATA);
                        if (readIfThis(Tokens.FROM)) {
                            readThis(Tokens.TIMESTAMP);
                            String s = readQuotedString();
                            timestamp =
                                (TimestampData) Type.SQL_TIMESTAMP.convertToType(
                                    session,
                                    s,
                                    Type.SQL_VARCHAR_DEFAULT);
                        } else {
                            timestamp = DateTimeType.epochTimestamp;
                        }
                        type = Tokens.VERSIONING;
                        break;
                    case Tokens.DATA :
                        read();
                        type = Tokens.DATA;
                        break;
                    default :
                        type = Tokens.ALL;
                }
                if (readIfThis(Tokens.WITH)) {
                    readThis(Tokens.COLUMN);
                    readThis(Tokens.NAMES);
                    colNames = Boolean.TRUE;
                }
                readThis(Tokens.TO);
                scope = Tokens.DATABASE;
            } else if (readIfThis(Tokens.TABLE)) {
                Table table = readTableName();
                if (table.isView() || table.isTemp()) {
                    throw Error.error(ErrorCode.X_42501);
                }
                tableName = table.getName();
                readThis(Tokens.DATA);
                if (readIfThis(Tokens.WITH)) {
                    readThis(Tokens.COLUMN);
                    readThis(Tokens.NAMES);
                    colNames = Boolean.TRUE;
                }
                readThis(Tokens.TO);
                scope = Tokens.TABLE;
                type  = Tokens.DATA;
            } else {
                throw unexpectedToken();
            }
        }
        if (token.tokenType == Tokens.X_VALUE) {
            if (scope == 0) {
                scope = Tokens.DATABASE;
                type  = Tokens.ALL;
            }
            name = readQuotedString();
        } else if (scope == 0) {
            scope = Tokens.DATABASE;
            type  = Tokens.STRUCTURE;
        } else {
            throw unexpectedTokenRequire(Tokens.T_PATH);
        }
        HsqlName[] names = database.schemaManager.getCatalogAndBaseTableNames();
        Object[]   args  = new Object[] {
            name, Integer.valueOf(
                scope), Integer.valueOf(type), colNames, tableName, timestamp
        };
        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;
                        }
                        throw Error.error(ErrorCode.X_0P000);
                    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<SchemaObject> 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<SchemaObject> 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 : {
                readAny(Tokens.READ, Tokens.ISOLATION, 0, 0);
                Object[] args = processTransactionCharacteristics();
                if (args[0] == null && args[1] == null) {
                    throw unexpectedToken();
                }
                return new StatementSession(
                    StatementTypes.SET_TRANSACTION,
                    args);
            }
            case Tokens.AUTOCOMMIT : {
                return compileSetAutoCommit();
            }
            // 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);
            }
        }
    }
    StatementSession compileSetAutoCommit() {
        read();
        boolean mode = false;
        int     rows = -1;
        switch (token.tokenType) {
            case Tokens.TRUE :
            case Tokens.FALSE :
                mode = processTrueOrFalse();
                break;
            case Tokens.AT :
                read();
                rows = readInteger();
                if (rows < 0) {
                    throw Error.error(ErrorCode.X_22003);
                }
                readThis(Tokens.ROWS);
                break;
            default :
                throw unexpectedToken();
        }
        Object[] args = new Object[]{ mode, rows };
        return new StatementSession(
            StatementTypes.SET_SESSION_AUTOCOMMIT,
            args);
    }
    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<String> 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) {
                    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), Boolean.TRUE };
                return new StatementCommand(
                    StatementTypes.SET_DATABASE_FILES_EVENT_LOG,
                    args,
                    null,
                    null);
            }
            case Tokens.EXTERNAL : {
                read();
                readThis(Tokens.EVENT);
                readThis(Tokens.LOG);
                readThis(Tokens.LEVEL);
                Integer value = readIntegerObject();
                Object[] args = new Object[]{ value, Boolean.FALSE,
                                              Boolean.FALSE };
                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.AVG :
                        read();
                        readThis(Tokens.SCALE);
                        value    = readIntegerObject();
                        property = HsqlDatabaseProperties.sql_avg_scale;
                        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.CONVERT :
                        read();
                        readThis(Tokens.TRUNCATE);
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_convert_trunc;
                        break;
                    case Tokens.DOUBLE :
                        read();
                        readThis(Tokens.NAN);
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_double_nan;
                        break;
                    case Tokens.IGNORECASE :
                        read();
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_ignore_case;
                        break;
                    case Tokens.LIVE :
                        read();
                        readThis(Tokens.OBJECT);
                        property = HsqlDatabaseProperties.sql_live_object;
                        flag     = processTrueOrFalseObject();
                        break;
                    case Tokens.LONGVAR :
                        read();
                        readThis(Tokens.IS);
                        readThis(Tokens.LOB);
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_longvar_is_lob;
                        break;
                    case Tokens.LOWER :
                        read();
                        readThis(Tokens.CASE);
                        readThis(Tokens.IDENTIFIER);
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_lowercase_ident;
                        break;
                    case Tokens.MAX :
                        read();
                        readThis(Tokens.RECURSIVE);
                        value    = readIntegerObject();
                        property = HsqlDatabaseProperties.sql_max_recursive;
                        break;
                    case Tokens.NAMES :
                        read();
                        property = HsqlDatabaseProperties.sql_enforce_names;
                        flag     = processTrueOrFalseObject();
                        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.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.RESTRICT :
                        read();
                        readThis(Tokens.EXEC);
                        property = HsqlDatabaseProperties.sql_restrict_exec;
                        flag     = processTrueOrFalseObject();
                        break;
                    case Tokens.SIZE :
                        read();
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_enforce_size;
                        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;
                    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.TRUNCATE :
                        read();
                        readThis(Tokens.TRAILING);
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_trunc_trailing;
                        break;
                    case Tokens.TYPES :
                        read();
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_enforce_types;
                        break;
                    case Tokens.UNIQUE :
                        read();
                        readThis(Tokens.NULLS);
                        flag     = processTrueOrFalseObject();
                        property = HsqlDatabaseProperties.sql_unique_nulls;
                        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.INTERRUPT)) {
                        Boolean mode = processTrueOrFalseObject();
                        StatementCommand cs = new StatementCommand(
                            StatementTypes.SET_DATABASE_TRANSACTION_INTERRUPT,
                            new Object[]{ mode },
                            null,
                            null);
                        return cs;
                    } else {
                        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;
        Object     value = null;
        Boolean    mode  = null;
        HsqlName[] names = database.schemaManager.getCatalogNameArray();
        checkDatabaseUpdateAuthorisation();
        switch (token.tokenType) {
            case Tokens.CHECK : {
                read();
                long longValue1 = 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(longValue1);
                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 = readQuotedString();
                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);
                    checkIsAny(
                        Tokens.SERIALIZABLE,
                        Tokens.READ,
                        Tokens.REPEATABLE,
                        0);
                    switch (token.tokenType) {
                        case Tokens.SERIALIZABLE :
                            read();
                            level = SessionInterface.TX_SERIALIZABLE;
                            break;
                        case Tokens.READ :
                            readAny(Tokens.COMMITTED, Tokens.UNCOMMITTED, 0, 0);
                            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;
                }
            }
        }
        return args;
    }
    /**
     * Responsible for  handling the execution of COMMIT [WORK]
     */
    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<HsqlName> readSet  = new OrderedHashSet<>();
        OrderedHashSet<HsqlName> writeSet = new OrderedHashSet<>();
        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;
        }
        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() {
        checkIsAny(
            Tokens.CHARACTERISTICS,
            Tokens.AUTHORIZATION,
            Tokens.RESULT,
            Tokens.FEATURE);
        switch (token.tokenType) {
            case Tokens.CHARACTERISTICS : {
                read();
                readThis(Tokens.AS);
                readThis(Tokens.TRANSACTION);
                checkIsAny(Tokens.READ, Tokens.ISOLATION, 0, 0);
                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 = null;
        } else {
            e = XreadValueSpecificationOrNull();
            if (e == null) {
                throw Error.error(ErrorCode.X_2A000);
            }
        }
        return new StatementSession(
            session,
            compileContext,
            StatementTypes.SET_ROLE,
            new Expression[]{ e });
    }
    private Statement compileSetTimeZone() {
        Expression e;
        readThis(Tokens.ZONE);
        switch (token.tokenType) {
            case Tokens.LOCAL : {
                read();
                e = new ExpressionValue(null, Type.SQL_INTERVAL_HOUR_TO_MINUTE);
                break;
            }
            case Tokens.INTERVAL : {
                e = XreadIntervalValueExpression();
                List<Expression> 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);
                }
                break;
            }
            default :
                e = XreadValueExpression();
        }
        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.isEmpty()) {
            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() {
        readAny(Tokens.CHECK, Tokens.EXPORT, Tokens.IMPORT, 0);
        switch (token.tokenType) {
            case Tokens.CHECK :
                return compileCheck();
            case Tokens.IMPORT :
                readAny(Tokens.SCRIPT, Tokens.DATA, 0, 0);
                if (token.tokenType == Tokens.SCRIPT) {
                    return compileImportScript();
                } else {
                    return compileImportData();
                }
            case Tokens.EXPORT :
                readAny(Tokens.SCRIPT, Tokens.DATA, 0, 0);
                if (token.tokenType == Tokens.SCRIPT) {
                    return compileScript(true);
                } else {
                    return compileExportData();
                }
            default :
                throw unexpectedToken();
        }
    }
    /*
     * PERFORM CHECK TABLE <name> INDEX [AND FIX]
     * PERFORM CHECK ALL TABLE INDEX [AND FIX]
     */
    private Statement compileCheck() {
        readAny(Tokens.ALL, Tokens.TABLE, 0, 0);
        boolean  isAll     = false;
        HsqlName tableName = null;
        Integer  type      = Integer.valueOf(IndexStats.checkRows);
        Integer  number    = Integer.valueOf(-1);
        switch (token.tokenType) {
            case Tokens.ALL : {
                read();
                isAll = true;
            }
            // fall through
            case Tokens.TABLE : {
                readThis(Tokens.TABLE);
                if (isAll) {
                    readThis(Tokens.INDEX);
                } else {
                    tableName = readTableName().getName();
                    readThis(Tokens.INDEX);
                }
            }
        }
        if (readIfThis(Tokens.AND)) {
            readThis("FIX");
            type = Integer.valueOf(IndexStats.fixAll);
        }
        Object[]   args  = new Object[]{ tableName, type, number };
        HsqlName[] names = isAll
                           ? database.schemaManager.getCatalogAndBaseTableNames()
                           : database.schemaManager.getCatalogAndBaseTableNames(
                               tableName);
        return new StatementCommand(
            StatementTypes.CHECK_INDEX,
            args,
            null,
            names);
    }
    private Statement compileExportData() {
        readThis(Tokens.DATA);
        readThis(Tokens.FROM);
        readThis(Tokens.TABLE);
        HsqlName tableName = readTableName().getName();
        readThis(Tokens.TO);
        String     fileName = readQuotedString();
        HsqlName[] names = database.schemaManager.getCatalogAndBaseTableNames();
        Object[]   args     = new Object[]{ tableName, fileName };
        return new StatementCommand(
            StatementTypes.UNLOAD_DATA,
            args,
            null,
            names);
    }
    private Statement compileImportScript() {
        String  fileName;
        int     mode         = RowInsertInterface.modes.continueOnError;
        Boolean isVersioning = Boolean.FALSE;
        readThis(Tokens.SCRIPT);
        checkIsAny(Tokens.VERSIONING, Tokens.DATA, 0, 0);
        if (token.tokenType == Tokens.VERSIONING) {
            readThis(Tokens.VERSIONING);
            isVersioning = Boolean.TRUE;
        }
        readThis(Tokens.DATA);
        readThis(Tokens.FROM);
        fileName = readQuotedString();
        if (!isVersioning) {
            mode = readLoadMode();
        }
        HsqlName[] names = database.schemaManager.getCatalogAndBaseTableNames();
        Object[] args = new Object[]{ fileName, Integer.valueOf(
            mode), isVersioning };
        return new StatementCommand(
            StatementTypes.LOAD_SCRIPT,
            args,
            null,
            names);
    }
    private Statement compileImportData() {
        readThis(Tokens.DATA);
        readThis(Tokens.INTO);
        readThis(Tokens.TABLE);
        HsqlName tableName = readTableName().getName();
        readThis(Tokens.FROM);
        String     fileName = readQuotedString();
        int        mode     = readLoadMode();
        HsqlName[] names = database.schemaManager.getCatalogAndBaseTableNames();
        Object[] args = new Object[]{ tableName, fileName,
                                      Integer.valueOf(mode) };
        return new StatementCommand(
            StatementTypes.LOAD_DATA,
            args,
            null,
            names);
    }
    private int readLoadMode() {
        int mode = -1;
        checkIsAny(Tokens.CONTINUE, Tokens.STOP, Tokens.CHECK, 0);
        switch (token.tokenType) {
            case Tokens.CONTINUE :
                mode = RowInsertInterface.modes.continueOnError;
                read();
                break;
            case Tokens.STOP :
                mode = RowInsertInterface.modes.stopOnError;
                read();
                break;
            case Tokens.CHECK :
                mode = RowInsertInterface.modes.checkUntillError;
                read();
                break;
            default :
                throw unexpectedToken();
        }
        readThis(Tokens.ON);
        readThis(Tokens.ERROR);
        return mode;
    }
    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.getSystemChangeNumber());
        cs.setSQL(Tokens.T_CHECKPOINT);
        return cs;
    }
    private Statement compileDisconnect() {
        read();
        String    sql = Tokens.T_DISCONNECT;
        Statement cs  = new StatementSession(StatementTypes.DISCONNECT, null);
        return cs;
    }
    private Statement compileExplain() {
        Statement cs;
        int       position = getPosition();
        readAny(Tokens.PLAN, Tokens.REFERENCES, 0, 0);
        if (token.tokenType == Tokens.PLAN) {
            cs = compileExplainPlan();
        } else {
            cs = compileExplainReferences();
        }
        cs.setSQL(getLastPart(position));
        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;
        readAny(Tokens.TO, Tokens.FROM, 0, 0);
        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;
            case Tokens.SEQUENCE :
                read();
                type = SchemaObject.SEQUENCE;
                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()) {
            throw 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() });
    }
}