/* 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 java.lang.reflect.Method;

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.index.Index;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.List;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.lib.OrderedIntHashSet;
import org.hsqldb.map.ValuePool;
import org.hsqldb.rights.GrantConstants;
import org.hsqldb.rights.Grantee;
import org.hsqldb.rights.GranteeManager;
import org.hsqldb.rights.Right;
import org.hsqldb.rights.User;
import org.hsqldb.types.Charset;
import org.hsqldb.types.Collation;
import org.hsqldb.types.Type;
import org.hsqldb.types.UserTypeModifier;

/**
 * Parser for DDL statements
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.7.3
 * @since 1.9.0
 */
public class ParserDDL extends ParserRoutine {
    //J-
    static final int[]   schemaCommands             = new int[] {
        Tokens.CREATE, Tokens.GRANT
    };
    static final short[] startStatementTokens       = new short[] {
        Tokens.CREATE, Tokens.GRANT, Tokens.ALTER, Tokens.DROP
    };
    static final short[] startStatementTokensSchema = new short[] {
        Tokens.CREATE, Tokens.GRANT,
    };

    //J+

    ParserDDL(Session session, Scanner scanner) {
        super(session, scanner);
    }

    void reset(Session session, String sql) {
        super.reset(session, sql);
    }

    StatementSchema compileCreate() {

        int     tableType;
        boolean isTable     = false;
        boolean isOrReplace = false;

        read();

        switch (token.tokenType) {

            case Tokens.GLOBAL :
                read();
                readThis(Tokens.TEMPORARY);
                readIfThis(Tokens.MEMORY);
                readThis(Tokens.TABLE);

                isTable   = true;
                tableType = TableBase.TEMP_TABLE;
                break;

            case Tokens.TEMP :
            case Tokens.TEMPORARY :
                read();
                readThis(Tokens.TABLE);

                isTable   = true;
                tableType = TableBase.TEMP_TABLE;
                break;

            case Tokens.MEMORY :
                read();
                readThis(Tokens.TABLE);

                isTable   = true;
                tableType = TableBase.MEMORY_TABLE;
                break;

            case Tokens.CACHED :
                read();
                readThis(Tokens.TABLE);

                isTable   = true;
                tableType = TableBase.CACHED_TABLE;
                break;

            case Tokens.TEXT :
                read();
                readThis(Tokens.TABLE);

                isTable   = true;
                tableType = TableBase.TEXT_TABLE;
                break;

            case Tokens.TABLE :
                read();

                isTable   = true;
                tableType = database.schemaManager.getDefaultTableType();
                break;

            default :
                tableType = TableBase.MEMORY_TABLE;
        }

        if (isTable) {
            return compileCreateTable(tableType);
        }

        if (database.sqlSyntaxOra) {
            if (token.tokenType == Tokens.OR) {
                read();
                readThis(Tokens.REPLACE);

                switch (token.tokenType) {

                    case Tokens.FUNCTION :
                    case Tokens.PROCEDURE :
                    case Tokens.TRIGGER :
                    case Tokens.TYPE :
                    case Tokens.VIEW :
                    case Tokens.SYNONYM :
                        break;

                    default :
                        throw unexpectedToken(Tokens.T_OR);
                }

                isOrReplace = true;
            }
        }

        switch (token.tokenType) {

            // other objects
            case Tokens.ALIAS :
                return compileCreateAlias();

            case Tokens.SEQUENCE :
                return compileCreateSequence();

            case Tokens.SCHEMA :
                return compileCreateSchema();

            case Tokens.TRIGGER :
                return compileCreateTrigger(isOrReplace);

            case Tokens.USER :
                return compileCreateUser();

            case Tokens.ROLE :
                return compileCreateRole();

            case Tokens.VIEW :
                return compileCreateView(false, isOrReplace);

            case Tokens.DOMAIN :
                return compileCreateDomain();

            case Tokens.TYPE :
                return compileCreateType(isOrReplace);

            case Tokens.CHARACTER :
                return compileCreateCharacterSet();

            case Tokens.COLLATION :
                return compileCreateCollation();

            // index
            case Tokens.UNIQUE :
                read();
                checkIsThis(Tokens.INDEX);

                return compileCreateIndex(true);

            case Tokens.INDEX :
                return compileCreateIndex(false);

            case Tokens.AGGREGATE :
                return compileCreateProcedureOrFunction(isOrReplace);

            case Tokens.FUNCTION :
            case Tokens.PROCEDURE :
                return compileCreateProcedureOrFunction(isOrReplace);

            case Tokens.SYNONYM :
                return compileCreateSynonym(isOrReplace);

            default : {
                throw unexpectedToken();
            }
        }
    }

    Statement compileAlter() {

        read();

        switch (token.tokenType) {

            case Tokens.INDEX : {
                return compileAlterIndex();
            }

            case Tokens.SCHEMA : {
                read();

                HsqlName name = readSchemaName();

                readThis(Tokens.RENAME);
                readThis(Tokens.TO);

                return compileRenameSchema(name, SchemaObject.SCHEMA);
            }

            case Tokens.CATALOG : {
                read();
                checkIsSimpleName();

                String name = token.tokenString;

                checkValidCatalogName(name);
                read();
                readThis(Tokens.RENAME);
                readThis(Tokens.TO);

                return compileRenameObject(
                    database.getCatalogName(),
                    SchemaObject.CATALOG,
                    false);
            }

            case Tokens.SEQUENCE : {
                return compileAlterSequence();
            }

            case Tokens.TABLE : {
                return compileAlterTable();
            }

            case Tokens.USER : {
                return compileAlterUser();
            }

            case Tokens.DOMAIN : {
                return compileAlterDomain();
            }

            case Tokens.VIEW : {
                return compileAlterView();
            }

            case Tokens.SESSION : {
                return compileAlterSession();
            }

            case Tokens.SPECIFIC : {
                return compileAlterSpecificRoutine();
            }

            case Tokens.ROUTINE : {
                return compileAlterRoutine();
            }

            case Tokens.CONSTRAINT : {
                read();

                Constraint constraint = (Constraint) readSchemaObjectName(
                    SchemaObject.CONSTRAINT);

                if (readIfThis(Tokens.INDEX)) {
                    return compileAlterFKIndex(constraint);
                }

                readThis(Tokens.RENAME);
                readThis(Tokens.TO);

                return compileRenameObject(
                    constraint.getName(),
                    SchemaObject.CONSTRAINT,
                    false);
            }

            default : {
                throw unexpectedToken();
            }
        }
    }

    Statement compileAlterSpecificRoutine() {

        readThis(Tokens.SPECIFIC);
        readThis(Tokens.ROUTINE);

        Routine routine = (Routine) readSchemaObjectName(
            SchemaObject.SPECIFIC_ROUTINE);

        switch (token.tokenType) {

            case Tokens.RENAME : {
                read();
                readThis(Tokens.TO);

                return compileRenameObject(
                    routine.getSpecificName(),
                    SchemaObject.SPECIFIC_ROUTINE,
                    false);
            }
        }

        return compileAlterSpecificRoutine(routine);
    }

    Statement compileAlterIndex() {

        readThis(Tokens.INDEX);

        HsqlName name = readNewSchemaObjectName(SchemaObject.INDEX, true);

        name.setSchemaIfNull(session.getCurrentSchemaHsqlName());

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

            return compileRenameObject(name, SchemaObject.INDEX, false);
        }

        readThis(Tokens.AS);

        Index index = (Index) database.schemaManager.findSchemaObject(name);

        if (index == null) {
            throw Error.error(ErrorCode.X_42501);
        }

        Table table = (Table) database.schemaManager.findSchemaObject(
            index.getName().parent);
        int[]    indexColumns = readColumnList(table, true);
        String   sql          = getLastPart();
        Object[] args = new Object[]{ table, indexColumns, index.getName() };
        HsqlName[] writeLockNames = new HsqlName[]{ database.getCatalogName(),
                table.getName() };

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_INDEX,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterFKIndex(Constraint constraint) {

        if (constraint.getConstraintType()
                != SchemaObject.ConstraintTypes.FOREIGN_KEY) {
            throw Error.error(ErrorCode.X_0A001);
        }

        if (constraint.getName().isReservedName()) {
            throw Error.error(ErrorCode.X_28502);
        }

        readThis(Tokens.ADD);

        Table table        = constraint.getRef();
        int[] newColumns   = readColumnList(table, true);
        int[] constColumns = constraint.getMainColumns();
        int   common = ArrayUtil.countCommonElements(constColumns, newColumns);

        if (common > 0) {
            throw Error.error(ErrorCode.X_0A001);
        }

        String sql = getLastPart();
        Object[] args = new Object[]{ table, newColumns, constraint.getName() };
        HsqlName[] writeLockNames = new HsqlName[]{ database.getCatalogName(),
                table.getName() };

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_CONSTRAINT,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterRoutine() {

        readThis(Tokens.ROUTINE);

        RoutineSchema routine = (RoutineSchema) readSchemaObjectName(
            SchemaObject.ROUTINE);

        readThis(Tokens.RENAME);
        readThis(Tokens.TO);

        return compileRenameObject(
            routine.getName(),
            routine.getName().type,
            false);
    }

    Statement compileDrop() {

        int          objectTokenType;
        int          objectType;
        int          statementType;
        boolean      canCascade  = false;
        boolean      cascade     = false;
        boolean      useIfExists = false;
        boolean      ifExists    = false;
        SchemaObject object;
        HsqlName[]   writeLockNames;

        read();

        objectTokenType = token.tokenType;

        switch (objectTokenType) {

            case Tokens.INDEX : {
                read();

                statementType = StatementTypes.DROP_INDEX;
                objectType    = SchemaObject.INDEX;
                useIfExists   = true;
                break;
            }

            case Tokens.ASSERTION : {
                read();

                statementType = StatementTypes.DROP_ASSERTION;
                objectType    = SchemaObject.ASSERTION;
                canCascade    = true;
                break;
            }

            case Tokens.SPECIFIC : {
                read();

                switch (token.tokenType) {

                    case Tokens.ROUTINE :
                    case Tokens.PROCEDURE :
                    case Tokens.FUNCTION :
                        read();
                        break;

                    default :
                        throw unexpectedToken();
                }

                statementType = StatementTypes.DROP_ROUTINE;
                objectType    = SchemaObject.SPECIFIC_ROUTINE;
                canCascade    = true;
                useIfExists   = true;
                break;
            }

            case Tokens.PROCEDURE : {
                read();

                statementType = StatementTypes.DROP_ROUTINE;
                objectType    = SchemaObject.PROCEDURE;
                canCascade    = true;
                useIfExists   = true;
                break;
            }

            case Tokens.FUNCTION : {
                read();

                statementType = StatementTypes.DROP_ROUTINE;
                objectType    = SchemaObject.FUNCTION;
                canCascade    = true;
                useIfExists   = true;
                break;
            }

            case Tokens.SCHEMA : {
                read();

                statementType = StatementTypes.DROP_SCHEMA;
                objectType    = SchemaObject.SCHEMA;
                canCascade    = true;
                useIfExists   = true;
                break;
            }

            case Tokens.SEQUENCE : {
                read();

                statementType = StatementTypes.DROP_SEQUENCE;
                objectType    = SchemaObject.SEQUENCE;
                canCascade    = true;
                useIfExists   = true;
                break;
            }

            case Tokens.TRIGGER : {
                read();

                statementType = StatementTypes.DROP_TRIGGER;
                objectType    = SchemaObject.TRIGGER;
                canCascade    = false;
                useIfExists   = true;
                break;
            }

            case Tokens.USER : {
                read();

                statementType = StatementTypes.DROP_USER;
                objectType    = SchemaObject.GRANTEE;
                canCascade    = true;
                break;
            }

            case Tokens.ROLE : {
                read();

                statementType = StatementTypes.DROP_ROLE;
                objectType    = SchemaObject.GRANTEE;
                canCascade    = true;
                break;
            }

            case Tokens.DOMAIN :
                read();

                statementType = StatementTypes.DROP_DOMAIN;
                objectType    = SchemaObject.DOMAIN;
                canCascade    = true;
                useIfExists   = true;
                break;

            case Tokens.TYPE :
                read();

                statementType = StatementTypes.DROP_TYPE;
                objectType    = SchemaObject.TYPE;
                canCascade    = true;
                useIfExists   = true;
                break;

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

                statementType = StatementTypes.DROP_CHARACTER_SET;
                objectType    = SchemaObject.CHARSET;
                canCascade    = false;
                useIfExists   = true;
                break;

            case Tokens.COLLATION :
                read();

                statementType = StatementTypes.DROP_COLLATION;
                objectType    = SchemaObject.COLLATION;
                canCascade    = false;
                useIfExists   = true;
                break;

            case Tokens.VIEW :
                read();

                statementType = StatementTypes.DROP_VIEW;
                objectType    = SchemaObject.VIEW;
                canCascade    = true;
                useIfExists   = true;
                break;

            case Tokens.TABLE :
                read();

                statementType = StatementTypes.DROP_TABLE;
                objectType    = SchemaObject.TABLE;
                canCascade    = true;
                useIfExists   = true;
                break;

            case Tokens.SYNONYM :
                read();

                statementType = StatementTypes.DROP_REFERENCE;
                objectType    = SchemaObject.REFERENCE;
                canCascade    = false;
                useIfExists   = true;
                break;

            default :
                throw unexpectedToken();
        }

        if (useIfExists && token.tokenType == Tokens.IF) {
            int position = getPosition();

            read();

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

                ifExists = true;
            } else {
                rewind(position);
            }
        }

        checkIsIdentifier();

        HsqlName name = null;

        switch (objectTokenType) {

            case Tokens.USER : {
                checkIsSimpleName();
                checkDatabaseUpdateAuthorisation();

                object = database.getUserManager().get(token.tokenString);

                read();
                break;
            }

            case Tokens.ROLE : {
                checkIsSimpleName();
                checkDatabaseUpdateAuthorisation();

                object = database.getGranteeManager()
                                 .getRole(token.tokenString);

                read();
                break;
            }

            case Tokens.SCHEMA : {
                name   = readNewSchemaName();
                object = database.schemaManager.findSchema(name.name);
                break;
            }

            case Tokens.TABLE : {
                boolean isSession = token.namePrePrefix == null
                                    && (Tokens.T_SESSION.equals(
                                        token.namePrefix) || Tokens.T_MODULE.equals(
                                            token.namePrefix));

                if (isSession) {
                    name = readNewSchemaObjectName(objectType, false);

                    if (!ifExists && token.tokenType == Tokens.IF) {
                        read();
                        readThis(Tokens.EXISTS);

                        ifExists = true;
                    }

                    Object[] args = new Object[]{ name,
                                                  Boolean.valueOf(ifExists) };

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

            // fall through
            default :
                name = readNewSchemaObjectName(objectType, false);

                name.setSchemaIfNull(session.getCurrentSchemaHsqlName());

                object = database.schemaManager.findSchemaObject(
                    name.name,
                    name.schema.name,
                    name.type);
        }

        if (!ifExists && useIfExists && token.tokenType == Tokens.IF) {
            read();
            readThis(Tokens.EXISTS);

            ifExists = true;
        }

        if (canCascade) {
            if (token.tokenType == Tokens.CASCADE) {
                cascade = true;

                read();

                if (database.sqlSyntaxOra) {
                    readIfThis(Tokens.CONSTRAINTS);
                }
            } else if (token.tokenType == Tokens.RESTRICT) {
                read();
            }
        }

        if (object == null) {
            writeLockNames = null;
        } else {
            if (objectType == SchemaObject.SPECIFIC_ROUTINE) {

                //
            } else {
                name = object.getName();
            }

            writeLockNames = database.schemaManager.getCatalogAndBaseTableNames(
                name);
        }

        String sql = getLastPart();
        Object[] args = new Object[]{ name, Integer.valueOf(
            objectType), Boolean.valueOf(cascade), Boolean.valueOf(ifExists) };
        Statement cs = new StatementSchema(
            sql,
            statementType,
            args,
            null,
            writeLockNames);

        return cs;
    }

    Statement compileAlterTable() {

        boolean ifExists = false;

        readThis(Tokens.TABLE);

        if (token.tokenType == Tokens.IF) {
            int position = getPosition();

            read();

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

                ifExists = true;
            } else {
                rewind(position);
            }
        }

        HsqlName name = readNewSchemaObjectName(SchemaObject.TABLE, false);

        if (name.schema == null) {
            name.schema = session.getCurrentSchemaHsqlName();
        }

        Table t;

        if (ifExists) {
            t = database.schemaManager.findUserTable(
                name.name,
                name.schema.name);
        } else {
            t = database.schemaManager.getUserTable(name);
        }

        if (t != null) {
            name = t.getName();

            checkSchemaUpdateAuthorisation(name.schema);
        }

        if (ifExists && token.tokenType != Tokens.RENAME) {
            throw unexpectedToken(Tokens.IF);
        }

        switch (token.tokenType) {

            case Tokens.RENAME : {
                read();

                if (database.sqlSyntaxPgs) {
                    if (token.tokenType != Tokens.TO) {
                        if (token.tokenType == Tokens.COLUMN) {
                            read();
                        }

                        checkIsIdentifier();

                        int columnIndex = t.getColumnIndex(token.tokenString);
                        ColumnSchema column = t.getColumn(columnIndex);

                        read();
                        readThis(Tokens.TO);

                        return compileAlterColumnRename(t, column);
                    }
                }

                readThis(Tokens.TO);

                return compileRenameObject(name, SchemaObject.TABLE, ifExists);
            }

            case Tokens.ADD : {
                read();

                HsqlName cname       = null;
                Boolean  ifNotExists = Boolean.FALSE;

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

                    ifNotExists = readIfNotExists();
                    cname = readNewDependentSchemaObjectName(
                        t.getName(),
                        SchemaObject.CONSTRAINT);
                }

                switch (token.tokenType) {

                    case Tokens.FOREIGN :
                        read();
                        readThis(Tokens.KEY);

                        return compileAlterTableAddForeignKeyConstraint(
                            t,
                            cname,
                            ifNotExists);

                    case Tokens.UNIQUE :
                        read();

                        if (database.sqlSyntaxMys) {
                            if (!readIfThis(Tokens.INDEX)) {
                                readIfThis(Tokens.KEY);
                            }
                        }

                        return compileAlterTableAddUniqueConstraint(
                            t,
                            cname,
                            ifNotExists);

                    case Tokens.CHECK :
                        read();

                        return compileAlterTableAddCheckConstraint(
                            t,
                            cname,
                            ifNotExists);

                    case Tokens.PRIMARY :
                        read();
                        readThis(Tokens.KEY);

                        return compileAlterTableAddPrimaryKey(
                            t,
                            cname,
                            ifNotExists);

                    case Tokens.PERIOD :
                        if (cname != null) {
                            throw unexpectedToken();
                        }

                        return compileAlterTableAddPeriod(t);

                    case Tokens.SYSTEM :
                        if (cname != null) {
                            throw unexpectedToken();
                        }

                        read();
                        readThis(Tokens.VERSIONING);

                        return compileAlterTableAddVersioning(t);

                    case Tokens.COLUMN :
                        if (cname != null) {
                            throw unexpectedToken();
                        }

                        read();
                        checkIsSimpleName();

                        return compileAlterTableAddColumn(t);

                    case Tokens.INDEX :
                    case Tokens.KEY :
                        if (!database.sqlSyntaxMys) {
                            throw unexpectedToken();
                        }

                        read();
                        checkIsSimpleName();

                        return compileAlterTableAddIndex(t);

                    default :
                        if (cname != null) {
                            throw unexpectedToken();
                        }

                        checkIsSimpleName();

                        return compileAlterTableAddColumn(t);
                }
            }

            case Tokens.DROP : {
                read();

                switch (token.tokenType) {

                    case Tokens.PRIMARY : {
                        read();
                        readThis(Tokens.KEY);

                        return compileAlterTableDropPrimaryKey(t);
                    }

                    case Tokens.CONSTRAINT : {
                        read();

                        return compileAlterTableDropConstraint(t);
                    }

                    case Tokens.PERIOD :
                        read();
                        readThis(Tokens.FOR);

                        return compileAlterTableDropPeriod(t);

                    case Tokens.SYSTEM :
                        read();
                        readThis(Tokens.VERSIONING);

                        return compileAlterTableDropVersioning(t);

                    case Tokens.COLUMN :
                        read();

                        return compileAlterTableDropColumn(t);

                    default :
                        return compileAlterTableDropColumn(t);
                }
            }

            case Tokens.MODIFY : {
                if (!database.sqlSyntaxOra) {
                    throw unexpectedToken();
                }

                read();

                boolean      bracket     = readIfThis(Tokens.OPENBRACKET);
                int          columnIndex = t.getColumnIndex(token.tokenString);
                ColumnSchema column      = t.getColumn(columnIndex);

                read();

                Statement alterStatement = compileModifyColumn(
                    t,
                    column,
                    columnIndex);

                if (bracket) {
                    readThis(Tokens.CLOSEBRACKET);
                }

                return alterStatement;
            }

            case Tokens.ALTER : {
                read();

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

                int          columnIndex = t.getColumnIndex(token.tokenString);
                ColumnSchema column      = t.getColumn(columnIndex);

                read();

                return compileAlterColumn(t, column, columnIndex);
            }

            default : {
                throw unexpectedToken();
            }
        }
    }

    private Statement compileAlterTableDropConstraint(Table table) {

        boolean cascade = false;
        SchemaObject object = readSchemaObjectName(
            table.getSchemaName(),
            SchemaObject.CONSTRAINT);

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

            cascade = true;
        }

        String   sql           = getLastPart();
        Object[] args = new Object[]{ object.getName(),
                                      ValuePool.getInt(
                                          SchemaObject.CONSTRAINT), Boolean.valueOf(
                                                  cascade), Boolean.FALSE };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());
        HsqlName mainTableName = ((Constraint) object).getMainTableName();

        if (mainTableName != null && mainTableName != table.getName()) {
            writeLockNames = (HsqlName[]) ArrayUtil.toAdjustedArray(
                writeLockNames,
                mainTableName,
                writeLockNames.length,
                1);
        }

        Statement cs = new StatementSchema(
            sql,
            StatementTypes.DROP_CONSTRAINT,
            args,
            null,
            writeLockNames);

        return cs;
    }

    private Statement compileAlterTableDropPrimaryKey(Table table) {

        boolean cascade = false;

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

            cascade = true;
        }

        if (!table.hasPrimaryKey()) {
            throw Error.error(ErrorCode.X_42501);
        }

        String       sql    = getLastPart();
        SchemaObject object = table.getPrimaryConstraint();
        Object[] args = new Object[]{ object.getName(),
                                      ValuePool.getInt(
                                          SchemaObject.CONSTRAINT), Boolean.valueOf(
                                                  cascade), Boolean.FALSE };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());
        Statement cs = new StatementSchema(
            sql,
            StatementTypes.DROP_CONSTRAINT,
            args,
            null,
            writeLockNames);

        return cs;
    }

    StatementSession compileDeclareLocalTableOrNull() {

        int position  = getPosition();
        int tableType = TableBase.TEMP_TABLE;

        try {
            readThis(Tokens.DECLARE);
            readThis(Tokens.LOCAL);
            readThis(Tokens.TEMPORARY);
            readThis(Tokens.TABLE);
        } catch (HsqlException e) {
            lastError = e;

            // may be cursor
            rewind(position);

            return null;
        }

        boolean isSession = token.namePrePrefix == null
                            && (token.namePrefix == null
                                || Tokens.T_SESSION.equals(
                                    token.namePrefix) || Tokens.T_MODULE.equals(
                                        token.namePrefix));

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

        boolean  ifNot = readIfNotExists();
        HsqlName name  = readNewSchemaObjectName(SchemaObject.TABLE, false);

        name.schema = SqlInvariants.SESSION_SCHEMA_HSQLNAME;

        Table           table = new Table(database, name, tableType);
        StatementSchema cs;

        if (token.tokenType == Tokens.AS) {
            cs = compileCreateTableAsSubqueryDefinition(table, ifNot);
        } else {
            cs = compileCreateTableBody(table, ifNot);

            HsqlArrayList<Constraint> constraints =
                (HsqlArrayList<Constraint>) cs.arguments[1];

            for (int i = 0; i < constraints.size(); i++) {
                Constraint c = constraints.get(i);

                if (c.getConstraintType()
                        == SchemaObject.ConstraintTypes.FOREIGN_KEY) {
                    throw unexpectedToken(Tokens.T_FOREIGN);
                }
            }
        }

        StatementSession ss = new StatementSession(
            StatementTypes.DECLARE_SESSION_TABLE,
            cs.arguments);

        return ss;
    }

    StatementSchema compileCreateView(boolean alter, boolean orReplace) {

        read();

        Boolean ifNotExists = Boolean.FALSE;

        if (!alter) {
            ifNotExists = readIfNotExists();
        }

        HsqlName name = readNewSchemaObjectName(SchemaObject.VIEW, true);

        name.setSchemaIfNull(session.getCurrentSchemaHsqlName());
        checkSchemaUpdateAuthorisation(name.schema);

        HsqlName[] colList = null;

        if (token.tokenType == Tokens.OPENBRACKET) {
            colList = readColumnNames(name);
        }

        readThis(Tokens.AS);

        Recorder        recorder = startRecording();
        QueryExpression queryExpression;

        try {
            isViewDefinition = true;
            queryExpression  = XreadQueryExpression();
        } catch (HsqlException e) {
            queryExpression = XreadJoinedTableAsView();
        } finally {
            isViewDefinition = false;
        }

        int check = SchemaObject.ViewCheckModes.CHECK_NONE;

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

            check = SchemaObject.ViewCheckModes.CHECK_CASCADE;

            if (readIfThis(Tokens.LOCAL)) {
                check = SchemaObject.ViewCheckModes.CHECK_LOCAL;
            } else {
                readIfThis(Tokens.CASCADED);
            }

            readThis(Tokens.CHECK);
            readThis(Tokens.OPTION);
        }

        View view = new View(database, name, colList, check);

        queryExpression.setView(view);
        queryExpression.resolve(session);

        String statementSQL = recorder.getSQL();

        view.setStatement(statementSQL);

        StatementQuery s = new StatementQuery(
            session,
            queryExpression,
            compileContext);
        String     fullSQL        = getLastPart();
        Object[]   args           = new Object[]{ view, ifNotExists };
        int        type           = alter
                                    ? StatementTypes.ALTER_VIEW
                                    : StatementTypes.CREATE_VIEW;
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            fullSQL,
            type,
            args,
            s.readTableNames,
            writeLockNames);
    }

    StatementSchema compileCreateSequence() {

        read();

        Boolean ifNotExists = readIfNotExists();

        /*
                CREATE SEQUENCE <name>
                [AS {INTEGER | BIGINT}]
                [START WITH <value>]
                [INCREMENT BY <value>]
        */
        HsqlName name = readNewSchemaObjectName(SchemaObject.SEQUENCE, false);
        NumberSequence sequence = new NumberSequence(name, Type.SQL_INTEGER);

        readSequenceOptions(sequence, true, false, false);

        String     sql            = getLastPart();
        Object[]   args           = new Object[]{ sequence, ifNotExists };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_SEQUENCE,
            args,
            null,
            writeLockNames);
    }

    StatementSchema compileCreateDomain() {

        UserTypeModifier userTypeModifier = null;
        HsqlName         name;

        read();

        name = readNewSchemaObjectName(SchemaObject.DOMAIN, false);

        readIfThis(Tokens.AS);

        Type       type          = readTypeDefinition(true, false).duplicate();
        Expression defaultClause = null;

        if (readIfThis(Tokens.DEFAULT)) {
            defaultClause = readDefaultClause(type);
        }

        userTypeModifier = new UserTypeModifier(
            name,
            SchemaObject.DOMAIN,
            type);

        userTypeModifier.setDefaultClause(defaultClause);

        type.userTypeModifier = userTypeModifier;

        HsqlArrayList<Constraint> tempConstraints = new HsqlArrayList<>();

        compileContext.currentDomain = type;

        while (true) {
            boolean end = false;

            switch (token.tokenType) {

                case Tokens.CONSTRAINT :
                case Tokens.CHECK :
                    readConstraint(type, tempConstraints);
                    break;

                default :
                    end = true;
                    break;
            }

            if (end) {
                break;
            }
        }

        compileContext.currentDomain = null;

        for (int i = 0; i < tempConstraints.size(); i++) {
            Constraint c = tempConstraints.get(i);

            c.prepareDomainCheckConstraint(session);
            userTypeModifier.addConstraint(c);
        }

        String     sql            = getLastPart();
        Object[]   args           = new Object[]{ type };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_DOMAIN,
            args,
            null,
            writeLockNames);
    }

    StatementSchema compileCreateType(boolean orReplace) {

        read();

        HsqlName name = readNewSchemaObjectName(SchemaObject.TYPE, false);

        readThis(Tokens.AS);

        Type type = readTypeDefinition(true, false).duplicate();

        readIfThis(Tokens.FINAL);

        type.userTypeModifier = new UserTypeModifier(
            name,
            SchemaObject.TYPE,
            type);

        String     sql            = getLastPart();
        Object[]   args           = new Object[]{ type };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_TYPE,
            args,
            null,
            writeLockNames);
    }

    StatementSchema compileCreateCharacterSet() {

        read();
        readThis(Tokens.SET);

        HsqlName name = readNewSchemaObjectName(SchemaObject.CHARSET, false);

        readIfThis(Tokens.AS);
        readThis(Tokens.GET);

        String schema = token.namePrefix;
        Charset source = (Charset) database.schemaManager.getCharacterSet(
            session,
            token.tokenString,
            schema);

        read();

        if (token.tokenType == Tokens.COLLATION) {
            read();
            readThis(Tokens.FROM);
            readThis(Tokens.DEFAULT);
        }

        Charset    charset        = new Charset(name, source.getName());
        String     sql            = getLastPart();
        Object[]   args           = new Object[]{ charset };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_CHARACTER_SET,
            args,
            null,
            writeLockNames);
    }

    StatementSchema compileCreateCollation() {

        read();

        HsqlName name = readNewSchemaObjectName(SchemaObject.COLLATION, false);

        name.setSchemaIfNull(session.getCurrentSchemaHsqlName());
        readThis(Tokens.FOR);

        HsqlName charsetName = readNewSchemaObjectName(
            SchemaObject.CHARSET,
            false);

        readThis(Tokens.FROM);

        HsqlName sourceName = readNewSchemaObjectName(
            SchemaObject.COLLATION,
            false);
        Boolean padSpace = null;

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

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

            padSpace = Boolean.TRUE;
        }

        String schemaName = charsetName.schema == null
                            ? null
                            : charsetName.schema.name;
        Charset charset = (Charset) database.schemaManager.getCharacterSet(
            session,
            charsetName.name,
            schemaName);

        if (charset == null) {
            throw Error.error(
                ErrorCode.X_42501,
                charsetName.getSchemaQualifiedStatementName());
        }

        schemaName = sourceName.schema == null
                     ? null
                     : sourceName.schema.name;

        Collation source = database.schemaManager.getCollation(
            session,
            sourceName.name,
            schemaName);
        Collation  collation = new Collation(name, source, charset, padSpace);
        String     sql            = getLastPart();
        Object[]   args           = new Object[]{ collation };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_COLLATION,
            args,
            null,
            writeLockNames);
    }

    StatementSchema compileCreateAlias() {

        HsqlName  name     = null;
        Routine[] routines = null;
        String    alias;
        String    methodFQN = null;

        if (!session.isProcessingScript()) {
            throw unsupportedFeature();
        }

        read();

        try {
            alias = token.tokenString;

            read();
            readThis(Tokens.FOR);

            methodFQN = token.tokenString;

            read();
        } catch (HsqlException e) {
            alias = null;
        }

        if (alias != null) {
            HsqlName schema = database.schemaManager.getDefaultSchemaHsqlName();

            name = database.nameManager.newHsqlName(
                schema,
                alias,
                SchemaObject.FUNCTION);

            Method[] methods = Routine.getMethods(methodFQN);

            routines = Routine.newRoutines(session, methods);
        }

        String     sql            = getLastPart();
        Object[]   args           = new Object[]{ name, routines };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_ALIAS,
            args,
            null,
            writeLockNames);
    }

    StatementSchema compileAlterTableAddIndex(Table table) {

        HsqlName indexHsqlName = readNewDependentSchemaObjectName(
            table.getName(),
            SchemaObject.INDEX);

        if (database.sqlSyntaxMys) {
            if (readIfThis(Tokens.USING)) {
                if (!readIfThis("HASH")) {
                    readThis("BTREE");
                }
            }

            if (readIfThis(Tokens.COMMENT)) {
                indexHsqlName.comment = readQuotedString();
            }
        }

        int[]    cols = readColumnList(table, true);
        String   sql  = getLastPart();
        Object[] args = new Object[] {
            table, cols, indexHsqlName, Boolean.FALSE, null, Boolean.FALSE
        };

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_INDEX,
            args,
            null,
            new HsqlName[]{ database.getCatalogName(), table.getName() });
    }

    StatementSchema compileCreateIndex(boolean unique) {

        Table    table;
        HsqlName indexHsqlName;

        read();

        Boolean ifNotExists = readIfNotExists();

        indexHsqlName = readNewSchemaObjectName(SchemaObject.INDEX, true);

        if (database.sqlSyntaxMys) {
            if (readIfThis(Tokens.USING)) {
                if (!readIfThis("HASH")) {
                    readThis("BTREE");
                }
            }
        }

        readThis(Tokens.ON);

        table = readTableName();

        HsqlName tableSchema = table.getSchemaName();

        indexHsqlName.setSchemaIfNull(tableSchema);

        indexHsqlName.parent = table.getName();

        if (indexHsqlName.schema != tableSchema) {
            throw Error.error(ErrorCode.X_42505);
        }

        indexHsqlName.schema = table.getSchemaName();

        int[] indexColumns = readColumnList(table, true);

        if (database.sqlSyntaxMys) {
            if (readIfThis(Tokens.USING)) {
                if (!readIfThis("HASH")) {
                    readThis("BTREE");
                }
            }

            if (readIfThis(Tokens.COMMENT)) {
                indexHsqlName.comment = readQuotedString();
            }
        }

        String   sql  = getLastPart();
        Object[] args = new Object[] {
            table, indexColumns, indexHsqlName, Boolean.valueOf(
                unique), null, ifNotExists
        };

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_INDEX,
            args,
            null,
            new HsqlName[]{ database.getCatalogName(), table.getName() });
    }

    StatementSchema compileCreateSchema() {

        HsqlName schemaName       = null;
        String   authorisation    = null;
        HsqlName characterSetName = null;

        read();

        Boolean ifNotExists = readIfNotExists();

        if (token.tokenType != Tokens.AUTHORIZATION) {
            schemaName = readNewSchemaName();
        }

        if (token.tokenType == Tokens.AUTHORIZATION) {
            read();
            checkIsSimpleName();

            authorisation = token.tokenString;

            read();

            if (schemaName == null) {
                Grantee owner = database.getGranteeManager().get(authorisation);

                if (owner == null) {
                    throw Error.error(ErrorCode.X_28501, authorisation);
                }

                schemaName = database.nameManager.newHsqlName(
                    owner.getName().name,
                    isDelimitedIdentifier(),
                    SchemaObject.SCHEMA);

                SqlInvariants.checkSchemaNameNotSystem(token.tokenString);
            }
        }

        if (SqlInvariants.PUBLIC_ROLE_NAME.equals(authorisation)) {
            throw Error.error(ErrorCode.X_28502, authorisation);
        }

        Grantee owner = authorisation == null
                        ? session.getGrantee()
                        : database.getGranteeManager().get(authorisation);

        if (owner == null) {
            throw Error.error(ErrorCode.X_28501, authorisation);
        }

        if (!session.getGrantee().isSchemaCreator()) {
            throw Error.error(
                ErrorCode.X_0L501,
                session.getGrantee().getName().getNameString());
        }

        if (owner instanceof User && ((User) owner).isExternalOnly) {
            throw Error.error(
                ErrorCode.X_0L000,
                session.getGrantee().getName().getNameString());
        }

        if (database.schemaManager.schemaExists(schemaName.name)) {
            if (session.isProcessingScript()
                    && SqlInvariants.PUBLIC_SCHEMA.equals(schemaName.name)) {}
            else {
                if (!ifNotExists.booleanValue()) {
                    throw Error.error(ErrorCode.X_42504, schemaName.name);
                }
            }
        }

        if (schemaName.name.equals(SqlInvariants.LOBS_SCHEMA)) {
            schemaName = SqlInvariants.LOBS_SCHEMA_HSQLNAME;
            owner      = schemaName.owner;
        }

        if (readIfThis(Tokens.DEFAULT)) {
            readThis(Tokens.CHARACTER);
            readThis(Tokens.SET);

            characterSetName = readNewSchemaObjectName(
                SchemaObject.CHARSET,
                false);
        }

        String     sql            = getLastPart();
        Object[]   args = new Object[]{ schemaName, owner, ifNotExists };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;
        StatementSchema cs = new StatementSchema(
            sql,
            StatementTypes.CREATE_SCHEMA,
            args,
            null,
            writeLockNames);

        cs.setSchemaHsqlName(schemaName);

        HsqlArrayList<StatementSchema> list = new HsqlArrayList<>(128);

        list.add(cs);
        getCompiledStatementBody(list);

        StatementSchema[] array = new StatementSchema[list.size()];

        list.toArray(array);

        boolean swapped;

        do {
            swapped = false;

            for (int i = 0; i < array.length - 1; i++) {
                if (array[i].order > array[i + 1].order) {
                    StatementSchema temp = array[i + 1];

                    array[i + 1] = array[i];
                    array[i]     = temp;
                    swapped      = true;
                }
            }
        } while (swapped);

        return new StatementSchemaDefinition(array);
    }

    void getCompiledStatementBody(List<StatementSchema> list) {

        int    position;
        String sql;
        int    statementType;

        for (boolean end = false; !end; ) {
            StatementSchema cs = null;

            position = getPosition();

            switch (token.tokenType) {

                case Tokens.CREATE :
                    read();

                    switch (token.tokenType) {

                        // not in schema definition
                        case Tokens.SCHEMA :
                        case Tokens.USER :
                        case Tokens.UNIQUE :
                            throw unexpectedToken();

                        case Tokens.INDEX :
                            statementType = StatementTypes.CREATE_INDEX;
                            sql = getStatement(
                                position,
                                startStatementTokensSchema);
                            cs = new StatementSchema(sql, statementType);
                            break;

                        case Tokens.SEQUENCE :
                            cs     = compileCreateSequence();
                            cs.sql = getLastPart(position);
                            break;

                        case Tokens.ROLE :
                            cs     = compileCreateRole();
                            cs.sql = getLastPart(position);
                            break;

                        case Tokens.DOMAIN :
                            statementType = StatementTypes.CREATE_DOMAIN;
                            sql = getStatement(
                                position,
                                startStatementTokensSchema);
                            cs = new StatementSchema(sql, statementType);
                            break;

                        case Tokens.TYPE :
                            cs     = compileCreateType(false);
                            cs.sql = getLastPart(position);
                            break;

                        case Tokens.CHARACTER :
                            cs     = compileCreateCharacterSet();
                            cs.sql = getLastPart(position);
                            break;

                        // no supported
                        case Tokens.ASSERTION :
                            throw unexpectedToken();

                        case Tokens.TABLE :
                        case Tokens.MEMORY :
                        case Tokens.CACHED :
                        case Tokens.TEMP :
                        case Tokens.GLOBAL :
                        case Tokens.TEMPORARY :
                        case Tokens.TEXT :
                            statementType = StatementTypes.CREATE_TABLE;
                            sql = getStatement(
                                position,
                                startStatementTokensSchema);
                            cs = new StatementSchema(sql, statementType);
                            break;

                        case Tokens.TRIGGER :
                            statementType = StatementTypes.CREATE_TRIGGER;
                            sql = getStatement(
                                position,
                                startStatementTokensSchema);
                            cs = new StatementSchema(sql, statementType);
                            break;

                        case Tokens.VIEW :
                            statementType = StatementTypes.CREATE_VIEW;
                            sql = getStatement(
                                position,
                                startStatementTokensSchema);
                            cs = new StatementSchema(sql, statementType);
                            break;

                        case Tokens.FUNCTION :
                        case Tokens.PROCEDURE :
                            statementType = StatementTypes.CREATE_ROUTINE;
                            sql = getStatementForRoutine(
                                position,
                                startStatementTokensSchema);
                            cs = new StatementSchema(sql, statementType);
                            break;

                        default :
                            throw unexpectedToken();
                    }

                    break;

                case Tokens.GRANT :
                    cs     = compileGrantOrRevoke();
                    cs.sql = getLastPart(position);
                    break;

                case Tokens.SEMICOLON :
                    read();

                    end = true;
                    break;

                case Tokens.X_ENDPARSE :
                    end = true;
                    break;

                default :
                    throw unexpectedToken();
            }

            if (cs != null) {
                cs.isSchemaDefinition = true;

                list.add(cs);
            }
        }
    }

    StatementSchema compileCreateRole() {

        read();

        HsqlName   name           = readNewUserIdentifier();
        String     sql            = getLastPart();
        Object[]   args           = new Object[]{ name };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_ROLE,
            args,
            null,
            writeLockNames);
    }

    StatementSchema compileCreateUser() {

        HsqlName name;
        String   password;
        Boolean  admin    = Boolean.FALSE;
        Boolean  isDigest = Boolean.FALSE;
        Grantee  grantor  = session.getGrantee();

        read();

        name = readNewUserIdentifier();

        readThis(Tokens.PASSWORD);

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

        password = readPassword();

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

            admin = Boolean.TRUE;
        }

        checkDatabaseUpdateAuthorisation();

        String     sql            = getLastPart();
        Object[] args = new Object[]{ name, password, grantor, admin,
                                      isDigest };
        HsqlName[] writeLockNames = database.schemaManager.catalogNameArray;

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_USER,
            args,
            null,
            writeLockNames);
    }

    HsqlName readNewUserIdentifier() {

        checkIsSimpleName();

        String  tokenS   = token.tokenString;
        boolean isQuoted = isDelimitedIdentifier();

        if (tokenS.equalsIgnoreCase("SA")) {
            tokenS   = "SA";
            isQuoted = false;
        }

        HsqlName name = database.nameManager.newHsqlName(
            tokenS,
            isQuoted,
            SchemaObject.GRANTEE);

        read();

        return name;
    }

    String readPassword() {

        String tokenS = token.tokenString;

        if (isUndelimitedSimpleName() || isDelimitedSimpleName()) {
            read();
        } else {
            readQuotedString();
        }

        return tokenS;
    }

    StatementSchema compileCreateSynonym(boolean isOrReplace) {

        HsqlName synonymHsqlName;
        HsqlName targetHsqlName;

        read();

        synonymHsqlName = readNewSchemaObjectName(SchemaObject.REFERENCE, true);

        readThis(Tokens.FOR);

        targetHsqlName = readNewSchemaObjectName(SchemaObject.REFERENCE, true);

        String   sql  = getLastPart();
        Object[] args = new Object[]{ synonymHsqlName, targetHsqlName };

        return new StatementSchema(
            sql,
            StatementTypes.CREATE_REFERENCE,
            args,
            null,
            new HsqlName[]{ database.getCatalogName() });
    }

    Statement compileRenameObject(HsqlName name, int type, boolean ifExists) {

        HsqlName newName = readNewSchemaObjectName(type, true);
        String   sql     = getLastPart();

        switch (type) {

            case SchemaObject.CATALOG :
                break;

            case SchemaObject.SCHEMA :
                checkSchemaUpdateAuthorisation(session, name);
                break;

            default :
                name.setSchemaIfNull(session.getCurrentSchemaHsqlName());
                checkSchemaUpdateAuthorisation(session, name.schema);
        }

        Object[] args = new Object[]{ name, newName, Boolean.valueOf(
            ifExists) };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogNameArray();

        return new StatementSchema(
            sql,
            StatementTypes.RENAME_OBJECT,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterTableAddUniqueConstraint(
            Table table,
            HsqlName name,
            Boolean ifNotExists) {

        if (name == null) {
            name = database.nameManager.newAutoName(
                "CT",
                table.getSchemaName(),
                table.getName(),
                SchemaObject.CONSTRAINT);
        }

        int[]  cols = readColumnList(table, false);
        HsqlName indexName =
            session.database.nameManager.newConstraintIndexName(
                table.getName(),
                name,
                session.database.sqlSysIndexNames);
        Index index = table.createIndexStructure(
            indexName,
            cols,
            null,
            null,
            false,
            true,
            true,
            false);
        Constraint constraint = new Constraint(
            name,
            table,
            index,
            SchemaObject.ConstraintTypes.UNIQUE);
        String sql  = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ADD_CONSTRAINT, table,
                                      constraint, ifNotExists };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterTableAddForeignKeyConstraint(
            Table table,
            HsqlName name,
            Boolean ifNotExists) {

        if (name == null) {
            name = database.nameManager.newAutoName(
                "FK",
                table.getSchemaName(),
                table.getName(),
                SchemaObject.CONSTRAINT);
        }

        OrderedHashSet<String> set           = readColumnNames(false);
        Constraint             c = readFKReferences(table, name, set);
        HsqlName               mainTableName = c.getMainTableName();

        c.core.mainTable = database.schemaManager.getUserTable(mainTableName);

        c.setColumnsIndexes(table);

        if (c.core.mainCols.length != c.core.refCols.length) {
            throw Error.error(ErrorCode.X_42593);
        }

        String sql = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ADD_CONSTRAINT, table, c,
                                      ifNotExists };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        if (mainTableName != table.getName()) {
            writeLockNames = (HsqlName[]) ArrayUtil.toAdjustedArray(
                writeLockNames,
                mainTableName,
                writeLockNames.length,
                1);
        }

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterTableAddCheckConstraint(
            Table table,
            HsqlName name,
            Boolean ifNotExists) {

        Constraint check;

        if (name == null) {
            name = database.nameManager.newAutoName(
                "CT",
                table.getSchemaName(),
                table.getName(),
                SchemaObject.CONSTRAINT);
        }

        check = new Constraint(name, null, SchemaObject.ConstraintTypes.CHECK);

        readCheckConstraintCondition(check);

        String sql = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ADD_CONSTRAINT, table,
                                      check, ifNotExists };
        HsqlName[] writeLockNames = new HsqlName[]{ database.getCatalogName(),
                table.getName() };

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterTableAddColumn(Table table) {

        int                       colIndex    = table.getColumnCount();
        HsqlArrayList<Constraint> list        = new HsqlArrayList<>();
        Constraint constraint = new Constraint(
            null,
            null,
            SchemaObject.ConstraintTypes.TEMP);
        Boolean                   ifNotExists = readIfNotExists();

        list.add(constraint);
        checkIsSimpleName();
        checkIsSchemaObjectName();

        HsqlName hsqlName = database.nameManager.newColumnHsqlName(
            table.getName(),
            token.tokenString,
            isDelimitedIdentifier());

        read();

        ColumnSchema column = readColumnDefinitionOrNull(table, hsqlName, list);

        if (column == null) {
            throw Error.error(ErrorCode.X_42000);
        }

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

            colIndex = table.getColumnIndex(token.tokenString);

            read();
        }

        String   sql  = getLastPart();
        Object[] args = new Object[] {
            Integer.valueOf(
                StatementTypes.ADD_COLUMN), table, column, Integer.valueOf(
                    colIndex), list, ifNotExists
        };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterTableAddPrimaryKey(
            Table table,
            HsqlName name,
            Boolean ifNotExists) {

        if (name == null) {
            name = session.database.nameManager.newAutoName(
                "PK",
                table.getSchemaName(),
                table.getName(),
                SchemaObject.CONSTRAINT);
        }

        OrderedHashSet<String> set = readColumnNames(false);
        Constraint constraint = new Constraint(
            name,
            set,
            SchemaObject.ConstraintTypes.PRIMARY_KEY);

        constraint.setColumnsIndexes(table);

        String sql = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ADD_CONSTRAINT, table,
                                      constraint, ifNotExists };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterTableDropColumn(Table table) {

        boolean cascade = false;

        checkIsSimpleName();

        String colName = token.tokenString;

        read();

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

            cascade = true;
        }

        int colindex = table.getColumnIndex(colName);

        if (table.getColumnCount() == 1) {
            throw Error.error(ErrorCode.X_42591);
        }

        String sql = getLastPart();
        Object[] args = new Object[]{ table.getColumn(
            colindex).getName(), ValuePool.getInt(
                SchemaObject.COLUMN), Boolean.valueOf(cascade), Boolean.FALSE };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.DROP_COLUMN,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterColumn(
            Table table,
            ColumnSchema column,
            int columnIndex) {

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

        switch (token.tokenType) {

            case Tokens.RENAME : {
                read();
                readThis(Tokens.TO);

                return compileAlterColumnRename(table, column);
            }

            case Tokens.DROP : {
                read();

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

                    String sql = getLastPart();
                    Object[] args = new Object[]{
                        StatementTypes.ALTER_COLUMN_DROP_DEFAULT,
                        table, column,
                        Integer.valueOf(
                            columnIndex) };

                    return new StatementSchema(
                        sql,
                        StatementTypes.ALTER_TABLE,
                        args,
                        null,
                        writeLockNames);
                } else if (token.tokenType == Tokens.EXPRESSION) {
                    read();

                    String sql = getLastPart();
                    Object[] args = new Object[]{
                        StatementTypes.ALTER_COLUMN_DROP_EXPRESSION,
                        table, column,
                        Integer.valueOf(
                            columnIndex) };

                    return new StatementSchema(
                        sql,
                        StatementTypes.ALTER_TABLE,
                        args,
                        null,
                        writeLockNames);
                } else if (token.tokenType == Tokens.GENERATED
                           || token.tokenType == Tokens.IDENTITY) {
                    read();

                    String sql = getLastPart();
                    Object[] args = new Object[]{
                        StatementTypes.ALTER_COLUMN_DROP_GENERATED,
                        table, column,
                        Integer.valueOf(
                            columnIndex) };

                    return new StatementSchema(
                        sql,
                        StatementTypes.ALTER_TABLE,
                        args,
                        null,
                        writeLockNames);
                } else if (token.tokenType == Tokens.NOT) {
                    read();
                    readThis(Tokens.NULL);

                    String sql = getLastPart();
                    Object[] args = new Object[]{
                        StatementTypes.ALTER_COLUMN_NULL,
                        table, column, Boolean.TRUE };

                    return new StatementSchema(
                        sql,
                        StatementTypes.ALTER_TABLE,
                        args,
                        null,
                        writeLockNames);
                } else {
                    throw unexpectedToken();
                }
            }

            case Tokens.SET : {
                read();

                switch (token.tokenType) {

                    case Tokens.DATA : {
                        read();
                        readThis(Tokens.TYPE);

                        return compileAlterColumnDataType(table, column);
                    }

                    case Tokens.DEFAULT : {
                        read();

                        return compileAlterColumnDefault(
                            table,
                            column,
                            columnIndex);
                    }

                    case Tokens.NOT : {

                        //ALTER TABLE .. ALTER COLUMN .. SET NOT NULL
                        read();
                        readThis(Tokens.NULL);

                        return compileAlterColumnSetNullability(
                            table,
                            column,
                            false);
                    }

                    case Tokens.NULL : {
                        read();

                        return compileAlterColumnSetNullability(
                            table,
                            column,
                            true);
                    }

                    case Tokens.GENERATED : {
                        return compileAlterColumnAddSequence(
                            table,
                            column,
                            columnIndex);
                    }

                    default :
                        rewind(position);
                        read();
                        break;
                }

                break;
            }

            case Tokens.TYPE : {
                if (database.sqlSyntaxPgs) {
                    read();

                    return compileAlterColumnDataType(table, column);
                }

                break;
            }

            case Tokens.GENERATED :
                return compileAlterColumnAddSequence(
                    table,
                    column,
                    columnIndex);

            default :
        }

        if (token.tokenType == Tokens.SET
                || token.tokenType == Tokens.RESTART) {
            if (!column.isIdentity()) {
                throw Error.error(ErrorCode.X_42535);
            }

            return compileAlterColumnSequenceOptions(
                table,
                column,
                columnIndex);
        } else {
            return compileAlterColumnDataTypeIdentity(table, column);
        }
    }

    Statement compileModifyColumn(
            Table table,
            ColumnSchema column,
            int columnIndex) {

        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());
        int       position         = getPosition();
        Statement statementNull    = null;
        Statement statementDropGen = null;
        Statement statementDef     = null;
        Statement statementGen     = null;
        Statement statementType    = null;

        try {
            statementType          = compileAlterColumnDataType(table, column);
            statementType.isLogged = false;
        } catch (HsqlException e) {
            rewind(position);
        }

        switch (token.tokenType) {

            case Tokens.DROP : {
                read();

                if (token.tokenType == Tokens.GENERATED
                        || token.tokenType == Tokens.IDENTITY) {
                    read();

                    String sql = getLastPart();
                    Object[] args = new Object[]{
                        StatementTypes.ALTER_COLUMN_DROP_GENERATED,
                        table, column,
                        Integer.valueOf(
                            columnIndex) };

                    statementDropGen = new StatementSchema(
                        sql,
                        StatementTypes.ALTER_TABLE,
                        args,
                        null,
                        writeLockNames);
                    statementDropGen.isLogged = false;
                } else {
                    throw unexpectedToken();
                }

                break;
            }

            case Tokens.DEFAULT : {
                read();

                statementDef = compileAlterColumnDefault(
                    table,
                    column,
                    columnIndex);
                statementDef.isLogged = false;
                break;
            }

            case Tokens.GENERATED : {
                statementGen = compileAlterColumnAddSequence(
                    table,
                    column,
                    columnIndex);
                statementGen.isLogged = false;
                break;
            }

            default :
                break;
        }

        switch (token.tokenType) {

            case Tokens.NOT : {

                //ALTER TABLE .. ALTER COLUMN .. SET NOT NULL
                read();
                readThis(Tokens.NULL);

                statementNull = compileAlterColumnSetNullability(
                    table,
                    column,
                    false);
                statementNull.isLogged = false;
                break;
            }

            case Tokens.NULL : {
                read();

                statementNull = compileAlterColumnSetNullability(
                    table,
                    column,
                    true);
                statementNull.isLogged = false;
                break;
            }

            default :
        }

        Statement[] statements = new Statement[]{ statementType,
                statementDropGen, statementGen, statementDef, statementNull };
        String sql = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ALTER_COLUMN_PROPERTIES,
                                      table, column,
                                      Integer.valueOf(
                                          columnIndex), statements };

        statementDef = new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);

        return statementDef;
    }

    /**
     * Allows changes to type of column or addition / removal of an IDENTITY generator.
     * IDENTITY is removed if it does not appear in new column definition
     * Constraint definitions are not allowed
     */
    private Statement compileAlterColumnDataTypeIdentity(
            Table table,
            ColumnSchema column) {

        if (column.isGenerated()) {
            throw Error.error(ErrorCode.X_42561);
        }

        NumberSequence sequence = column.getIdentitySequence();
        Type           type     = column.getDataType();

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

            if (!type.isIntegralType()) {
                throw Error.error(ErrorCode.X_42561);
            }

            if (sequence == null) {
                sequence = new NumberSequence(null, type);
            }
        } else {
            type = readTypeDefinition(true, true);

            switch (token.tokenType) {

                case Tokens.IDENTITY : {
                    if (!type.isIntegralType()) {
                        throw Error.error(ErrorCode.X_42561);
                    }

                    read();

                    if (sequence == null) {
                        sequence = new NumberSequence(null, type);
                    }

                    break;
                }

                case Tokens.GENERATED : {
                    sequence = readSequence(table, column, true);
                    break;
                }

                default :
                    sequence = null;
            }
        }

        String sql = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ALTER_COLUMN_TYPE_IDENTITY,
                                      table, column, type, sequence };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    private Statement compileAlterColumnDataType(
            Table table,
            ColumnSchema column) {

        if (column.isGenerated()) {
            throw Error.error(ErrorCode.X_42561);
        }

        Type type = readTypeDefinition(true, true);

        if (column.isIdentity()) {
            if (!type.isIntegralType()) {
                throw Error.error(ErrorCode.X_42561);
            }
        }

        String sql = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ALTER_COLUMN_TYPE, table,
                                      column, type };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    private Statement compileAlterColumnSetNullability(
            Table table,
            ColumnSchema column,
            boolean nullable) {

        String sql = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ALTER_COLUMN_NULL, table,
                                      column, Boolean.valueOf(nullable) };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    private Statement compileAlterColumnDefault(
            Table table,
            ColumnSchema column,
            int columnIndex) {

        //ALTER TABLE .. ALTER COLUMN .. SET DEFAULT
        Type       type          = column.getDataType();
        Expression expr          = readDefaultClause(type);
        String     sql           = getLastPart();
        int        statementType = StatementTypes.ALTER_COLUMN_DEFAULT;
        Object     argObject     = expr;

        if (expr.opType == OpTypes.SEQUENCE) {
            statementType = StatementTypes.ALTER_COLUMN_SEQUENCE;
            argObject     = ((ExpressionColumn) expr).sequence;
        }

        Object[] args = new Object[]{ statementType, table, column,
                                      Integer.valueOf(columnIndex), argObject };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterSequence() {

        read();

        HsqlName schema = session.getSchemaHsqlName(token.namePrefix);
        NumberSequence sequence = database.schemaManager.getSequence(
            token.tokenString,
            schema.name,
            true);

        read();

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

            return compileRenameObject(
                sequence.getName(),
                SchemaObject.SEQUENCE,
                false);
        }

        checkSchemaUpdateAuthorisation(session, sequence.getName().schema);

        NumberSequence copy = sequence.duplicate();

        readSequenceOptions(copy, false, true, false);

        String     sql            = getLastPart();
        Object[]   args           = new Object[]{ sequence, copy };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogNameArray();

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_SEQUENCE,
            args,
            null,
            writeLockNames);
    }

    StatementSchema compileAlterColumnAddSequence(
            Table table,
            ColumnSchema column,
            int colIndex) {

        if (!column.getDataType().isIntegralType()) {
            throw Error.error(ErrorCode.X_42525);
        }

        if (column.isIdentity()) {
            throw Error.error(ErrorCode.X_42525);
        }

        NumberSequence sequence = readSequence(table, column, false);
        String         sql      = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ALTER_COLUMN_SEQUENCE,
                                      table, column, Integer.valueOf(
                                          colIndex), sequence };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogAndBaseTableNames(
                table.getName());

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    NumberSequence readSequence(
            Table table,
            ColumnSchema column,
            boolean withType) {

        readThis(Tokens.GENERATED);

        NumberSequence sequence;

        sequence = new NumberSequence(null, column.getDataType());

        boolean generatedAlways = false;

        if (token.tokenType == Tokens.BY) {
            read();
            readThis(Tokens.DEFAULT);
        } else {
            readThis(Tokens.ALWAYS);

            generatedAlways = true;
        }

        readThis(Tokens.AS);

        if (!withType && token.tokenType == Tokens.SEQUENCE) {
            if (generatedAlways) {
                throw unexpectedToken();
            }

            read();

            if (token.namePrefix != null) {
                if (!token.namePrefix.equals(table.getSchemaName().name)) {
                    throw unexpectedToken(token.namePrefix);
                }
            }

            sequence = database.schemaManager.getSequence(
                token.tokenString,
                table.getSchemaName().name,
                true);

            read();
        } else {
            readThis(Tokens.IDENTITY);
            sequence.setAlways(generatedAlways);

            if (token.tokenType == Tokens.OPENBRACKET) {
                read();
                readSequenceOptions(sequence, false, false, false);
                readThis(Tokens.CLOSEBRACKET);
            }

            sequence.checkValues();
        }

        return sequence;
    }

    StatementSchema compileAlterColumnSequenceOptions(
            Table table,
            ColumnSchema column,
            int columnIndex) {

        OrderedIntHashSet set      = new OrderedIntHashSet();
        NumberSequence    sequence = column.getIdentitySequence().duplicate();

        while (true) {
            boolean end = false;

            switch (token.tokenType) {

                case Tokens.RESTART : {
                    if (!set.add(token.tokenType)) {
                        throw unexpectedToken();
                    }

                    read();

                    if (readIfThis(Tokens.WITH)) {
                        long value = readBigint();

                        sequence.setCurrentValueNoCheck(value);
                    } else {
                        sequence.reset();
                    }

                    break;
                }

                case Tokens.SET :
                    read();

                    switch (token.tokenType) {

                        case Tokens.INCREMENT : {
                            if (!set.add(token.tokenType)) {
                                throw unexpectedToken();
                            }

                            read();
                            readThis(Tokens.BY);

                            long value = readBigint();

                            sequence.setIncrement(value);
                            break;
                        }

                        case Tokens.NO :
                            read();

                            if (token.tokenType == Tokens.MAXVALUE) {
                                sequence.setDefaultMaxValue();
                            } else if (token.tokenType == Tokens.MINVALUE) {
                                sequence.setDefaultMinValue();
                            } else if (token.tokenType == Tokens.CYCLE) {
                                sequence.setCycle(false);
                            } else {
                                throw unexpectedToken();
                            }

                            if (!set.add(token.tokenType)) {
                                throw unexpectedToken();
                            }

                            read();
                            break;

                        case Tokens.MAXVALUE : {
                            if (!set.add(token.tokenType)) {
                                throw unexpectedToken();
                            }

                            read();

                            long value = readBigint();

                            sequence.setMaxValueNoCheck(value);
                            break;
                        }

                        case Tokens.MINVALUE : {
                            if (!set.add(token.tokenType)) {
                                throw unexpectedToken();
                            }

                            read();

                            long value = readBigint();

                            sequence.setMinValueNoCheck(value);
                            break;
                        }

                        case Tokens.CYCLE :
                            if (!set.add(token.tokenType)) {
                                throw unexpectedToken();
                            }

                            read();
                            sequence.setCycle(true);
                            break;

                        default :
                            throw unexpectedToken();
                    }

                    break;

                default :
                    end = true;
                    break;
            }

            if (end) {
                break;
            }
        }

        sequence.checkValues();

        String sql = getLastPart();
        Object[] args = new Object[]{ StatementTypes.ALTER_COLUMN_SEQUENCE,
                                      table, column,
                                      Integer.valueOf(columnIndex), sequence };
        HsqlName[] writeLockNames = new HsqlName[]{ database.getCatalogName(),
                table.getName() };

        return new StatementSchema(
            sql,
            StatementTypes.ALTER_TABLE,
            args,
            null,
            writeLockNames);
    }

    /**
     * Responsible for handling tail of ALTER COLUMN ... RENAME ...
     */
    private Statement compileAlterColumnRename(
            Table table,
            ColumnSchema column) {

        checkIsSimpleName();

        HsqlName name = readNewSchemaObjectName(SchemaObject.COLUMN, true);

        if (table.findColumn(name.name) > -1) {
            throw Error.error(ErrorCode.X_42504, name.name);
        }

        database.schemaManager.checkColumnIsReferenced(
            table.getName(),
            column.getName());

        String   sql = getLastPart();
        Object[] args = new Object[]{ column.getName(), name, Boolean.FALSE };
        HsqlName[] writeLockNames = new HsqlName[]{ database.getCatalogName(),
                table.getName() };

        return new StatementSchema(
            sql,
            StatementTypes.RENAME_OBJECT,
            args,
            null,
            writeLockNames);
    }

    Statement compileRenameSchema(HsqlName name, int type) {

        HsqlName newName = readNewSchemaObjectName(type, true);
        String   sql     = getLastPart();

        checkSchemaUpdateAuthorisation(session, name);

        Object[]   args           = new Object[]{ name, newName,
            Boolean.FALSE };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogNameArray();

        return new StatementSchema(
            sql,
            StatementTypes.RENAME_SCHEMA,
            args,
            null,
            writeLockNames);
    }

    Statement compileAlterUser() {

        read();

        String   password;
        User     userObject;
        HsqlName userName = readNewUserIdentifier();

        userObject = database.getUserManager().get(userName.name);

        if (userName.name.equals(Tokens.T_PUBLIC)) {
            throw Error.error(ErrorCode.X_42503);
        }

        if (userName.name.equals(SqlInvariants.SYSTEM_AUTHORIZATION_NAME)) {
            throw Error.error(ErrorCode.X_42503);
        }

        readThis(Tokens.SET);

        switch (token.tokenType) {

            case Tokens.LOCAL : {
                read();

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

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

            case Tokens.PASSWORD : {
                read();

                boolean isDigest = false;

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

                password = readPassword();

                Object[] args = new Object[]{ userObject, password,
                                              Boolean.valueOf(isDigest) };
                Statement cs = new StatementCommand(
                    StatementTypes.SET_USER_PASSWORD,
                    args);
                String sql = userObject.getSetUserPasswordDigestSQL(
                    password,
                    isDigest);

                cs.setSQL(sql);

                return cs;
            }

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

                HsqlName schemaName;

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

                read();

                Object[] args = new Object[]{ userObject, schemaName };

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

            default : {
                throw unexpectedToken();
            }
        }
    }

    Statement compileAlterDomain() {

        read();

        HsqlName schema = session.getSchemaHsqlName(token.namePrefix);

        checkSchemaUpdateAuthorisation(schema);

        Type domain = database.schemaManager.getDomain(
            token.tokenString,
            schema.name,
            true);

        read();

        switch (token.tokenType) {

            case Tokens.RENAME : {
                read();
                readThis(Tokens.TO);

                return compileRenameObject(
                    domain.getName(),
                    SchemaObject.DOMAIN,
                    false);
            }

            case Tokens.DROP : {
                read();

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

                    String sql = getLastPart();
                    Object[] args = new Object[]{ StatementTypes.DROP_DEFAULT,
                                                  domain };
                    HsqlName[] writeLockNames =
                        database.schemaManager.getCatalogAndBaseTableNames(
                            domain.getName());

                    return new StatementSchema(
                        sql,
                        StatementTypes.ALTER_DOMAIN,
                        args,
                        null,
                        writeLockNames);
                } else if (token.tokenType == Tokens.CONSTRAINT) {
                    read();
                    checkIsSchemaObjectName();

                    HsqlName name = database.schemaManager.getSchemaObjectName(
                        domain.getSchemaName(),
                        token.tokenString,
                        SchemaObject.CONSTRAINT,
                        true);

                    read();

                    String sql = getLastPart();
                    Object[] args = new Object[]{
                        StatementTypes.DROP_CONSTRAINT,
                        domain, name };
                    HsqlName[] writeLockNames =
                        database.schemaManager.getCatalogAndBaseTableNames(
                            domain.getName());

                    return new StatementSchema(
                        sql,
                        StatementTypes.ALTER_DOMAIN,
                        args,
                        null,
                        writeLockNames);
                } else {
                    throw unexpectedToken();
                }
            }

            case Tokens.SET : {
                read();
                readThis(Tokens.DEFAULT);

                Expression e   = readDefaultClause(domain);
                String     sql = getLastPart();
                Object[] args = new Object[]{ StatementTypes.ADD_DEFAULT,
                                              domain, e };
                HsqlName[] writeLockNames =
                    database.schemaManager.getCatalogAndBaseTableNames(
                        domain.getName());

                return new StatementSchema(
                    sql,
                    StatementTypes.ALTER_DOMAIN,
                    args,
                    null,
                    writeLockNames);
            }

            case Tokens.ADD : {
                read();

                if (token.tokenType == Tokens.CONSTRAINT
                        || token.tokenType == Tokens.CHECK) {
                    HsqlArrayList<Constraint> tempConstraints =
                        new HsqlArrayList<>();

                    compileContext.currentDomain = domain;

                    readConstraint(domain, tempConstraints);

                    compileContext.currentDomain = null;

                    Constraint c = tempConstraints.get(0);

                    c.prepareDomainCheckConstraint(session);

                    String sql = getLastPart();
                    Object[] args = new Object[]{ StatementTypes.ADD_CONSTRAINT,
                                                  domain, c };
                    HsqlName[] writeLockNames =
                        database.schemaManager.getCatalogAndBaseTableNames(
                            domain.getName());

                    return new StatementSchema(
                        sql,
                        StatementTypes.ALTER_DOMAIN,
                        args,
                        null,
                        writeLockNames);
                }
            }
        }

        throw unexpectedToken();
    }

    private Statement compileAlterView() {

        int position = getPosition();

        read();

        String   tableName = token.tokenString;
        HsqlName schema    = session.getSchemaHsqlName(token.namePrefix);

        checkSchemaUpdateAuthorisation(schema);

        Table t = database.schemaManager.getUserTable(tableName, schema.name);

        read();

        switch (token.tokenType) {

            case Tokens.RENAME : {
                read();
                readThis(Tokens.TO);

                return compileRenameObject(
                    t.getName(),
                    SchemaObject.VIEW,
                    false);
            }

            case Tokens.AS : {
                rewind(position);

                return compileCreateView(true, false);
            }
        }

        throw unexpectedToken();
    }

    private boolean isGrantToken() {

        switch (token.tokenType) {

            case Tokens.ALL :
            case Tokens.INSERT :
            case Tokens.UPDATE :
            case Tokens.SELECT :
            case Tokens.DELETE :
            case Tokens.USAGE :
            case Tokens.EXECUTE :
            case Tokens.REFERENCES :
                return true;

            default :
                return false;
        }
    }

    StatementSchema compileGrantOrRevoke() {

        boolean grant = token.tokenType == Tokens.GRANT;

        read();

        if (isGrantToken()
                || (!grant
                    && (token.tokenType == Tokens.GRANT
                        || token.tokenType == Tokens.HIERARCHY))) {
            return compileRightGrantOrRevoke(grant);
        } else {
            return compileRoleGrantOrRevoke(grant);
        }
    }

    private StatementSchema compileRightGrantOrRevoke(boolean grant) {

        OrderedHashSet<String> granteeList   = new OrderedHashSet<>();
        Grantee                grantor       = null;
        Right                  right         = null;
        HsqlName               objectName    = null;
        boolean                isTable       = false;
        boolean                isUsage       = false;
        boolean                isExec        = false;
        boolean                isAll         = false;
        boolean                isGrantOption = false;
        boolean                isFilter      = false;
        boolean                cascade       = false;

        if (!grant) {
            if (token.tokenType == Tokens.GRANT) {
                read();
                readThis(Tokens.OPTION);
                readThis(Tokens.FOR);

                isGrantOption = true;
            } else if (token.tokenType == Tokens.HIERARCHY) {
                throw unsupportedFeature();

/*
                read();
                readThis(Token.OPTION);
                readThis(Token.FOR);
*/
            }
        }

        // ALL means all the rights the grantor can grant
        if (token.tokenType == Tokens.ALL) {
            read();

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

            right = Right.fullRights;
            isAll = true;
        } else {
            right = new Right();

            boolean loop = true;

            while (loop) {
                checkIsUndelimitedIdentifier();

                int rightType = GranteeManager.getCheckSingleRight(
                    token.tokenString);
                int                    tokenType     = token.tokenType;
                OrderedHashSet<String> columnNameSet = null;

                read();

                switch (tokenType) {

                    case Tokens.REFERENCES :
                    case Tokens.SELECT :
                    case Tokens.INSERT :
                    case Tokens.UPDATE :
                        if (token.tokenType == Tokens.OPENBRACKET) {
                            columnNameSet = readColumnNames(false);
                        }

                    // fall through
                    case Tokens.DELETE :
                        right.set(rightType, columnNameSet);

                        isTable = true;

                        if (grant && tokenType != Tokens.REFERENCES) {
                            Recorder   recorder = startRecording();
                            Expression filter   = XreadFilterExpressionOrNull();

                            if (filter != null) {
                                String sql = recorder.getSQL();

                                right.setFilterExpression(
                                    rightType,
                                    filter,
                                    sql);

                                isFilter = true;
                            }
                        }

                        break;

                    case Tokens.TRIGGER :
                        right.set(rightType, null);

                        isTable = true;
                        break;

                    case Tokens.USAGE :
                        if (isTable) {
                            throw unexpectedToken();
                        }

                        right   = Right.fullRights;
                        isUsage = true;
                        loop    = false;
                        continue;
                    case Tokens.EXECUTE :
                        if (isTable) {
                            throw unexpectedToken();
                        }

                        right  = Right.fullRights;
                        isExec = true;
                        loop   = false;
                        continue;
                }

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

                break;
            }
        }

        readThis(Tokens.ON);

        int objectType = 0;

        switch (token.tokenType) {

            case Tokens.CLASS :
                if (!isExec && !isAll) {
                    throw unexpectedToken();
                }

                read();

                if (!isSimpleName() || !isDelimitedIdentifier()) {
                    throw Error.error(ErrorCode.X_42569);
                }

                objectType = SchemaObject.FUNCTION;
                break;

            case Tokens.SPECIFIC : {
                if (!isExec && !isAll) {
                    throw unexpectedToken();
                }

                read();

                switch (token.tokenType) {

                    case Tokens.ROUTINE :
                    case Tokens.PROCEDURE :
                    case Tokens.FUNCTION :
                        read();
                        break;

                    default :
                        throw unexpectedToken();
                }

                objectType = SchemaObject.SPECIFIC_ROUTINE;
                break;
            }

            case Tokens.FUNCTION :
                if (!isExec && !isAll) {
                    throw unexpectedToken();
                }

                read();

                objectType = SchemaObject.FUNCTION;
                break;

            case Tokens.PROCEDURE :
                if (!isExec && !isAll) {
                    throw unexpectedToken();
                }

                read();

                objectType = SchemaObject.PROCEDURE;
                break;

            case Tokens.ROUTINE :
                if (!isExec && !isAll) {
                    throw unexpectedToken();
                }

                read();

                objectType = SchemaObject.ROUTINE;
                break;

            case Tokens.TYPE :
                if (!isUsage && !isAll) {
                    throw unexpectedToken();
                }

                read();

                objectType = SchemaObject.TYPE;
                break;

            case Tokens.DOMAIN :
                if (!isUsage && !isAll) {
                    throw unexpectedToken();
                }

                read();

                objectType = SchemaObject.DOMAIN;
                break;

            case Tokens.SEQUENCE :
                if (!isUsage && !isAll) {
                    throw unexpectedToken();
                }

                read();

                objectType = SchemaObject.SEQUENCE;
                break;

            case Tokens.CHARACTER :
                if (!isUsage && !isAll) {
                    throw unexpectedToken();
                }

                read();
                readThis(Tokens.SET);

                objectType = SchemaObject.CHARSET;
                break;

            case Tokens.ALL :
                read();

                if (readIfThis("SEQUENCES")) {
                    if (!isUsage && !isAll) {
                        throw unexpectedToken("SEQUENCES");
                    }

                    right = new Right();

                    right.set(GrantConstants.USAGE, null);
                } else if (readIfThis("ROUTINES")) {
                    if (!isExec && !isAll) {
                        throw unexpectedToken("ROUTINES");
                    }

                    right = new Right();

                    right.set(GrantConstants.EXECUTE, null);
                } else {
                    if (!isTable && !isAll) {
                        throw unexpectedToken();
                    }

                    readThis("TABLES");
                }

                readThis(Tokens.IN);
                readThis(Tokens.SCHEMA);

                objectType = SchemaObject.SCHEMA;
                break;

            case Tokens.TABLE :
            default :
                if (!isTable && !isAll) {
                    throw unexpectedToken();
                }

                readIfThis(Tokens.TABLE);

                objectType = SchemaObject.TABLE;
        }

        objectName = readNewSchemaObjectName(objectType, false);

        if (grant) {
            if (objectType == SchemaObject.TABLE && !isFilter) {
                Recorder   recorder = startRecording();
                Expression filter   = XreadFilterExpressionOrNull();

                if (filter != null) {
                    String sql = recorder.getSQL();

                    right.setFilterExpression(filter, sql);
                }
            }

            readThis(Tokens.TO);
        } else {
            readThis(Tokens.FROM);
        }

        while (true) {
            checkIsSimpleName();
            granteeList.add(token.tokenString);
            read();

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

        if (grant) {
            if (token.tokenType == Tokens.WITH) {
                read();
                readThis(Tokens.GRANT);
                readThis(Tokens.OPTION);

                isGrantOption = true;
            }

            /* @todo - implement */
            if (token.tokenType == Tokens.GRANTED) {
                read();
                readThis(Tokens.BY);

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

                    //
                } else {
                    readThis(Tokens.CURRENT_ROLE);

                    if (session.getRole() == null) {
                        throw Error.error(ErrorCode.X_0P000);
                    }

                    grantor = session.getRole();
                }
            }
        } else {
            if (token.tokenType == Tokens.CASCADE) {
                cascade = true;

                read();
            } else {
                readThis(Tokens.RESTRICT);
            }
        }

        String   sql              = getLastPart();
        int      type             = grant
                                    ? StatementTypes.GRANT
                                    : StatementTypes.REVOKE;
        Object[] args             = new Object[] {
            granteeList, objectName, right, grantor, Boolean.valueOf(
                cascade), Boolean.valueOf(isGrantOption)
        };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogNameArray();
        StatementSchema cs = new StatementSchema(
            sql,
            type,
            args,
            null,
            writeLockNames);

        return cs;
    }

    private StatementSchema compileRoleGrantOrRevoke(boolean grant) {

        Grantee                grantor     = session.getGrantee();
        OrderedHashSet<String> roleList    = new OrderedHashSet<>();
        OrderedHashSet<String> granteeList = new OrderedHashSet<>();
        boolean                cascade     = false;

        if (!grant && token.tokenType == Tokens.ADMIN) {
            throw unsupportedFeature();

/*
            read();
            readThis(Token.OPTION);
            readThis(Token.FOR);
*/
        }

        while (true) {
            checkIsSimpleName();
            roleList.add(token.tokenString);
            read();

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

            break;
        }

        if (grant) {
            readThis(Tokens.TO);
        } else {
            readThis(Tokens.FROM);
        }

        while (true) {
            checkIsSimpleName();
            granteeList.add(token.tokenString);
            read();

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

        if (grant) {
            if (token.tokenType == Tokens.WITH) {
                throw unsupportedFeature();

/*
                read();
                readThis(Token.ADMIN);
                readThis(Token.OPTION);
*/
            }
        }

        if (token.tokenType == Tokens.GRANTED) {
            read();
            readThis(Tokens.BY);

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

                //
            } else {
                readThis(Tokens.CURRENT_ROLE);

                if (session.getRole() == null) {
                    throw Error.error(ErrorCode.X_0P000);
                }

                grantor = session.getRole();
            }
        }

        if (!grant) {
            if (token.tokenType == Tokens.CASCADE) {
                cascade = true;

                read();
            } else {
                readThis(Tokens.RESTRICT);
            }
        }

        String sql                = getLastPart();
        int    type               = grant
                                    ? StatementTypes.GRANT_ROLE
                                    : StatementTypes.REVOKE_ROLE;
        Object[] args = new Object[]{ granteeList, roleList, grantor,
                                      Boolean.valueOf(cascade) };
        HsqlName[] writeLockNames =
            database.schemaManager.getCatalogNameArray();
        StatementSchema cs = new StatementSchema(
            sql,
            type,
            args,
            null,
            writeLockNames);

        return cs;
    }

    void checkDatabaseUpdateAuthorisation() {
        session.checkAdmin();
        session.checkDDLWrite();
    }

    void checkSchemaUpdateAuthorisation(Session session, HsqlName schema) {

        if (session.isProcessingLog()) {
            return;
        }

        if (SqlInvariants.isSystemSchemaName(schema.name)) {
            throw Error.error(ErrorCode.X_42503);
        }

        if (session.parser.isSchemaDefinition) {
            if (schema == session.getCurrentSchemaHsqlName()) {
                return;
            }

            throw Error.error(ErrorCode.X_42505, schema.name);
        }

        session.getGrantee().checkSchemaUpdateOrGrantRights(schema);
        session.checkDDLWrite();
    }

    StatementSchema compileComment() {

        HsqlName name;
        int      type;

        readThis(Tokens.COMMENT);
        readThis(Tokens.ON);

        switch (token.tokenType) {

            case Tokens.SEQUENCE :
                type = SchemaObject.SEQUENCE;
                break;

            case Tokens.TRIGGER :
                type = SchemaObject.TRIGGER;
                break;

            case Tokens.ROUTINE :
                type = SchemaObject.ROUTINE;
                break;

            case Tokens.TABLE :
                type = SchemaObject.TABLE;
                break;

            case Tokens.COLUMN : {
                type = SchemaObject.COLUMN;
                break;
            }

            case Tokens.VIEW :
                type = SchemaObject.VIEW;
                break;

            default :
                throw unexpectedToken();
        }

        read();
        checkIsSchemaObjectName();

        name = database.nameManager.newHsqlName(
            token.tokenString,
            token.isDelimitedIdentifier,
            type);

        if (type == SchemaObject.COLUMN) {
            if (token.namePrefix == null) {
                throw Error.error(ErrorCode.X_42501);
            }

            name.parent = database.nameManager.newHsqlName(
                token.namePrefix,
                token.isDelimitedPrefix,
                SchemaObject.TABLE);

            if (token.namePrePrefix == null) {
                name.parent.schema = session.getCurrentSchemaHsqlName();
            } else {
                name.parent.schema = database.nameManager.newHsqlName(
                    token.namePrePrefix,
                    token.isDelimitedPrePrefix,
                    SchemaObject.SCHEMA);
            }
        } else {
            if (token.namePrefix == null) {
                name.schema = session.getCurrentSchemaHsqlName();
            } else {
                name.schema = database.nameManager.newHsqlName(
                    token.namePrefix,
                    token.isDelimitedPrefix,
                    SchemaObject.SCHEMA);
            }
        }

        read();
        readThis(Tokens.IS);

        String   comment   = readQuotedString();
        Object[] arguments = new Object[]{ name, comment };

        return new StatementSchema(
            null,
            StatementTypes.COMMENT,
            arguments,
            null,
            null);
    }

    Statement compileAlterSession() {

        read();
        session.checkAdmin();

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

            int action = token.tokenType;

            switch (token.tokenType) {

                case Tokens.ALL :
                    read();
                    break;

                case Tokens.RESULT :
                    read();
                    readThis(Tokens.SETS);
                    break;

                case Tokens.TABLE :
                    read();
                    readThis(Tokens.DATA);
                    break;

                default :
                    throw unexpectedTokenRequire(
                        Tokens.T_ALL + ',' + Tokens.T_RESULT + ','
                        + Tokens.T_TABLE);
            }

            Object[] args = new Object[]{ Long.valueOf(
                session.getId()), Integer.valueOf(action) };

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

        long    sessionID     = readBigint();
        Session targetSession = database.sessionManager.getSession(sessionID);

        if (targetSession == null) {
            throw Error.error(ErrorCode.X_2E000);
        }

        int action = token.tokenType;

        switch (token.tokenType) {

            case Tokens.CLOSE :
                read();
                break;

            case Tokens.RELEASE :
                read();
                break;

            case Tokens.END :
                read();
                readThis(Tokens.STATEMENT);
                break;

            default :
                throw unexpectedToken();
        }

        Object[] args = new Object[]{ Long.valueOf(
            sessionID), Integer.valueOf(action) };

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

    /**
     * Retrieves boolean value corresponding to the next token.
     *
     * @return   true if next token is "TRUE"; false if next token is "FALSE"
     * @throws  HsqlException if the next token is neither "TRUE" nor "FALSE"
     */
    boolean processTrueOrFalse() {

        if (token.namePrefix != null) {
            throw unexpectedToken();
        }

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

            return true;
        } else if (token.tokenType == Tokens.FALSE) {
            read();

            return false;
        } else {
            throw unexpectedToken();
        }
    }

    Boolean processTrueOrFalseObject() {
        return Boolean.valueOf(processTrueOrFalse());
    }
}
